Allow comparisons on IPv6 stuff, too
[freeradius.git] / src / main / valuepair.c
1 /*
2  * valuepair.c  Valuepair functions that are radiusd-specific
3  *              and as such do not belong in the library.
4  *
5  * Version:     $Id$
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
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 2000  The FreeRADIUS server project
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25 static const char rcsid[] = "$Id$";
26
27 #include "autoconf.h"
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #ifdef HAVE_NETINET_IN_H
34 #       include <netinet/in.h>
35 #endif
36
37 #ifdef HAVE_REGEX_H
38 #       include <regex.h>
39
40 /*
41  *  For POSIX Regular expressions.
42  *  (0) Means no extended regular expressions.
43  *  REG_EXTENDED means use extended regular expressions.
44  */
45 #ifndef REG_EXTENDED
46 #define REG_EXTENDED (0)
47 #endif
48
49 #ifndef REG_NOSUB
50 #define REG_NOSUB (0)
51 #endif
52 #endif
53
54 #include "radiusd.h"
55
56 struct cmp {
57         int attribute;
58         int otherattr;
59         void *instance; /* module instance */
60         RAD_COMPARE_FUNC compare;
61         struct cmp *next;
62 };
63 static struct cmp *cmp;
64
65
66 /*
67  *      Compare 2 attributes. May call the attribute compare function.
68  */
69 static int compare_pair(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
70                        VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
71 {
72         int ret = -2;
73         struct cmp *c;
74
75         /*
76          *      Check for =* and !* and return appropriately
77          */
78         if( check->operator == T_OP_CMP_TRUE )
79                  return 0;  /* always return 0/EQUAL */
80         if( check->operator == T_OP_CMP_FALSE )
81                  return 1;  /* always return 1/NOT EQUAL */
82
83         /*
84          *      See if there is a special compare function.
85          *
86          *      FIXME: use new RB-Tree code.
87          */
88         for (c = cmp; c; c = c->next)
89                 if (c->attribute == check->attribute)
90                         return (c->compare)(c->instance, req, request, check,
91                                 check_pairs, reply_pairs);
92
93         if (!request) return -1; /* doesn't exist, don't compare it */
94
95         switch(check->type) {
96 #ifdef ASCEND_BINARY
97                 /*
98                  *      Ascend binary attributes can be treated
99                  *      as opaque objects, I guess...
100                  */
101                 case PW_TYPE_ABINARY:
102 #endif
103                 case PW_TYPE_OCTETS:
104                         if (request->length != check->length) {
105                                 ret = 1; /* NOT equal */
106                                 break;
107                         }
108                         ret = memcmp(request->vp_strvalue, check->vp_strvalue,
109                                         request->length);
110                         break;
111                 case PW_TYPE_STRING:
112                         if (check->flags.caseless) {
113                                 ret = strcasecmp((char *)request->vp_strvalue,
114                                                  (char *)check->vp_strvalue);
115                         } else {
116                                 ret = strcmp((char *)request->vp_strvalue,
117                                              (char *)check->vp_strvalue);
118                         }
119                         break;
120                 case PW_TYPE_INTEGER:
121                 case PW_TYPE_DATE:
122                         ret = request->lvalue - check->lvalue;
123                         break;
124                 case PW_TYPE_IPADDR:
125                         ret = ntohl(request->vp_ipaddr) - ntohl(check->vp_ipaddr);
126                         break;
127                 case PW_TYPE_IPV6ADDR:
128                         ret = memcmp(&request->vp_ipv6addr, &check->vp_ipv6addr,
129                                      sizeof(request->vp_ipv6addr));
130                         break;
131                         
132                 case PW_TYPE_IPV6PREFIX:
133                         ret = memcmp(&request->vp_ipv6prefix, &check->vp_ipv6prefix,
134                                      sizeof(request->vp_ipv6prefix));
135                         break;
136                 
137                 case PW_TYPE_IFID:
138                         ret = memcmp(&request->vp_ifid, &check->vp_ifid,
139                                      sizeof(request->vp_ifid));
140                         break;
141
142                 default:
143                         break;
144         }
145
146         return ret;
147 }
148
149
150 /*
151  *      See what attribute we want to compare with.
152  */
153 static int otherattr(int attr)
154 {
155         struct cmp      *c;
156
157         for (c = cmp; c; c = c->next) {
158                 if (c->attribute == attr)
159                         return c->otherattr;
160         }
161
162         return attr;
163 }
164
165 /*
166  *      Register a function as compare function.
167  *      compare_attr is the attribute in the request we want to
168  *      compare with. Normally this is the same as "attr".
169  *      You can set this to:
170  *
171  *      -1   the same as "attr"
172  *      0    always call compare function, not tied to request attribute
173  *      >0   Attribute to compare with.
174  *
175  *      For example, PW_GROUP in a check item needs to be compared
176  *      with PW_USER_NAME in the incoming request.
177  */
178 int paircompare_register(int attr, int compare_attr, RAD_COMPARE_FUNC fun, void *instance)
179 {
180         struct cmp      *c;
181
182         paircompare_unregister(attr, fun);
183
184         c = rad_malloc(sizeof(struct cmp));
185
186         if (compare_attr < 0)
187                 compare_attr = attr;
188         c->compare = fun;
189         c->attribute = attr;
190         c->otherattr = compare_attr;
191         c->instance = instance;
192         c->next = cmp;
193         cmp = c;
194
195         return 0;
196 }
197
198 /*
199  *      Unregister a function.
200  */
201 void paircompare_unregister(int attr, RAD_COMPARE_FUNC fun)
202 {
203         struct cmp      *c, *last;
204
205         last = NULL;
206         for (c = cmp; c; c = c->next) {
207                 if (c->attribute == attr && c->compare == fun)
208                         break;
209                 last = c;
210         }
211
212         if (c == NULL) return;
213
214         if (last != NULL)
215                 last->next = c->next;
216         else
217                 cmp = c->next;
218
219         free(c);
220 }
221
222 /*
223  *      Compare two pair lists except for the password information.
224  *      For every element in "check" at least one matching copy must
225  *      be present in "reply".
226  *
227  *      Return 0 on match.
228  */
229 int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **reply)
230 {
231         VALUE_PAIR *check_item;
232         VALUE_PAIR *auth_item;
233         int result = 0;
234         int compare;
235         int other;
236 #ifdef HAVE_REGEX_H
237         regex_t reg;
238 #endif
239
240         for (check_item = check; check_item != NULL; check_item = check_item->next) {
241                 /*
242                  *      If the user is setting a configuration value,
243                  *      then don't bother comparing it to any attributes
244                  *      sent to us by the user.  It ALWAYS matches.
245                  */
246                 if ((check_item->operator == T_OP_SET) ||
247                     (check_item->operator == T_OP_ADD)) {
248                         continue;
249                 }
250
251                 switch (check_item->attribute) {
252                         /*
253                          *      Attributes we skip during comparison.
254                          *      These are "server" check items.
255                          */
256                         case PW_CRYPT_PASSWORD:
257                         case PW_AUTH_TYPE:
258                         case PW_AUTZ_TYPE:
259                         case PW_ACCT_TYPE:
260                         case PW_SESSION_TYPE:
261                         case PW_STRIP_USER_NAME:
262                                 continue;
263                                 break;
264
265                         /*
266                          *      IF the password attribute exists, THEN
267                          *      we can do comparisons against it.  If not,
268                          *      then the request did NOT contain a
269                          *      User-Password attribute, so we CANNOT do
270                          *      comparisons against it.
271                          *
272                          *      This hack makes CHAP-Password work..
273                          */
274                         case PW_PASSWORD:
275                                 if (pairfind(request, PW_PASSWORD) == NULL) {
276                                         continue;
277                                 }
278                                 break;
279                 }
280
281                 /*
282                  *      See if this item is present in the request.
283                  */
284                 other = otherattr(check_item->attribute);
285
286                 auth_item = request;
287         try_again:
288                 for (; auth_item != NULL; auth_item = auth_item->next) {
289                         if (auth_item->attribute == other || other == 0)
290                                 break;
291                 }
292
293                 /*
294                  *      Not found, it's not a match.
295                  */
296                 if (auth_item == NULL) {
297                         /*
298                          *      Didn't find it.  If we were *trying*
299                          *      to not find it, then we succeeded.
300                          */
301                         if (check_item->operator == T_OP_CMP_FALSE)
302                                 return 0;
303                         else
304                                 return -1;
305                 }
306
307                 /*
308                  *      Else we found it, but we were trying to not
309                  *      find it, so we failed.
310                  */
311                 if (check_item->operator == T_OP_CMP_FALSE)
312                         return -1;
313
314
315                 /*
316                  *      We've got to xlat the string before doing
317                  *      the comparison.
318                  */
319                 if (check_item->flags.do_xlat) {
320                         int rcode;
321                         char buffer[sizeof(check_item->vp_strvalue)];
322
323                         check_item->flags.do_xlat = 0;
324                         rcode = radius_xlat(buffer, sizeof(buffer),
325                                             check_item->vp_strvalue,
326                                             req, NULL);
327
328                         /*
329                          *      Parse the string into a new value.
330                          */
331                         pairparsevalue(check_item, buffer);
332                 }
333
334                 /*
335                  *      OK it is present now compare them.
336                  */
337                 compare = compare_pair(req, auth_item, check_item, check, reply);
338
339                 switch (check_item->operator) {
340                         case T_OP_EQ:
341                         default:
342                                 radlog(L_INFO,  "Invalid operator for item %s: "
343                                                 "reverting to '=='", check_item->name);
344                                 /*FALLTHRU*/
345                         case T_OP_CMP_TRUE:    /* compare always == 0 */
346                         case T_OP_CMP_FALSE:   /* compare always == 1 */
347                         case T_OP_CMP_EQ:
348                                 if (compare != 0) result = -1;
349                                 break;
350
351                         case T_OP_NE:
352                                 if (compare == 0) result = -1;
353                                 break;
354
355                         case T_OP_LT:
356                                 if (compare >= 0) result = -1;
357                                 break;
358
359                         case T_OP_GT:
360                                 if (compare <= 0) result = -1;
361                                 break;
362
363                         case T_OP_LE:
364                                 if (compare > 0) result = -1;
365                                 break;
366
367                         case T_OP_GE:
368                                 if (compare < 0) result = -1;
369                                 break;
370
371 #ifdef HAVE_REGEX_H
372                         case T_OP_REG_EQ:
373                         {
374                                 int i;
375                                 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
376
377                                 if ((auth_item->type == PW_TYPE_IPADDR) &&
378                                     (auth_item->vp_strvalue[0] == '\0')) {
379                                   inet_ntop(AF_INET, &(auth_item->lvalue),
380                                             auth_item->vp_strvalue,
381                                             sizeof(auth_item->vp_strvalue));
382                                 }
383
384                                 /*
385                                  *      Include substring matches.
386                                  */
387                                 regcomp(&reg, (char *)check_item->vp_strvalue,
388                                         REG_EXTENDED);
389                                 compare = regexec(&reg,
390                                                   (char *)auth_item->vp_strvalue,
391                                                   REQUEST_MAX_REGEX + 1,
392                                                   rxmatch, 0);
393                                 regfree(&reg);
394
395                                 /*
396                                  *      Add %{0}, %{1}, etc.
397                                  */
398                                 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
399                                         char *p;
400                                         char buffer[sizeof(check_item->vp_strvalue)];
401
402                                         /*
403                                          *      Didn't match: delete old
404                                          *      match, if it existed.
405                                          */
406                                         if ((compare != 0) ||
407                                             (rxmatch[i].rm_so == -1)) {
408                                                 p = request_data_get(req, req,
409                                                                      REQUEST_DATA_REGEX | i);
410                                                 if (p) {
411                                                         free(p);
412                                                         continue;
413                                                 }
414
415                                                 /*
416                                                  *      No previous match
417                                                  *      to delete, stop.
418                                                  */
419                                                 break;
420                                         }
421                                         
422                                         /*
423                                          *      Copy substring into buffer.
424                                          */
425                                         memcpy(buffer,
426                                                auth_item->vp_strvalue + rxmatch[i].rm_so,
427                                                rxmatch[i].rm_eo - rxmatch[i].rm_so);
428                                         buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
429
430                                         /*
431                                          *      Copy substring, and add it to
432                                          *      the request.
433                                          *
434                                          *      Note that we don't check
435                                          *      for out of memory, which is
436                                          *      the only error we can get...
437                                          */
438                                         p = strdup(buffer);
439                                         request_data_add(req,
440                                                          req,
441                                                          REQUEST_DATA_REGEX | i,
442                                                          p, free);
443                                 }
444                         }                               
445                                 if (compare != 0) result = -1;
446                                 break;
447
448                         case T_OP_REG_NE:
449                                 if ((auth_item->type == PW_TYPE_IPADDR) &&
450                                     (auth_item->vp_strvalue[0] == '\0')) {
451                                   inet_ntop(AF_INET, &(auth_item->lvalue),
452                                             auth_item->vp_strvalue,
453                                             sizeof(auth_item->vp_strvalue));
454                                 }
455
456                                 regcomp(&reg, (char *)check_item->vp_strvalue, REG_EXTENDED|REG_NOSUB);
457                                 compare = regexec(&reg, (char *)auth_item->vp_strvalue,
458                                                 0, NULL, 0);
459                                 regfree(&reg);
460                                 if (compare == 0) result = -1;
461                                 break;
462 #endif
463
464                 } /* switch over the operator of the check item */
465
466                 /*
467                  *      This attribute didn't match, but maybe there's
468                  *      another of the same attribute, which DOES match.
469                  */
470                 if (result != 0) {
471                         auth_item = auth_item->next;
472                         result = 0;
473                         goto try_again;
474                 }
475
476         } /* for every entry in the check item list */
477
478         return 0;               /* it matched */
479 }
480
481 /*
482  *      Compare two attributes simply.  Calls compare_pair.
483  */
484
485 int simplepaircmp(REQUEST *req, VALUE_PAIR *first, VALUE_PAIR *second)
486 {
487         return compare_pair( req, first, second, NULL, NULL );
488 }
489
490
491 /*
492  *      Move pairs, replacing/over-writing them, and doing xlat.
493  */
494 /*
495  *      Move attributes from one list to the other
496  *      if not already present.
497  */
498 void pairxlatmove(REQUEST *req, VALUE_PAIR **to, VALUE_PAIR **from)
499 {
500         VALUE_PAIR **tailto, *i, *j, *next;
501         VALUE_PAIR *tailfrom = NULL;
502         VALUE_PAIR *found;
503
504         /*
505          *      Point "tailto" to the end of the "to" list.
506          */
507         tailto = to;
508         for(i = *to; i; i = i->next) {
509                 tailto = &i->next;
510         }
511
512         /*
513          *      Loop over the "from" list.
514          */
515         for(i = *from; i; i = next) {
516                 next = i->next;
517
518                 /*
519                  *      Don't move 'fallthrough' over.
520                  */
521                 if (i->attribute == PW_FALL_THROUGH) {
522                         continue;
523                 }
524
525                 /*
526                  *      We've got to xlat the string before moving
527                  *      it over.
528                  */
529                 if (i->flags.do_xlat) {
530                         int rcode;
531                         char buffer[sizeof(i->vp_strvalue)];
532
533                         i->flags.do_xlat = 0;
534                         rcode = radius_xlat(buffer, sizeof(buffer),
535                                             i->vp_strvalue,
536                                             req, NULL);
537
538                         /*
539                          *      Parse the string into a new value.
540                          */
541                         pairparsevalue(i, buffer);
542                 }
543
544                 found = pairfind(*to, i->attribute);
545                 switch (i->operator) {
546
547                         /*
548                          *  If a similar attribute is found,
549                          *  delete it.
550                          */
551                 case T_OP_SUB:          /* -= */
552                         if (found) {
553                                 if (!i->vp_strvalue[0] ||
554                                     (strcmp((char *)found->vp_strvalue,
555                                             (char *)i->vp_strvalue) == 0)){
556                                         pairdelete(to, found->attribute);
557
558                                         /*
559                                          *      'tailto' may have been
560                                          *      deleted...
561                                          */
562                                         tailto = to;
563                                         for(j = *to; j; j = j->next) {
564                                                 tailto = &j->next;
565                                         }
566                                 }
567                         }
568                         tailfrom = i;
569                         continue;
570                         break;
571
572                         /*
573                          *  Add it, if it's not already there.
574                          */
575                 case T_OP_EQ:           /* = */
576                         if (found) {
577                                 tailfrom = i;
578                                 continue; /* with the loop */
579                         }
580                         break;
581
582                         /*
583                          *  If a similar attribute is found,
584                          *  replace it with the new one.  Otherwise,
585                          *  add the new one to the list.
586                          */
587                 case T_OP_SET:          /* := */
588                         if (found) {
589                                 VALUE_PAIR *vp;
590
591                                 vp = found->next;
592                                 memcpy(found, i, sizeof(*found));
593                                 found->next = vp;
594                                 continue;
595                         }
596                         break;
597
598                         /*
599                          *  FIXME: Add support for <=, >=, <, >
600                          *
601                          *  which will mean (for integers)
602                          *  'make the attribute the smaller, etc'
603                          */
604
605                         /*
606                          *  Add the new element to the list, even
607                          *  if similar ones already exist.
608                          */
609                 default:
610                 case T_OP_ADD:          /* += */
611                         break;
612                 }
613
614                 if (tailfrom)
615                         tailfrom->next = next;
616                 else
617                         *from = next;
618
619                 /*
620                  *      If ALL of the 'to' attributes have been deleted,
621                  *      then ensure that the 'tail' is updated to point
622                  *      to the head.
623                  */
624                 if (!*to) {
625                         tailto = to;
626                 }
627                 *tailto = i;
628                 if (i) {
629                         i->next = NULL;
630                         tailto = &i->next;
631                 }
632         } /* loop over the 'from' list */
633 }