import from HEAD
[freeradius.git] / src / lib / filters.c
1 /*
2  * filters.c    Routines to parse Ascend's filter attributes.
3  *
4  * Version:     $Id$
5  *
6  *   This library is free software; you can redistribute it and/or
7  *   modify it under the terms of the GNU Lesser General Public
8  *   License as published by the Free Software Foundation; either
9  *   version 2.1 of the License, or (at your option) any later version.
10  *
11  *   This library is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  *   Lesser General Public License for more details.
15  *
16  *   You should have received a copy of the GNU Lesser General Public
17  *   License along with this library; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2003  The FreeRADIUS server project
21  */
22
23 static const char rcsid[] = "$Id$";
24
25 #include <string.h>
26 #include <ctype.h>
27 #include <stdlib.h>
28
29 #include "libradius.h"
30
31 #ifdef HAVE_NETINET_IN_H
32 #include <netinet/in.h>
33 #endif
34
35 /*
36  * Two types of filters are supported, GENERIC and IP.  The identifiers
37  * are:
38  */
39
40 #define RAD_FILTER_GENERIC      0
41 #define RAD_FILTER_IP           1
42 #define RAD_FILTER_IPX          2
43
44 /*
45  * Generic filters mask and match up to RAD_MAX_FILTER_LEN bytes
46  * starting at some offset.  The length is:
47  */
48 #define RAD_MAX_FILTER_LEN      6
49
50 /*
51  * ASCEND extensions for ABINARY filters
52  */
53
54 #define IPX_NODE_ADDR_LEN               6
55
56 #if ! defined( FALSE )
57 # define FALSE          0
58 # define TRUE           (! FALSE)
59 #endif
60
61 /*
62  *      ascend_ip_filter_t
63  *
64  *      The binary format of an IP filter.  ALL fields are stored in
65  *      network byte order.
66  *
67  *      srcip:          The source IP address.
68  *
69  *      dstip:          The destination IP address.
70  *
71  *      srcmask:        The number of leading one bits in the source address
72  *                      mask.  Specifies the bits of interest.
73  *
74  *      dstmask:        The number of leading one bits in the destination
75  *                      address mask. Specifies the bits of interest.
76  *
77  *      proto:          The IP protocol number
78  *
79  *      established:    A boolean value.  TRUE when we care about the
80  *                      established state of a TCP connection.  FALSE when
81  *                      we dont care.
82  *
83  *      srcport:        TCP or UDP source port number.
84  *
85  *      dstport:        TCP or UDP destination port number.
86  *
87  *      srcPortCmp:     One of the values of the RadFilterComparison
88  *                      enumeration, specifying how to compare the
89  *                      srcport value.
90  *
91  *      dstPortCmp:     One of the values of the RadFilterComparison
92  *                      enumeration, specifying how to compare the
93  *                      dstport value.
94  *
95  *      fill:           Round things out to a int16_t boundary.
96  */
97 typedef struct ascend_ip_filter_t {
98         uint32_t        srcip;
99         uint32_t        dstip;
100         uint8_t         srcmask;
101         uint8_t         dstmask;
102         uint8_t         proto;
103         uint8_t         established;
104         uint16_t        srcport;
105         uint16_t        dstport;
106         uint8_t         srcPortComp;
107         uint8_t         dstPortComp;
108         unsigned char   fill[4];        /* used to be fill[2] */
109 } ascend_ip_filter_t;
110
111
112 /*
113  *      ascend_ipx_net_t
114  *
115  *      net:      IPX Net address
116  *
117  *      node:     IPX Node address
118  *
119  *      socket:      IPX socket address
120  */
121 typedef struct ascend_ipx_net_t {
122         uint32_t        net;
123         uint8_t         node[IPX_NODE_ADDR_LEN];
124         uint16_t        socket;
125 } ascend_ipx_net_t;
126
127 /*
128  *      ascend_ipx_filter_t
129  *
130  *      The binary format of an IPX filter.  ALL fields are stored in
131  *      network byte order.
132  *
133  *      src:            Source net, node, and socket.
134  *
135  *      dst:            Destination net, node, and socket.
136  *
137  *      srcSocComp:     Source socket compare value
138  *
139  *      dstSocComp:     Destination socket compare value
140  */
141 typedef struct ascend_ipx_filter_t {
142         ascend_ipx_net_t src;
143         ascend_ipx_net_t dst;
144         uint8_t         srcSocComp;
145         uint8_t         dstSocComp;
146 } ascend_ipx_filter_t;
147
148
149 /*
150  *      ascend_generic_filter_t
151  *
152  *      The binary format of a GENERIC filter.  ALL fields are stored in
153  *      network byte order.
154  *
155  *      offset:         Number of bytes into packet to start comparison.
156  *
157  *      len:            Number of bytes to mask and compare.  May not
158  *                      exceed RAD_MAX_FILTER_LEN.
159  *
160  *      more:           Boolean.  If non-zero the next filter entry is
161  *                      also to be applied to a packet.
162  *
163  *      mask:           A bit mask specifying the bits to compare.
164  *
165  *      value:          A value to compare against the masked bits at
166  *                      offset in a users packet.
167  *
168  *      compNeq:        Defines type of comarison (Equal or Notequal)
169  *                      default is Equal.
170  *
171  *      fill:           Round things out to a dword boundary
172  */
173 typedef struct ascend_generic_filter_t {
174         uint16_t        offset;
175         uint16_t        len;
176         uint16_t        more;
177         uint8_t         mask[ RAD_MAX_FILTER_LEN ];
178         uint8_t         value[ RAD_MAX_FILTER_LEN ];
179         uint8_t         compNeq;
180         uint8_t         fill[3];        /* used to be fill[1] */
181 } ascend_generic_filter_t;
182
183 /*
184  *      ascend_filter_t
185  *
186  *      A binary filter element.  Contains one of ascend_ip_filter_t,
187  *      ascend_ipx_filter_t, or ascend_generic_filter_t.
188  *
189  *      All fields are stored in network byte order.
190  *
191  *      type:           Either RAD_FILTER_GENERIC or RAD_FILTER_IP.
192  *
193  *      forward:        TRUE if we should forward packets that match this
194  *                      filter, FALSE if we should drop packets that match
195  *                      this filter.
196  *
197  *      direction:      TRUE if this is an input filter, FALSE if this is
198  *                      an output filter.
199  *
200  *      fill:           Round things out to a dword boundary.
201  *
202  *      u:              A union of
203  *                      ip:             An ip filter entry
204  *                      generic:        A generic filter entry
205  */
206 typedef struct ascend_filter_t {
207         uint8_t         type;
208         uint8_t         forward;
209         uint8_t         direction;
210         uint8_t         fill;
211         union {
212                 ascend_ip_filter_t       ip;
213                 ascend_ipx_filter_t      ipx;
214                 ascend_generic_filter_t generic;
215         } u;
216 } ascend_filter_t;
217 #define SIZEOF_RADFILTER 32
218
219 /*
220  * FilterPortType:
221  *
222  * Ascii names of some well known tcp/udp services.
223  * Used for filtering on a port type.
224  *
225  * ??? What the heck is wrong with getservbyname?
226  */
227 static const LRAD_NAME_NUMBER filterPortType[] = {
228         { "ftp-data",   20 },
229         { "ftp",        21 },
230         { "telnet",     23 },
231         { "smtp",       25 },
232         { "nameserver", 42 },
233         { "domain",     53 },
234         { "tftp",       69 },
235         { "gopher",     70 },
236         { "finger",     79 },
237         { "www",        80 },
238         { "kerberos",   88 },
239         { "hostname",   101 },
240         { "nntp",       119 },
241         { "ntp",        123 },
242         { "exec",       512 },
243         { "login",      513 },
244         { "cmd",        514 },
245         { "talk",       517 },
246         {  NULL ,       0},
247 };
248
249 static const LRAD_NAME_NUMBER filterType[] = {
250         { "generic",    RAD_FILTER_GENERIC},
251         { "ip",         RAD_FILTER_IP},
252         { "ipx",        RAD_FILTER_IPX},
253         { NULL,         0},
254 };
255
256 typedef enum {
257     FILTER_GENERIC_TYPE,
258     FILTER_IP_TYPE,
259     FILTER_IN,
260     FILTER_OUT,
261     FILTER_FORWARD,
262     FILTER_DROP,
263     FILTER_GENERIC_OFFSET,
264     FILTER_GENERIC_MASK,
265     FILTER_GENERIC_VALUE,
266     FILTER_GENERIC_COMPNEQ,
267     FILTER_GENERIC_COMPEQ,
268     FILTER_MORE,
269     FILTER_IP_DST,
270     FILTER_IP_SRC,
271     FILTER_IP_PROTO,
272     FILTER_IP_DST_PORT,
273     FILTER_IP_SRC_PORT,
274     FILTER_EST,
275     FILTER_IPX_TYPE,
276     FILTER_IPX_DST_IPXNET,
277     FILTER_IPX_DST_IPXNODE,
278     FILTER_IPX_DST_IPXSOCK,
279     FILTER_IPX_SRC_IPXNET,
280     FILTER_IPX_SRC_IPXNODE,
281     FILTER_IPX_SRC_IPXSOCK
282 } FilterTokens;
283
284
285 static const LRAD_NAME_NUMBER filterKeywords[] = {
286         { "ip",         FILTER_IP_TYPE },
287         { "generic",    FILTER_GENERIC_TYPE },
288         { "in",         FILTER_IN },
289         { "out",        FILTER_OUT },
290         { "forward",    FILTER_FORWARD },
291         { "drop",       FILTER_DROP },
292         { "dstip",      FILTER_IP_DST },
293         { "srcip",      FILTER_IP_SRC },
294         { "dstport",    FILTER_IP_DST_PORT },
295         { "srcport",    FILTER_IP_SRC_PORT },
296         { "est",        FILTER_EST },
297         { "more",       FILTER_MORE },
298         { "!=",         FILTER_GENERIC_COMPNEQ },
299         { "==",         FILTER_GENERIC_COMPEQ  },
300         { "ipx",        FILTER_IPX_TYPE  },
301         { "dstipxnet",  FILTER_IPX_DST_IPXNET  },
302         { "dstipxnode", FILTER_IPX_DST_IPXNODE  },
303         { "dstipxsock", FILTER_IPX_DST_IPXSOCK  },
304         { "srcipxnet",  FILTER_IPX_SRC_IPXNET  },
305         { "srcipxnode", FILTER_IPX_SRC_IPXNODE  },
306         { "srcipxsock", FILTER_IPX_SRC_IPXSOCK  },
307         {  NULL ,       -1},
308 };
309
310 /*
311  * FilterProtoName:
312  *
313  * Ascii name of protocols used for filtering.
314  *
315  *  ??? What the heck is wrong with getprotobyname?
316  */
317 static const LRAD_NAME_NUMBER filterProtoName[] = {
318         { "tcp",  6 },
319         { "udp",  17 },
320         { "ospf", 89 },
321         { "icmp", 1 },
322         { "0",    0 },
323         {  NULL , -1 },
324 };
325
326
327 /*
328  * RadFilterComparison:
329  *
330  * An enumerated values for the IP filter port comparisons.
331  */
332 typedef enum {
333         RAD_NO_COMPARE = 0,
334         RAD_COMPARE_LESS,
335         RAD_COMPARE_EQUAL,
336         RAD_COMPARE_GREATER,
337         RAD_COMPARE_NOT_EQUAL
338 } RadFilterComparison;
339
340 static const LRAD_NAME_NUMBER filterCompare[] = {
341         { "<",  RAD_COMPARE_LESS },
342         { "=",  RAD_COMPARE_EQUAL },
343         { ">",  RAD_COMPARE_GREATER },
344         { "!=", RAD_COMPARE_NOT_EQUAL },
345         { NULL, 0 },
346 };
347
348
349 /*
350  *      String split routine.  Splits an input string IN PLACE
351  *      into pieces, based on spaces.
352  */
353 static int str2argv(char *str, char **argv, int max_argc)
354 {
355         int argc = 0;
356
357         while (*str) {
358                 if (argc >= max_argc) return argc;
359
360                 while (*str == ' ') *(str++) = '\0';
361
362                 if (!*str) return argc;
363
364                 argv[argc] = str;
365                 argc++;
366
367                 while (*str && (*str != ' ')) str++;
368         }
369
370         return argc;
371 }
372
373
374 /*
375  *      hex2bin converts hexadecimal strings into binary
376  *
377  *      Hmm... there are a number of such functions in the source.
378  *      maybe we want to make a library function?
379  */
380 static int hex2bin(const char *str, uint8_t *bin, size_t length)
381 {
382         unsigned int            len;
383         const           char *letters = "0123456789ABCDEFabcdef";
384
385         /*
386          *      Must be byte aligned, not nibble aligned.
387          */
388         len = strlen(str);
389         if ((len & 0x01) != 0) return -1;
390
391         /*
392          *      Input string is too long to fit.  Don't even bother
393          *      trying.
394          */
395         if ((len / 2) > length) return -1;
396
397         /*
398          *      Input string contains non-hex characters, die.
399          */
400         if (strspn(str, letters) != len) return -1;
401
402         len = 0;
403         while (*str) {
404                 char    *c1, *c2;
405
406                 c1 = memchr(letters, toupper((int) *(str++)), 16);
407                 c2 = memchr(letters, toupper((int) *(str++)), 16);
408
409                 *(bin++) = ((c1-letters)<<4) + (c2-letters);
410                 len++;
411         }
412
413         return len;
414 }
415
416
417 /*
418  *      ascend_parse_ipx_net
419  *
420  *      srcipxnet nnnn srcipxnode mmmmm [srcipxsoc cmd value ]
421  */
422 static int ascend_parse_ipx_net(int argc, char **argv,
423                                 ascend_ipx_net_t *net, uint8_t *comp)
424 {
425         int             token;
426         const char      *p;
427
428         if (argc < 3) return -1;
429
430         /*
431          *      Parse the net, which is a hex number.
432          */
433         net->net = htonl(strtol(argv[0], NULL, 16));
434
435         /*
436          *      Parse the node.
437          */
438         token = lrad_str2int(filterKeywords, argv[1], -1);
439         switch (token) {
440         case FILTER_IPX_SRC_IPXNODE:
441         case FILTER_IPX_DST_IPXNODE:
442                 break;
443
444         default:
445                 return -1;
446         }
447
448         /*
449          *      Can have a leading "0x" or "0X"
450          */
451         p = argv[2];
452         if ((memcmp(p, "0X", 2) == 0) ||
453             (memcmp(p, "0x", 2) == 0)) p += 2;
454
455         /*
456          *      Node must be 6 octets long.
457          */
458         token = hex2bin(p, net->node, IPX_NODE_ADDR_LEN);
459         if (token != IPX_NODE_ADDR_LEN) return -1;
460
461         /*
462          *      Nothing more, die.
463          */
464         if (argc == 3) return 3;
465
466         /*
467          *      Can't be too little or too much.
468          */
469         if (argc != 6) return -1;
470
471         /*
472          *      Parse the socket.
473          */
474         token = lrad_str2int(filterKeywords, argv[3], -1);
475         switch (token) {
476         case FILTER_IPX_SRC_IPXSOCK:
477         case FILTER_IPX_DST_IPXSOCK:
478                 break;
479
480         default:
481                 return -1;
482         }
483
484         /*
485          *      Parse the command "<", ">", "=" or "!="
486          */
487         token = lrad_str2int(filterCompare, argv[4], -1);
488         switch (token) {
489         case RAD_COMPARE_LESS:
490         case RAD_COMPARE_EQUAL:
491         case RAD_COMPARE_GREATER:
492         case RAD_COMPARE_NOT_EQUAL:
493                 *comp = token;
494                 break;
495
496         default:
497                 return -1;
498         }
499
500         /*
501          *      Parse the value.
502          */
503         token = strtoul(argv[5], NULL, 16);
504         if (token > 65535) return -1;
505
506         net->socket = token;
507         net->socket = htons(net->socket);
508
509
510         /*
511          *      Everything's OK, we parsed 6 entries.
512          */
513         return 6;
514 }
515
516 /*
517  *      ascend_parse_ipx_filter
518  *
519  *      This routine parses an IPX filter string from a string.
520  *      The format of the string is:
521  *
522  *      [ srcipxnet nnnn srcipxnode mmmmm [srcipxsoc cmd value ]]
523  *      [ dstipxnet nnnn dstipxnode mmmmm [dstipxsoc cmd value ]]
524  *
525  * Fields in [...] are optional.
526  *      where:
527  *
528  *  srcipxnet:      Keyword for source IPX address.
529  *                  nnnn = IPX Node address.
530  *
531  *  srcipxnode:     Keyword for source IPX Node address.
532  *                  mmmmm = IPX Node Address, could be FFFFFF.
533  *                  A vlid ipx node number should accompany ipx net number.
534  *
535  *      srcipxsoc:      Keyword for source IPX socket address.
536  *
537  *      cmd:            One of ">" or "<" or "=" or "!=".
538  *
539  *      value:          Socket value to be compared against, in hex.
540  *
541  *      dstipxnet:      Keyword for destination IPX address.
542  *                      nnnn = IPX Node address.
543  *
544  *      dstipxnode:     Keyword for destination IPX Node address.
545  *              mmmmm = IPX Node Address, could be FFFFFF.
546  *                     A valid ipx node number should accompany ipx net number.
547  *
548  *      dstipxsoc:      Keyword for destination IPX socket address.
549  *
550  *      cmd:            One of ">" or "<" or "=" or "!=".
551  *
552  *      value:          Socket value to be compared against, in hex.
553  */
554 static int ascend_parse_ipx(int argc, char **argv, ascend_ipx_filter_t *filter)
555 {
556         int rcode;
557         int token;
558         int flags = 0;
559
560         /*
561          *      We may have nothing, in which case we simply return.
562          */
563         if (argc == 0) return 0;
564
565         /*
566          *      Must have "net N node M"
567          */
568         if (argc < 4) return -1;
569
570         while ((argc > 0) && (flags != 0x03)) {
571                 token = lrad_str2int(filterKeywords, argv[0], -1);
572                 switch (token) {
573                 case FILTER_IPX_SRC_IPXNET:
574                         if (flags & 0x01) return -1;
575                         rcode = ascend_parse_ipx_net(argc - 1, argv + 1,
576                                                      &(filter->src),
577                                                      &(filter->srcSocComp));
578                         if (rcode < 0) return -1;
579                         argc -= (rcode + 1);
580                         argv += rcode + 1;
581                         flags |= 0x01;
582                         break;
583
584                 case FILTER_IPX_DST_IPXNET:
585                         if (flags & 0x02) return -1;
586                         rcode = ascend_parse_ipx_net(argc - 1, argv + 1,
587                                                      &(filter->dst),
588                                                      &(filter->dstSocComp));
589                         if (rcode < 0) return -1;
590                         argc -= (rcode + 1);
591                         argv += rcode + 1;
592                         flags |= 0x02;
593                         break;
594
595                 default:
596                         librad_log("Unknown string \"%s\" in IPX data filter",
597                                    argv[0]);
598                         return -1;
599                 }
600         }
601
602         /*
603          *      Arguments left over: die.
604          */
605         if (argc != 0) return -1;
606
607         /*
608          *      Everything's OK.
609          */
610         return 0;
611 }
612
613
614 /*
615  *      Parse an IP address and optionally a netmask, to a uint32_t.
616  *
617  *      ipaddr should already be initialized to zero.
618  *      ipaddr is in network byte order.
619  *
620  *      Returns -1 on error, or the number of bits in the netmask, otherwise.
621  */
622 static int ascend_parse_ipaddr(uint32_t *ipaddr, char *str)
623 {
624         int             count = 0;
625         int             ip[4];
626         int             masklen;
627         uint32_t        netmask = 0;
628
629         /*
630          *      Look for IP's.
631          */
632         count = 0;
633         while (*str && (count < 4) && (netmask == 0)) {
634         next:
635                 ip[count] = 0;
636
637                 while (*str) {
638                         switch (*str) {
639                         case '0': case '1': case '2': case '3':
640                         case '4': case '5': case '6': case '7':
641                         case '8': case '9':
642                                 ip[count] *= 10;
643                                 ip[count] += (*str) - '0';
644                                 str++;
645                                 break;
646
647
648                         case '.': /* dot between IP numbers. */
649                                 str++;
650                                 if (ip[count] > 255) return -1;
651
652                                 /*
653                                  *      24, 16, 8, 0, done.
654                                  */
655                                 *ipaddr |= (ip[count] << (8 * (3 - count)));
656                                 count++;
657                                 goto next;
658
659                         case '/': /* netmask  */
660                                 str++;
661                                 masklen = atoi(str);
662                                 if ((masklen < 0) || (masklen > 32)) return -1;
663                                 str += strspn(str, "0123456789");
664                                 netmask = masklen;
665                                 goto finalize;
666                                 break;
667
668                         default:
669                                 librad_log("Invalid character in IP address");
670                                 return -1;
671                         }
672                 } /* loop over one character */
673         } /* loop until the count hits 4 */
674
675         if (count == 3) {
676         finalize:
677                 /*
678                  *      Do the last one, too.
679                  */
680                 if (ip[count] > 255) return -1;
681
682                 /*
683                  *      24, 16, 8, 0, done.
684                  */
685                 *ipaddr |= (ip[count] << (8 * (3 - count)));
686         }
687
688         /*
689          *      We've hit the end of the IP address, and there's something
690          *      else left over: die.
691          */
692         if (*str) return -1;
693
694         /*
695          *      Set the default netmask.
696          */
697         if (!netmask) {
698                 if (!*ipaddr) {
699                         netmask = 0;
700                 } else if ((*ipaddr & 0x80000000) == 0) {
701                         netmask = 8;
702                 } else if ((*ipaddr & 0xc0000000) == 0x80000000) {
703                         netmask = 16;
704                 } else if ((*ipaddr & 0xe0000000) == 0xc0000000) {
705                         netmask = 24;
706                 } else {
707                         netmask = 32;
708                 }
709         }
710
711         *ipaddr = htonl(*ipaddr);
712         return netmask;
713 }
714
715 /*
716  *      ascend_parse_port:  Parse a comparator and port.
717  *
718  *      Returns -1 on error, or the comparator.
719  */
720 static int ascend_parse_port(uint16_t *port, char *compare, char *str)
721 {
722         int rcode, token = -1;
723
724         /*
725          *      There MUST be a comparison string.
726          */
727         rcode = lrad_str2int(filterCompare, compare, -1);
728         if (rcode < 0) return rcode;
729
730         if (strspn(str, "0123456789") == strlen(str)) {
731                 token = atoi(str);
732         } else {
733                 token = lrad_str2int(filterPortType, str, -1);
734         }
735
736         if ((token < 0) || (token > 65535)) return -1;
737
738         *port = token;
739         *port = htons(*port);
740
741         return rcode;
742 }
743
744
745 /*
746  *      ascend_parse_ip:
747  *
748  *      This routine parses an IP filter string from a RADIUS
749  *      reply. The format of the string is:
750  *
751  *      ip dir action [ dstip n.n.n.n/nn ] [ srcip n.n.n.n/nn ]
752  *          [ proto [ dstport cmp value ] [ srcport cmd value ] [ est ] ]
753  *
754  *      Fields in [...] are optional.
755  *
756  *      dstip:          Keyword for destination IP address.
757  *                      n.n.n.n = IP address. /nn - netmask.
758  *
759  *      srcip:          Keyword for source IP address.
760  *                      n.n.n.n = IP address. /nn - netmask.
761  *
762  *      proto:          Optional protocol field. Either a name or
763  *                      number. Known names are in FilterProtoName[].
764  *
765  *      dstport:        Keyword for destination port. Only valid with tcp
766  *                      or udp. 'cmp' are in FilterPortType[]. 'value' can be
767  *                      a name or number.
768  *
769  *      srcport:        Keyword for source port. Only valid with tcp
770  *                      or udp. 'cmp' are in FilterPortType[]. 'value' can be
771  *                      a name or number.
772  *
773  *      est:            Keyword for TCP established. Valid only for tcp.
774  *
775  */
776 static int ascend_parse_ip(int argc, char **argv, ascend_ip_filter_t *filter)
777 {
778         int rcode;
779         int token;
780         int flags;
781
782         /*
783          *      We may have nothing, in which case we simply return.
784          */
785         if (argc == 0) return 0;
786
787         /*
788          *      There may, or may not, be src & dst IP's in the string.
789          */
790         flags = 0;
791         while ((argc > 0) && (flags != 7)) {
792                 token = lrad_str2int(filterKeywords, argv[0], -1);
793                 switch (token) {
794                 case FILTER_IP_SRC:
795                         if (flags & 0x01) return -1;
796                         if (argc < 2) return -1;
797
798                         rcode = ascend_parse_ipaddr(&filter->srcip, argv[1]);
799                         if (rcode < 0) return rcode;
800
801                         filter->srcmask = rcode;
802                         flags |= 0x01;
803                         argv += 2;
804                         argc -= 2;
805                         break;
806
807                 case FILTER_IP_DST:
808                         if (flags & 0x02) return -1;
809                         if (argc < 2) return -1;
810
811                         rcode = ascend_parse_ipaddr(&filter->dstip, argv[1]);
812                         if (rcode < 0) return rcode;
813
814                         filter->dstmask = rcode;
815                         flags |= 0x02;
816                         argv += 2;
817                         argc -= 2;
818                         break;
819
820                         /*
821                          *      Should be protocol, ASCII or otherwise.
822                          */
823                 default:
824                         if (strspn(argv[0], "0123456789") == strlen(argv[0])) {
825                                 token = atoi(argv[0]);
826                         } else {
827                                 token = lrad_str2int(filterProtoName, argv[0], -1);
828                                 if (token == -1) {
829                                         librad_log("Unknown IP protocol \"%s\" in IP data filter",
830                                                    argv[0]);
831                                         return -1;
832                                 }
833                         }
834                         filter->proto = token;
835                         flags = 0x07; /* MUST have parsed everything. */
836
837                         argv++;
838                         argc--;
839                         break;
840                 }
841         } /* looking for src/dst IP, and proto */
842
843         /*
844          *      Done looking for everything, return.
845          */
846         if (argc == 0) return 0;
847
848         /*
849          *      There may, or may not, be src & dst ports in the string.
850          */
851         flags = 0;
852         while ((argc > 0) && (flags != 7)) {
853                 token = lrad_str2int(filterKeywords, argv[0], -1);
854                 switch (token) {
855                 case FILTER_IP_SRC_PORT:
856                         if (flags & 0x01) return -1;
857                         if (argc < 3) return -1;
858
859                         rcode = ascend_parse_port(&filter->srcport,
860                                                   argv[1], argv[2]);
861                         if (rcode < 0) return rcode;
862                         filter->srcPortComp = rcode;
863
864                         flags |= 0x01;
865                         argv += 3;
866                         argc -= 3;
867                         break;
868
869                 case FILTER_IP_DST_PORT:
870                         if (flags & 0x02) return -1;
871                         if (argc < 3) return -1;
872
873                         rcode = ascend_parse_port(&filter->dstport,
874                                                   argv[1], argv[2]);
875                         if (rcode < 0) return rcode;
876                         filter->dstPortComp = rcode;
877
878                         flags |= 0x02;
879                         argv += 3;
880                         argc -= 3;
881                         break;
882
883                         /*
884                          *      Look for established connections.
885                          */
886                 case FILTER_EST:
887                         filter->established = 1;
888                         argv++;
889                         argc--;
890                         flags = 0x07;
891                         break;
892
893                         /*
894                          *      Unknown thingy.
895                          */
896                 default:
897                         librad_log("Unknown string \"%s\" in IP data filter",
898                                    argv[0]);
899                         return -1;
900                         break;
901                 }
902         } /* looking for src/dst port */
903
904         /*
905          *      We should have parsed everything by now.
906          */
907         if (argc != 0) {
908                 librad_log("Unknown extra string \"%s\" in IP data filter",
909                            argv[0]);
910                 return -1;
911         }
912
913         return 0;
914 }
915
916
917 /*
918  *      ascend_parse_generic
919  *
920  *      This routine parses a Generic filter string from a RADIUS
921  *      reply. The format of the string is:
922  *
923  *      generic dir action offset mask value [== or != ] [more]
924  *
925  *      Fields in [...] are optional.
926  *
927  *      offset:         A Number. Specifies an offset into a frame
928  *                      to start comparing.
929  *
930  *      mask:           A hexadecimal mask of bits to compare.
931  *
932  *      value:          A value to compare with the masked data.
933  *
934  *      compNeq:        Defines type of comparison. ( "==" or "!=")
935  *                      Default is "==".
936  *
937  *      more:           Optional keyword MORE, to represent the attachment
938  *                      to the next entry.
939  */
940 static int ascend_parse_generic(int argc, char **argv,
941                                 ascend_generic_filter_t *filter)
942 {
943         int rcode;
944         int token;
945         int flags;
946
947         /*
948          *      We may have nothing, in which case we simply return.
949          */
950         if (argc == 0) return 0;
951
952         /*
953          *      We need at least "offset mask value"
954          */
955         if (argc < 3) return -1;
956
957         /*
958          *      No more than optional comparison and "more"
959          */
960         if (argc > 5) return -1;
961
962         /*
963          *      Offset is a uint16_t number.
964          */
965         if (strspn(argv[0], "0123456789") != strlen(argv[0])) return -1;
966
967         rcode = atoi(argv[0]);
968         if (rcode > 65535) return -1;
969
970         filter->offset = rcode;
971         filter->offset = htons(filter->offset);
972
973         rcode = hex2bin(argv[1], filter->mask, sizeof(filter->mask));
974         if (rcode < 0) return -1;
975
976         token = hex2bin(argv[2], filter->value, sizeof(filter->value));
977         if (token < 0) return -1;
978
979         /*
980          *      The mask and value MUST be the same length.
981          */
982         if (rcode != token) return -1;
983
984         filter->len = rcode;
985         filter->len = htons(filter->len);
986
987         /*
988          *      Nothing more.  Exit.
989          */
990         if (argc == 3) return 0;
991
992         argc -= 3;
993         argv += 3;
994         flags = 0;
995
996         while (argc >= 1) {
997                 token = lrad_str2int(filterKeywords, argv[0], -1);
998                 switch (token) {
999                 case FILTER_GENERIC_COMPNEQ:
1000                         if (flags & 0x01) return -1;
1001                         filter->compNeq = TRUE;
1002                         flags |= 0x01;
1003                         break;
1004                 case FILTER_GENERIC_COMPEQ:
1005                         if (flags & 0x01) return -1;
1006                         filter->compNeq = FALSE;
1007                         flags |= 0x01;
1008                         break;
1009
1010                 case FILTER_MORE:
1011                         if (flags & 0x02) return -1;
1012                         filter->more = htons( 1 );
1013                         flags |= 0x02;
1014                         break;
1015
1016                 default:
1017                         librad_log("Invalid string \"%s\" in generic data filter",
1018                                    argv[0]);
1019                         return -1;
1020                 }
1021
1022                 argc--;
1023                 argv++;
1024         }
1025
1026         return 0;
1027 }
1028
1029
1030 /*
1031  * filterBinary:
1032  *
1033  * This routine will call routines to parse entries from an ASCII format
1034  * to a binary format recognized by the Ascend boxes.
1035  *
1036  *      pair:                   Pointer to value_pair to place return.
1037  *
1038  *      valstr:                 The string to parse
1039  *
1040  *      return:                 -1 for error or 0.
1041  */
1042 int
1043 ascend_parse_filter(VALUE_PAIR *pair)
1044 {
1045         int             token, type;
1046         int             rcode;
1047         int             argc;
1048         char            *argv[32];
1049         ascend_filter_t filter;
1050
1051         rcode = -1;
1052         
1053         /*
1054          *      Rather than printing specific error messages, we create
1055          *      a general one here, which won't be used if the function
1056          *      returns OK.
1057          */
1058         librad_log("Text is not in proper format");
1059
1060         /*
1061          *      Tokenize the input string in the VP.
1062          *
1063          *      Once the filter is *completely* parsed, then we will
1064          *      over-write it with the final binary filter.
1065          */
1066         argc = str2argv(pair->strvalue, argv, 32);
1067         if (argc < 3) return -1;
1068
1069         /*
1070          *      Decide which filter type it is: ip, ipx, or generic
1071          */
1072         type = lrad_str2int(filterType, argv[0], -1);
1073         memset(&filter, 0, sizeof(filter));
1074
1075         /*
1076          *      Validate the filter type.
1077          */
1078         switch (type) {
1079         case RAD_FILTER_GENERIC:
1080         case RAD_FILTER_IP:
1081         case RAD_FILTER_IPX:
1082                 filter.type = type;
1083                 break;
1084
1085         default:
1086                 librad_log("Unknown Ascend filter type \"%s\"", argv[0]);
1087                 return -1;
1088                 break;
1089         }
1090
1091         /*
1092          *      Parse direction
1093          */
1094         token = lrad_str2int(filterKeywords, argv[1], -1);
1095         switch (token) {
1096         case FILTER_IN:
1097                 filter.direction = 1;
1098                 break;
1099
1100         case FILTER_OUT:
1101                 filter.direction = 0;
1102                 break;
1103
1104         default:
1105                 librad_log("Unknown Ascend filter direction \"%s\"", argv[1]);
1106                 return -1;
1107                 break;
1108         }
1109
1110         /*
1111          *      Parse action
1112          */
1113         token = lrad_str2int(filterKeywords, argv[2], -1);
1114         switch (token) {
1115         case FILTER_FORWARD:
1116                 filter.forward = 1;
1117                 break;
1118
1119         case FILTER_DROP:
1120                 filter.forward = 0;
1121                 break;
1122
1123         default:
1124                 librad_log("Unknown Ascend filter action \"%s\"", argv[2]);
1125                 return -1;
1126                 break;
1127         }
1128
1129
1130         switch (type) {
1131         case RAD_FILTER_GENERIC:
1132                 rcode = ascend_parse_generic(argc - 3, &argv[3],
1133                                           &filter.u.generic);
1134                 break;
1135
1136         case RAD_FILTER_IP:
1137                 rcode = ascend_parse_ip(argc - 3, &argv[3], &filter.u.ip);
1138                 break;
1139
1140         case RAD_FILTER_IPX:
1141                 rcode = ascend_parse_ipx(argc - 3, &argv[3], &filter.u.ipx);
1142                 break;
1143
1144         default:                /* should never reach here. */
1145                 break;
1146         }
1147
1148         /*
1149          *      Touch the VP only if everything was OK.
1150          */
1151         if (rcode == 0) {
1152                 pair->length = SIZEOF_RADFILTER;
1153                 memcpy(pair->strvalue, &filter, sizeof(filter));
1154         }
1155
1156         return rcode;
1157
1158 #if 0
1159     /*
1160      * if 'more' is set then this new entry must exist, be a
1161      * FILTER_GENERIC_TYPE, direction and disposition must match for
1162      * the previous 'more' to be valid. If any should fail then TURN OFF
1163      * previous 'more'
1164      */
1165     if( prevRadPair ) {
1166         filt = ( RadFilter * )prevRadPair->strvalue;
1167         if(( tok != FILTER_GENERIC_TYPE ) || (rc == -1 ) ||
1168            ( prevRadPair->attribute != pair->attribute ) ||
1169            ( filt->indirection != radFil.indirection ) ||
1170            ( filt->forward != radFil.forward ) ) {
1171             gen = &filt->u.generic;
1172             gen->more = FALSE;
1173             librad_log("filterBinary:  'more' for previous entry doesn't match: %s.\n",
1174                      valstr);
1175         }
1176     }
1177     prevRadPair = NULL;
1178     if( rc != -1 && tok == FILTER_GENERIC_TYPE ) {
1179         if( radFil.u.generic.more ) {
1180             prevRadPair = pair;
1181         }
1182     }
1183
1184     if( rc != -1 ) {
1185         memcpy( pair->strvalue, (char *) &radFil, pair->length );
1186     }
1187     return(rc);
1188
1189 #endif
1190 }
1191
1192 /*
1193  *      Print an Ascend binary filter attribute to a string,
1194  *      Grrr... Ascend makes the server do this work, instead
1195  *      of doing it on the NAS.
1196  *
1197  *      Note we don't bother checking 'len' after the snprintf's.
1198  *      This function should ONLY be called with a large (~1k) buffer.
1199  */
1200 void print_abinary(VALUE_PAIR *vp, u_char *buffer, int len)
1201 {
1202   int                   i;
1203   char                  *p;
1204   ascend_filter_t       filter;
1205
1206   static const char *action[] = {"drop", "forward"};
1207   static const char *direction[] = {"out", "in"};
1208
1209   p = (char *)buffer;
1210
1211   /*
1212    *  Just for paranoia: wrong size filters get printed as octets
1213    */
1214   if (vp->length > SIZEOF_RADFILTER) {
1215     strcpy(p, "0x");
1216     p += 2;
1217     len -= 2;
1218     for (i = 0; i < vp->length; i++) {
1219       snprintf(p, len, "%02x", vp->strvalue[i]);
1220       p += 2;
1221       len -= 2;
1222     }
1223     return;
1224   }
1225
1226   memcpy(&filter, vp->strvalue, SIZEOF_RADFILTER); /* alignment issues */
1227   *(p++) = '"';
1228   len -= 3;                     /* account for leading & trailing quotes */
1229
1230   i = snprintf(p, len, "%s %s %s",
1231                lrad_int2str(filterType, filter.type, "??"),
1232                direction[filter.direction & 0x01],
1233                action[filter.forward & 0x01]);
1234
1235   p += i;
1236   len -= i;
1237
1238   /*
1239    *    Handle IP filters
1240    */
1241   if (filter.type == RAD_FILTER_IP) {
1242
1243     if (filter.u.ip.srcip) {
1244       i = snprintf(p, len, " srcip %d.%d.%d.%d/%d",
1245                    ((u_char *) &filter.u.ip.srcip)[0],
1246                    ((u_char *) &filter.u.ip.srcip)[1],
1247                    ((u_char *) &filter.u.ip.srcip)[2],
1248                    ((u_char *) &filter.u.ip.srcip)[3],
1249                    filter.u.ip.srcmask);
1250       p += i;
1251       len -= i;
1252     }
1253
1254     if (filter.u.ip.dstip) {
1255       i = snprintf(p, len, " dstip %d.%d.%d.%d/%d",
1256                    ((u_char *) &filter.u.ip.dstip)[0],
1257                    ((u_char *) &filter.u.ip.dstip)[1],
1258                    ((u_char *) &filter.u.ip.dstip)[2],
1259                    ((u_char *) &filter.u.ip.dstip)[3],
1260                    filter.u.ip.dstmask);
1261       p += i;
1262       len -= i;
1263     }
1264
1265     i =  snprintf(p, len, " %s",
1266                   lrad_int2str(filterProtoName, filter.u.ip.proto, "??"));
1267     p += i;
1268     len -= i;
1269
1270     if (filter.u.ip.srcPortComp > RAD_NO_COMPARE) {
1271       i = snprintf(p, len, " srcport %s %d",
1272                    lrad_int2str(filterCompare, filter.u.ip.srcPortComp, "??"),
1273                    ntohs(filter.u.ip.srcport));
1274       p += i;
1275       len -= i;
1276     }
1277
1278     if (filter.u.ip.dstPortComp > RAD_NO_COMPARE) {
1279       i = snprintf(p, len, " dstport %s %d",
1280                    lrad_int2str(filterCompare, filter.u.ip.dstPortComp, "??"),
1281                    ntohs(filter.u.ip.dstport));
1282       p += i;
1283       len -= i;
1284     }
1285
1286     if (filter.u.ip.established) {
1287       i = snprintf(p, len, " est");
1288       p += i;
1289       len -= i;
1290     }
1291
1292     /*
1293      *  Handle IPX filters
1294      */
1295   } else if (filter.type == RAD_FILTER_IPX) {
1296     /* print for source */
1297     if (filter.u.ipx.src.net) {
1298       i = snprintf(p, len, " srcipxnet 0x%04x srcipxnode 0x%02x%02x%02x%02x%02x%02x",
1299                   (unsigned int)ntohl(filter.u.ipx.src.net),
1300                   filter.u.ipx.src.node[0], filter.u.ipx.src.node[1],
1301                   filter.u.ipx.src.node[2], filter.u.ipx.src.node[3],
1302                   filter.u.ipx.src.node[4], filter.u.ipx.src.node[5]);
1303       p += i;
1304       len -= i;
1305
1306       if (filter.u.ipx.srcSocComp > RAD_NO_COMPARE) {
1307         i = snprintf(p, len, " srcipxsock %s 0x%04x",
1308                      lrad_int2str(filterCompare, filter.u.ipx.srcSocComp, "??"),
1309                      ntohs(filter.u.ipx.src.socket));
1310         p += i;
1311         len -= i;
1312       }
1313     }
1314
1315     /* same for destination */
1316     if (filter.u.ipx.dst.net) {
1317       i = snprintf(p, len, " dstipxnet 0x%04x dstipxnode 0x%02x%02x%02x%02x%02x%02x",
1318                   (unsigned int)ntohl(filter.u.ipx.dst.net),
1319                   filter.u.ipx.dst.node[0], filter.u.ipx.dst.node[1],
1320                   filter.u.ipx.dst.node[2], filter.u.ipx.dst.node[3],
1321                   filter.u.ipx.dst.node[4], filter.u.ipx.dst.node[5]);
1322       p += i;
1323       len -= i;
1324
1325       if (filter.u.ipx.dstSocComp > RAD_NO_COMPARE) {
1326         i = snprintf(p, len, " dstipxsock %s 0x%04x",
1327                      lrad_int2str(filterCompare, filter.u.ipx.dstSocComp, "??"),
1328                      ntohs(filter.u.ipx.dst.socket));
1329         p += i;
1330         len -= i;
1331       }
1332     }
1333
1334
1335   } else if (filter.type == RAD_FILTER_GENERIC) {
1336     int count;
1337
1338     i = snprintf(p, len, " %u ", (unsigned int) ntohs(filter.u.generic.offset));
1339     p += i;
1340     i -= len;
1341
1342     /* show the mask */
1343     for (count = 0; count < ntohs(filter.u.generic.len); count++) {
1344       i = snprintf(p, len, "%02x", filter.u.generic.mask[count]);
1345       p += i;
1346       len -= i;
1347     }
1348
1349     strcpy(p, " ");
1350     p++;
1351     len--;
1352
1353     /* show the value */
1354     for (count = 0; count < ntohs(filter.u.generic.len); count++) {
1355       i = snprintf(p, len, "%02x", filter.u.generic.value[count]);
1356       p += i;
1357       len -= i;
1358     }
1359
1360     i = snprintf(p, len, " %s", (filter.u.generic.compNeq) ? "!=" : "==");
1361     p += i;
1362     len -= i;
1363
1364     if (filter.u.generic.more != 0) {
1365       i = snprintf(p, len, " more");
1366       p += i;
1367       len -= i;
1368     }
1369   }
1370
1371   *(p++) = '"';
1372   *p = '\0';
1373 }