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