import from HEAD:
[freeradius.git] / src / modules / rlm_attr_filter / rlm_attr_filter.c
1 /*
2  * rlm_attr_filter.c  - Filter A/V Pairs received back from proxy reqs
3  *                      before sending reply to the NAS/Server that sent
4  *                      it to us.
5  *
6  * Version:      $Id$
7  *
8  *   This program is is free software; you can redistribute it and/or modify
9  *   it under the terms of the GNU General Public License, version 2 if the
10  *   License as published by the Free Software Foundation.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  *
21  * Copyright (C) 2001 The FreeRADIUS server project
22  * Copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
23  */
24
25 #include        "autoconf.h"
26 #include        "libradius.h"
27
28 #include        <sys/stat.h>
29
30 #include        <stdlib.h>
31 #include        <string.h>
32 #include        <netdb.h>
33 #include        <ctype.h>
34 #include        <fcntl.h>
35 #include        <limits.h>
36
37 #ifdef HAVE_REGEX_H
38 #  include      <regex.h>
39 #endif
40
41 #include        "radiusd.h"
42 #include        "modules.h"
43
44 static const char rcsid[] = "$Id$";
45
46 struct attr_filter_instance {
47         /* autz */
48         char *attrsfile;
49         PAIR_LIST *attrs;
50 };
51
52 static int check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
53                       int comp, int *pa, int *fa) {
54 #ifdef HAVE_REGEX_H
55         regex_t         reg;
56 #endif
57         switch(check_item->operator) {
58
59                 case T_OP_SET:            /* nothing to do for set */
60                     break;
61                 case T_OP_EQ:
62                      default:
63                         radlog(L_ERR, "Invalid operator for item %s: "
64                         "reverting to '=='", check_item->name);
65
66                 case T_OP_CMP_TRUE:       /* comp always == 0 */
67                 case T_OP_CMP_FALSE:      /* comp always == 1 */
68                 case T_OP_CMP_EQ:
69                     if (comp == 0) {
70                         ++*(pa);
71                     } else {
72                         ++*(fa);
73                     }
74                     break;
75
76                 case T_OP_NE:
77                     if (comp != 0) {
78                         ++*(pa);
79                     } else {
80                         ++*(fa);
81                     }
82                     break;
83
84                 case T_OP_LT:
85                     if (comp < 0) {
86                         ++*(pa);
87                     } else {
88                         ++*(fa);
89                     }
90                     break;
91
92                 case T_OP_GT:
93                     if (comp > 0) {
94                         ++*(pa);
95                     } else {
96                         ++*(fa);
97                     }
98                     break;
99
100                 case T_OP_LE:
101                     if (comp <= 0) {
102                         ++*(pa);
103                     } else {
104                         ++*(fa);
105                     }
106                     break;
107
108                 case T_OP_GE:
109                     if (comp >= 0) {
110                         ++*(pa);
111                     } else {
112                         ++*(fa);
113                     }
114                     break;
115 #ifdef HAVE_REGEX_H
116                 case T_OP_REG_EQ:
117                     regcomp(&reg, (char *)check_item->strvalue, 0);
118                     comp = regexec(&reg, (char *)reply_item->strvalue,
119                                       0, NULL, 0);
120                     regfree(&reg);
121                     if (comp == 0) {
122                         ++*(pa);
123                     } else {
124                         ++*(fa);
125                     }
126                     break;
127
128                 case T_OP_REG_NE:
129                     regcomp(&reg, (char *)check_item->strvalue, 0);
130                     comp = regexec(&reg, (char *)reply_item->strvalue,
131                                       0, NULL, 0);
132                     regfree(&reg);
133                     if (comp != 0) {
134                         ++*(pa);
135                     } else {
136                         ++*(fa);
137                     }
138                     break;
139 #endif
140         }
141         return 0;
142 }
143
144 /*
145  *      Copy the specified attribute to the specified list
146  */
147 static int mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
148 {
149   VALUE_PAIR *tmp;
150   tmp = paircreate(item->attribute, item->type);
151   if (!tmp) {
152         radlog(L_ERR|L_CONS, "no memory");
153         return -1;
154   }
155
156   /*
157    *    Copy EVERYTHING.
158    */
159   memcpy(tmp, item, sizeof(*tmp));
160   tmp->next = NULL;
161   pairadd(to, tmp);
162   return 0;
163 }
164
165 /*
166  *     See if a VALUE_PAIR list contains Fall-Through = Yes
167  */
168 static int fallthrough(VALUE_PAIR *vp)
169 {
170         VALUE_PAIR *tmp;
171
172         tmp = pairfind(vp, PW_FALL_THROUGH);
173
174         return tmp ? tmp->lvalue : 0;
175 }
176
177 static CONF_PARSER module_config[] = {
178         { "attrsfile",     PW_TYPE_STRING_PTR,
179           offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
180         { NULL, -1, 0, NULL, NULL }
181 };
182
183 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
184 {
185         int rcode;
186         PAIR_LIST *attrs = NULL;
187         PAIR_LIST *entry;
188         VALUE_PAIR *vp;
189
190         rcode = pairlist_read(filename, &attrs, 1);
191         if (rcode < 0) {
192                 return -1;
193         }
194
195         /*
196          * Walk through the 'attrs' file list.
197          */
198
199         entry = attrs;
200         while (entry) {
201
202                 entry->check = entry->reply;
203                 entry->reply = NULL;
204
205                 for (vp = entry->check; vp != NULL; vp = vp->next) {
206
207                     /*
208                      * If it's NOT a vendor attribute,
209                      * and it's NOT a wire protocol
210                      * and we ignore Fall-Through,
211                      * then bitch about it, giving a good warning message.
212                      */
213                     if (!(vp->attribute & ~0xffff) &&
214                          (vp->attribute > 0xff) &&
215                          (vp->attribute > 1000)) {
216                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
217                                   "\tfound in filter list for realm \"%s\".\n",
218                                   filename, entry->lineno, vp->name,
219                                   entry->name);
220                     }
221                 }
222
223                 entry = entry->next;
224         }
225
226         *pair_list = attrs;
227         return 0;
228 }
229
230 /*
231  *      (Re-)read the "attrs" file into memory.
232  */
233 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
234 {
235         struct attr_filter_instance *inst;
236         int rcode;
237
238         inst = rad_malloc(sizeof *inst);
239         if (!inst) {
240                 return -1;
241         }
242         memset(inst, 0, sizeof(*inst));
243
244         if (cf_section_parse(conf, inst, module_config) < 0) {
245                 free(inst);
246                 return -1;
247         }
248
249         rcode = getattrsfile(inst->attrsfile, &inst->attrs);
250         if (rcode != 0) {
251                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
252                 free(inst->attrsfile);
253                 free(inst);
254                 return -1;
255         }
256         radlog(L_ERR|L_CONS, " rlm_attr_filter: Authorize method will be"\
257                              " deprecated.");
258         *instance = inst;
259         return 0;
260 }
261
262 static int attr_filter_authorize(void *instance, REQUEST *request)
263 {
264         struct attr_filter_instance *inst = instance;
265         VALUE_PAIR      *request_pairs;
266         VALUE_PAIR      **reply_items;
267         VALUE_PAIR      *reply_item;
268         VALUE_PAIR      *reply_tmp = NULL;
269         VALUE_PAIR      *check_item;
270         PAIR_LIST       *pl;
271         int             found = 0;
272         int             compare;
273         int             pass, fail;
274         VALUE_PAIR      *realmpair;
275         REALM           *realm;
276         char            *realmname;
277
278         /*
279          *      It's not a proxy reply, so return NOOP
280          */
281
282         if( request->proxy == NULL ) {
283                 return( RLM_MODULE_NOOP );
284         }
285
286         request_pairs = request->packet->vps;
287         reply_items = &request->reply->vps;
288
289         /*
290          *      Get the realm.  Can't use request->config_items as
291          *      that gets freed by rad_authenticate....  use the one
292          *      set in the original request vps
293          */
294         realmpair = pairfind(request_pairs, PW_REALM);
295         if(!realmpair) {
296                 /*    Can't find a realm, so no filtering of attributes
297                  *    or should we use a DEFAULT entry?
298                  *    For now, just return NOTFOUND. (maybe NOOP?)
299                  */
300                 return RLM_MODULE_NOTFOUND;
301         }
302
303         realmname = (char *) realmpair->strvalue;
304         realm = realm_find(realmname, FALSE);
305
306         /*
307          *      Find the attr_filter profile entry for the realm.
308          */
309         for(pl = inst->attrs; pl; pl = pl->next) {
310
311                 /*
312                  *  If the current entry is NOT a default,
313                  *  AND the realm does NOT match the current entry,
314                  *  then skip to the next entry.
315                  */
316                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
317                      (strcmp(realmname, pl->name) != 0) )  {
318                     continue;
319                 }
320
321                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
322                                                                     pl->lineno);
323                 found = 1;
324
325                 check_item = pl->check;
326
327                 while( check_item != NULL ) {
328
329                     /*
330                      *      If it is a SET operator, add the attribute to
331                      *      the reply list without checking reply_items.
332                      *
333                      */
334
335                     if( check_item->operator == T_OP_SET ) {
336                         if (mypairappend(check_item, &reply_tmp) < 0) {
337                                 return RLM_MODULE_FAIL;
338                         }
339                     }
340                     check_item = check_item->next;
341
342                 }
343
344                 /*
345                  * Iterate through the reply items, comparing each reply item
346                  * to every rule, then moving it to the reply_tmp list
347                  * only if it matches all rules for that attribute.
348                  * IE, Idle-Timeout is moved only if it matches all rules that
349                  * describe an Idle-Timeout.
350                  */
351
352                 for(reply_item = *reply_items;
353                     reply_item != NULL;
354                     reply_item = reply_item->next ) {
355
356                     /* reset the pass,fail vars for each reply item */
357                     pass = fail = 0;
358
359                     /* reset the check_item pointer to beginning of the list */
360                     check_item = pl->check;
361
362                     while( check_item != NULL ) {
363
364                         if(reply_item->attribute == check_item->attribute) {
365
366                             compare = simplepaircmp(request, reply_item,
367                                                     check_item);
368                             check_pair(check_item, reply_item, compare,
369                                        &pass, &fail);
370                         }
371
372                         check_item = check_item->next;
373
374                     }
375
376                     /* only move attribute if it passed all rules */
377                     if (fail == 0 && pass > 0) {
378                         if (mypairappend( reply_item, &reply_tmp) < 0) {
379                                 return RLM_MODULE_FAIL;
380                         }
381                     }
382
383                 }
384
385                 /* If we shouldn't fall through, break */
386                 if(!fallthrough(pl->check))
387                     break;
388         }
389
390         pairfree(&request->reply->vps);
391         request->reply->vps = reply_tmp;
392
393         /*
394          *      See if we succeeded.  If we didn't find the realm,
395          *      then exit from the module.
396          */
397         if (!found)
398                 return RLM_MODULE_OK;
399
400         /*
401          *      Remove server internal parameters.
402          */
403         pairdelete(reply_items, PW_FALL_THROUGH);
404
405         return RLM_MODULE_UPDATED;
406 }
407
408 static int attr_filter_accounting(void *instance, REQUEST *request)
409 {
410         struct attr_filter_instance *inst = instance;
411         VALUE_PAIR      *request_pairs;
412         VALUE_PAIR      *send_item;
413         VALUE_PAIR      *send_tmp = NULL;
414         VALUE_PAIR      *check_item;
415         PAIR_LIST       *pl;
416         int             found = 0;
417         int             compare;
418         int             pass, fail;
419         VALUE_PAIR      *realmpair;
420         REALM           *realm;
421         char            *realmname;
422         /*
423          * Accounting is similar to pre-proxy.
424          * Here we are concerned with what we are going to forward to
425          * the remote server as opposed to concerns with what we will send
426          * to the NAS based on a proxy reply to an auth request.
427          */
428
429         if (request->packet->code != PW_ACCOUNTING_REQUEST) {
430                 return (RLM_MODULE_NOOP);
431         }
432
433         request_pairs = request->packet->vps;
434
435         /* Get the realm from the original request vps. */
436         realmpair = pairfind(request_pairs, PW_REALM);
437
438         if (!realmpair) {
439                 /* If there is no realm...NOOP */
440                 return (RLM_MODULE_NOOP);
441         }
442
443         realmname = (char *) realmpair->strvalue;
444         realm = realm_find (realmname, FALSE);
445
446         /*
447          * Find the attr_filter profile entry for the realm
448          */
449         for (pl = inst->attrs; pl; pl = pl->next) {
450
451                 /*
452                  * If the current entry is NOT a default,
453                  * AND the realm does not match the current entry,
454                  * then skip to the next entry.
455                  */
456                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
457                      (strcasecmp(realmname, pl->name) != 0) ) {
458                     continue;
459                 }
460
461                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
462                                                                     pl->lineno);
463                 found = 1;
464
465                 check_item = pl->check;
466
467                 while (check_item != NULL) {
468
469                     /*
470                      * If it is a SET operator, add the attribute to
471                      * the send list w/out checking.
472                      */
473
474                     if (check_item->operator == T_OP_SET) {
475                         if (mypairappend(check_item, &send_tmp) < 0) {
476                                 return RLM_MODULE_FAIL;
477                         }
478                     }
479                     check_item = check_item->next;
480                 }
481                 /*
482                  * Iterate through the request_pairs (items sent from NAS).
483                  * Compare each pair to every rule for this realm/DEFAULT.
484                  * Move an item to send_tmp if it matches all rules for
485                  * attribute in question.
486                  */
487                 for (send_item = request_pairs; send_item != NULL;
488                      send_item = send_item->next ) {
489
490                     /* reset the pass/fail vars for each packet->vp. */
491                     pass = fail = 0;
492
493                     /* reset the check_item pointer to beginning of the list */
494                     check_item = pl->check;
495
496                     while (check_item != NULL) {
497                         if (send_item->attribute == check_item->attribute) {
498
499                             compare = simplepaircmp(request, send_item,
500                                                     check_item);
501                             check_pair(check_item, send_item, compare,
502                                        &pass, &fail);
503                         }
504
505                         check_item = check_item->next;
506                     }
507                     /* only send if attribute passed all rules */
508                     if (fail == 0 && pass > 0) {
509                         if (mypairappend (send_item, &send_tmp) < 0) {
510                                 return RLM_MODULE_FAIL;
511                         }
512                     }
513                 }
514                 if (!fallthrough(pl->check))
515                     break;
516         }
517         pairfree (&request->packet->vps);
518         request->packet->vps = send_tmp;
519
520         /*
521          * See if we succeeded. If we didn't find the realm,
522          * then exit from the module.
523          */
524         if (!found)
525                 return RLM_MODULE_OK;
526
527         /*
528          * Remove server internal paramters.
529          */
530         pairdelete(&send_tmp, PW_FALL_THROUGH);
531
532         return RLM_MODULE_UPDATED;
533 }
534
535 static int attr_filter_preproxy (void *instance, REQUEST *request)
536 {
537         struct attr_filter_instance *inst = instance;
538         VALUE_PAIR      *request_pairs;
539         VALUE_PAIR      *send_item;
540         VALUE_PAIR      *send_tmp = NULL;
541         VALUE_PAIR      *check_item;
542         PAIR_LIST       *pl;
543         int             found = 0;
544         int             compare;
545         int             pass, fail;
546         VALUE_PAIR      *realmpair;
547         REALM           *realm;
548         char            *realmname;
549
550         /*
551          * Pre-proxy we are
552          * concerned with what we are going to forward to
553          * to the remote server as opposed to we will do with
554          * with the remote servers' repsonse pairs. Consequently,
555          * we deal with modifications to the request->packet->vps;
556          */
557         request_pairs = request->proxy->vps;
558         if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
559                 return (RLM_MODULE_NOOP);
560         }
561         realmpair = pairfind(request_pairs, PW_REALM);
562         if (!realmpair) {
563                 return (RLM_MODULE_NOOP);
564         }
565
566         realmname = (char *)realmpair->strvalue;
567         realm = realm_find(realmname, FALSE);
568
569         for (pl = inst->attrs; pl; pl = pl->next) {
570                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
571                      (strcasecmp(realmname, pl->name) != 0) ) {
572                     continue;
573                 }
574
575                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
576                                                                     pl->lineno);
577                 found = 1;
578
579                 check_item = pl->check;
580
581                 while (check_item != NULL) {
582
583                     /*
584                      * Append all SET operator attributes with no check.
585                      */
586                     if (check_item->operator == T_OP_SET) {
587                         if (mypairappend(check_item, &send_tmp) < 0) {
588                                 return RLM_MODULE_FAIL;
589                         }
590                     }
591                     check_item = check_item->next;
592                 }
593                 /*
594                  * Iterate through the request_pairs (items sent from NAS).
595                  * Compare each pair to every rule for this realm/DEFAULT.
596                  * Move an item to send_tmp if it matches all rules for
597                  * attribute in question.
598                  */
599                 for (send_item = request_pairs;
600                      send_item != NULL;
601                     send_item = send_item->next ) {
602
603                     /* reset the pass/fail vars for each packet->vp. */
604                     pass = fail = 0;
605
606                     /* reset the check_item to the beginning */
607                     check_item = pl->check;
608
609                     /*
610                      * compare each packet->vp to the entire list of
611                      * check_items for this realm.
612                      */
613                     while (check_item != NULL) {
614                         if (send_item->attribute == check_item->attribute) {
615
616                             compare = simplepaircmp(request, send_item,
617                                                     check_item);
618                             check_pair(check_item, send_item, compare,
619                                        &pass, &fail);
620
621                         }
622
623                         check_item = check_item->next;
624                     }
625
626                     /* only send if attribute passed all rules */
627                     if (fail == 0 && pass > 0) {
628                         if (mypairappend (send_item, &send_tmp) < 0) {
629                                 return RLM_MODULE_FAIL;
630                         }
631                     }
632                 }
633                 if (!fallthrough(pl->check))
634                     break;
635         }
636         pairfree (&request->proxy->vps);
637         request->proxy->vps = send_tmp;
638
639         if (!found)
640                 return RLM_MODULE_OK;
641         pairdelete(&send_tmp, PW_FALL_THROUGH);
642         return RLM_MODULE_UPDATED;
643 }
644
645 static int attr_filter_postproxy(void *instance, REQUEST *request)
646 {
647         struct attr_filter_instance *inst = instance;
648         VALUE_PAIR      *request_pairs;
649         VALUE_PAIR      **reply_items;
650         VALUE_PAIR      *reply_item;
651         VALUE_PAIR      *reply_tmp = NULL;
652         VALUE_PAIR      *check_item;
653         PAIR_LIST       *pl;
654         int             found = 0;
655         int             compare;
656         int             pass, fail = 0;
657         VALUE_PAIR      *realmpair;
658         REALM           *realm;
659         char            *realmname;
660         /*
661          *      It's not a proxy reply, so return NOOP
662          */
663
664         if( request->proxy == NULL ) {
665                 return( RLM_MODULE_NOOP );
666         }
667
668         request_pairs = request->packet->vps;
669         reply_items = &request->proxy_reply->vps;
670
671         /*
672          * Get the realm.  Can't use request->config_items as
673          * that gets freed by rad_authenticate....  use the one
674          * set in the original request vps
675          */
676         realmpair = pairfind(request_pairs, PW_REALM);
677         if(!realmpair) {
678                 /*    Can't find a realm, so no filtering of attributes
679                  *    or should we use a DEFAULT entry?
680                  *    For now, just return NOTFOUND. (maybe NOOP?)
681                  */
682                 return RLM_MODULE_NOTFOUND;
683         }
684
685         realmname = (char *) realmpair->strvalue;
686
687         realm = realm_find(realmname, FALSE);
688
689         /*
690          *      Find the attr_filter profile entry for the realm.
691          */
692         for(pl = inst->attrs; pl; pl = pl->next) {
693
694                 /*
695                  *  If the current entry is NOT a default,
696                  *  AND the realm does NOT match the current entry,
697                  *  then skip to the next entry.
698                  */
699                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
700                      (strcmp(realmname, pl->name) != 0) )  {
701                     continue;
702                 }
703
704                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
705                                                                     pl->lineno);
706                 found = 1;
707
708                 check_item = pl->check;
709
710                 while( check_item != NULL ) {
711
712                     /*
713                      *    If it is a SET operator, add the attribute to
714                      *    the reply list without checking reply_items.
715                      */
716
717                     if( check_item->operator == T_OP_SET ) {
718                         if (mypairappend(check_item, &reply_tmp) < 0) {
719                                 return RLM_MODULE_FAIL;
720                         }
721                     }
722                     check_item = check_item->next;
723
724                 }
725
726                 /*
727                  * Iterate through the reply items,
728                  * comparing each reply item to every rule,
729                  * then moving it to the reply_tmp list only if it matches all
730                  * rules for that attribute.
731                  * IE, Idle-Timeout is moved only if it matches
732                  * all rules that describe an Idle-Timeout.
733                  */
734
735                 for( reply_item = *reply_items; reply_item != NULL;
736                      reply_item = reply_item->next ) {
737
738                     /* reset the pass,fail vars for each reply item */
739                     pass = fail = 0;
740
741                     /* reset the check_item pointer to beginning of the list */
742                     check_item = pl->check;
743
744                     while( check_item != NULL ) {
745
746                         if(reply_item->attribute == check_item->attribute) {
747
748                             compare = simplepaircmp(request, reply_item,
749                                                     check_item);
750                             check_pair(check_item, reply_item, compare,
751                                        &pass, &fail);
752                         }
753
754                         check_item = check_item->next;
755
756                     }
757
758                     /* only move attribute if it passed all rules */
759                     if (fail == 0 && pass > 0) {
760                         if (mypairappend( reply_item, &reply_tmp) < 0) {
761                                 return RLM_MODULE_FAIL;
762                         }
763                     }
764
765                 }
766
767                 /* If we shouldn't fall through, break */
768                 if(!fallthrough(pl->check))
769                     break;
770         }
771
772         pairfree(&request->proxy_reply->vps);
773         request->proxy_reply->vps = reply_tmp;
774
775         /*
776          * See if we succeeded.  If we didn't find the realm,
777          * then exit from the module.
778          */
779         if (!found)
780                 return RLM_MODULE_OK;
781
782         /*
783          *      Remove server internal parameters.
784          */
785         pairdelete(reply_items, PW_FALL_THROUGH);
786
787         return RLM_MODULE_UPDATED;
788 }
789
790 /*
791  *      Clean up.
792  */
793 static int attr_filter_detach(void *instance)
794 {
795         struct attr_filter_instance *inst = instance;
796         pairlist_free(&inst->attrs);
797         free(inst->attrsfile);
798         free(inst);
799         return 0;
800 }
801
802
803 /* globally exported name */
804 module_t rlm_attr_filter = {
805         "attr_filter",
806         0,                              /* type: reserved */
807         NULL,                           /* initialization */
808         attr_filter_instantiate,        /* instantiation */
809         {
810                 NULL,                   /* authentication */
811                 attr_filter_authorize,  /* authorization */
812                 NULL,                   /* preaccounting */
813                 attr_filter_accounting, /* accounting */
814                 NULL,                   /* checksimul */
815                 attr_filter_preproxy,   /* pre-proxy */
816                 attr_filter_postproxy,  /* post-proxy */
817                 NULL                    /* post-auth */
818         },
819         attr_filter_detach,             /* detach */
820         NULL                            /* destroy */
821 };
822