607bab4b22320fe50c2484fbc86e9dd3067c946e
[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 #include "libradius.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #ifdef HAVE_NETINET_IN_H
35 #       include <netinet/in.h>
36 #endif
37
38 #ifdef HAVE_REGEX_H
39 #       include <regex.h>
40
41 /*
42  *  For POSIX Regular expressions.
43  *  (0) Means no extended regular expressions.
44  *  REG_EXTENDED means use extended regular expressions.
45  */
46 #ifndef REG_EXTENDED
47 #define REG_EXTENDED (0)
48 #endif
49
50 #ifndef REG_NOSUB
51 #define REG_NOSUB (0)
52 #endif
53 #endif
54
55 #include "radiusd.h"
56
57 struct cmp {
58         int attribute;
59         int otherattr;
60         void *instance; /* module instance */
61         RAD_COMPARE_FUNC compare;
62         struct cmp *next;
63 };
64 static struct cmp *cmp;
65
66
67 /*
68  *      Compare 2 attributes. May call the attribute compare function.
69  */
70 static int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
71                        VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
72 {
73         int ret = -2;
74         struct cmp *c;
75
76         /*
77          *      Sanity check.
78          */
79 #if 0
80         if (request->attribute != check->attribute)
81                 return -2;
82 #endif
83
84         /*
85          *      Check for =* and !* and return appropriately
86          */
87         if( check->operator == T_OP_CMP_TRUE )
88                  return 0;  /* always return 0/EQUAL */
89         if( check->operator == T_OP_CMP_FALSE )
90                  return 1;  /* always return 1/NOT EQUAL */
91
92         /*
93          *      See if there is a special compare function.
94          */
95         for (c = cmp; c; c = c->next)
96                 if (c->attribute == check->attribute)
97                         return (c->compare)(c->instance, req, request, check,
98                                 check_pairs, reply_pairs);
99
100         switch(check->type) {
101 #ifdef ASCEND_BINARY
102                 /*
103                  *      Ascend binary attributes can be treated
104                  *      as opaque objects, I guess...
105                  */
106                 case PW_TYPE_ABINARY:
107 #endif
108                 case PW_TYPE_OCTETS:
109                         if (request->length != check->length) {
110                                 ret = 1; /* NOT equal */
111                                 break;
112                         }
113                         ret = memcmp(request->strvalue, check->strvalue,
114                                         request->length);
115                         break;
116                 case PW_TYPE_STRING:
117                         if (check->flags.caseless) {
118                                 ret = strcasecmp((char *)request->strvalue,
119                                                  (char *)check->strvalue);
120                         } else {
121                                 ret = strcmp((char *)request->strvalue,
122                                              (char *)check->strvalue);
123                         }
124                         break;
125                 case PW_TYPE_INTEGER:
126                 case PW_TYPE_DATE:
127                         ret = request->lvalue - check->lvalue;
128                         break;
129                 case PW_TYPE_IPADDR:
130                         ret = ntohl(request->lvalue) - ntohl(check->lvalue);
131                         break;
132                 default:
133                         break;
134         }
135
136         return ret;
137 }
138
139
140 /*
141  *      See what attribute we want to compare with.
142  */
143 static int otherattr(int attr)
144 {
145         struct cmp      *c;
146
147         for (c = cmp; c; c = c->next) {
148                 if (c->attribute == attr)
149                         return c->otherattr;
150         }
151
152         return attr;
153 }
154
155 /*
156  *      Register a function as compare function.
157  *      compare_attr is the attribute in the request we want to
158  *      compare with. Normally this is the same as "attr".
159  *      You can set this to:
160  *
161  *      -1   the same as "attr"
162  *      0    always call compare function, not tied to request attribute
163  *      >0   Attribute to compare with.
164  *
165  *      For example, PW_GROUP in a check item needs to be compared
166  *      with PW_USER_NAME in the incoming request.
167  */
168 int paircompare_register(int attr, int compare_attr, RAD_COMPARE_FUNC fun, void *instance)
169 {
170         struct cmp      *c;
171
172         paircompare_unregister(attr, fun);
173
174         c = rad_malloc(sizeof(struct cmp));
175
176         if (compare_attr < 0)
177                 compare_attr = attr;
178         c->compare = fun;
179         c->attribute = attr;
180         c->otherattr = compare_attr;
181         c->instance = instance;
182         c->next = cmp;
183         cmp = c;
184
185         return 0;
186 }
187
188 /*
189  *      Unregister a function.
190  */
191 void paircompare_unregister(int attr, RAD_COMPARE_FUNC fun)
192 {
193         struct cmp      *c, *last;
194
195         last = NULL;
196         for (c = cmp; c; c = c->next) {
197                 if (c->attribute == attr && c->compare == fun)
198                         break;
199                 last = c;
200         }
201
202         if (c == NULL) return;
203
204         if (last != NULL)
205                 last->next = c->next;
206         else
207                 cmp = c->next;
208
209         free(c);
210 }
211
212 /*
213  *      Compare two pair lists except for the password information.
214  *      For every element in "check" at least one matching copy must
215  *      be present in "reply".
216  *
217  *      Return 0 on match.
218  */
219 int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **reply)
220 {
221         VALUE_PAIR *check_item;
222         VALUE_PAIR *auth_item;
223         int result = 0;
224         int compare;
225         int other;
226 #ifdef HAVE_REGEX_H
227         regex_t reg;
228 #endif
229
230         for (check_item = check; check_item != NULL; check_item = check_item->next) {
231                 /*
232                  *      If the user is setting a configuration value,
233                  *      then don't bother comparing it to any attributes
234                  *      sent to us by the user.  It ALWAYS matches.
235                  */
236                 if ((check_item->operator == T_OP_SET) ||
237                     (check_item->operator == T_OP_ADD)) {
238                         continue;
239                 }
240
241                 switch (check_item->attribute) {
242                         /*
243                          *      Attributes we skip during comparison.
244                          *      These are "server" check items.
245                          */
246                         case PW_CRYPT_PASSWORD:
247                         case PW_AUTH_TYPE:
248                         case PW_AUTZ_TYPE:
249                         case PW_ACCT_TYPE:
250                         case PW_SESSION_TYPE:
251                         case PW_STRIP_USER_NAME:
252                                 continue;
253                                 break;
254
255                         /*
256                          *      IF the password attribute exists, THEN
257                          *      we can do comparisons against it.  If not,
258                          *      then the request did NOT contain a
259                          *      User-Password attribute, so we CANNOT do
260                          *      comparisons against it.
261                          *
262                          *      This hack makes CHAP-Password work..
263                          */
264                         case PW_PASSWORD:
265                                 if (pairfind(request, PW_PASSWORD) == NULL) {
266                                         continue;
267                                 }
268                                 break;
269                 }
270
271                 /*
272                  *      See if this item is present in the request.
273                  */
274                 other = otherattr(check_item->attribute);
275
276                 auth_item = request;
277         try_again:
278                 for (; auth_item != NULL; auth_item = auth_item->next) {
279                         if (auth_item->attribute == other || other == 0)
280                                 break;
281                 }
282
283                 /*
284                  *      Not found, it's not a match.
285                  */
286                 if (auth_item == NULL) {
287                         /*
288                          *      Didn't find it.  If we were *trying*
289                          *      to not find it, then we succeeded.
290                          */
291                         if (check_item->operator == T_OP_CMP_FALSE)
292                                 return 0;
293                         else
294                                 return -1;
295                 }
296
297                 /*
298                  *      Else we found it, but we were trying to not
299                  *      find it, so we failed.
300                  */
301                 if (check_item->operator == T_OP_CMP_FALSE)
302                         return -1;
303
304
305                 /*
306                  *      We've got to xlat the string before doing
307                  *      the comparison.
308                  */
309                 if (check_item->flags.do_xlat) {
310                         int rcode;
311                         char buffer[sizeof(check_item->strvalue)];
312
313                         check_item->flags.do_xlat = 0;
314                         rcode = radius_xlat(buffer, sizeof(buffer),
315                                             check_item->strvalue,
316                                             req, NULL);
317
318                         /*
319                          *      Parse the string into a new value.
320                          */
321                         pairparsevalue(check_item, buffer);
322                 }
323
324                 /*
325                  *      OK it is present now compare them.
326                  */
327                 compare = paircompare(req, auth_item, check_item, check, reply);
328
329                 switch (check_item->operator) {
330                         case T_OP_EQ:
331                         default:
332                                 radlog(L_INFO,  "Invalid operator for item %s: "
333                                                 "reverting to '=='", check_item->name);
334                                 /*FALLTHRU*/
335                         case T_OP_CMP_TRUE:    /* compare always == 0 */
336                         case T_OP_CMP_FALSE:   /* compare always == 1 */
337                         case T_OP_CMP_EQ:
338                                 if (compare != 0) result = -1;
339                                 break;
340
341                         case T_OP_NE:
342                                 if (compare == 0) result = -1;
343                                 break;
344
345                         case T_OP_LT:
346                                 if (compare >= 0) result = -1;
347                                 break;
348
349                         case T_OP_GT:
350                                 if (compare <= 0) result = -1;
351                                 break;
352
353                         case T_OP_LE:
354                                 if (compare > 0) result = -1;
355                                 break;
356
357                         case T_OP_GE:
358                                 if (compare < 0) result = -1;
359                                 break;
360
361 #ifdef HAVE_REGEX_H
362                         case T_OP_REG_EQ:
363                         {
364                                 int i;
365                                 regmatch_t rxmatch[9];
366
367                                 /*
368                                  *      Include substring matches.
369                                  */
370                                 regcomp(&reg, (char *)check_item->strvalue,
371                                         REG_EXTENDED);
372                                 compare = regexec(&reg,
373                                                   (char *)auth_item->strvalue,
374                                                   16, rxmatch, 0);
375                                 regfree(&reg);
376
377                                 /*
378                                  *      Add %{0}, %{1}, etc.
379                                  */
380                                 for (i = 0; i <= 8; i++) {
381                                         char *p;
382                                         char buffer[sizeof(check_item->strvalue)];
383
384                                         /*
385                                          *      Didn't match: delete old
386                                          *      match, if it existed.
387                                          */
388                                         if ((compare != 0) ||
389                                             (rxmatch[i].rm_so == -1)) {
390                                                 p = request_data_get(req, req,
391                                                                      REQUEST_DATA_REGEX | i);
392                                                 if (p) {
393                                                         free(p);
394                                                         continue;
395                                                 }
396
397                                                 /*
398                                                  *      No previous match
399                                                  *      to delete, stop.
400                                                  */
401                                                 break;
402                                         }
403                                         
404                                         /*
405                                          *      Copy substring into buffer.
406                                          */
407                                         memcpy(buffer,
408                                                auth_item->strvalue + rxmatch[i].rm_so,
409                                                rxmatch[i].rm_eo);
410                                         buffer[rxmatch[i].rm_eo] = '\0';
411
412                                         /*
413                                          *      Copy substring, and add it to
414                                          *      the request.
415                                          *
416                                          *      Note that we don't check
417                                          *      for out of memory, which is
418                                          *      the only error we can get...
419                                          */
420                                         p = strdup(buffer);
421                                         request_data_add(req,
422                                                          req,
423                                                          REQUEST_DATA_REGEX | i,
424                                                          p, free);
425                                 }
426                         }                               
427                                 if (compare != 0) result = -1;
428                                 break;
429
430                         case T_OP_REG_NE:
431                                 regcomp(&reg, (char *)check_item->strvalue, REG_EXTENDED|REG_NOSUB);
432                                 compare = regexec(&reg, (char *)auth_item->strvalue,
433                                                 0, NULL, 0);
434                                 regfree(&reg);
435                                 if (compare == 0) result = -1;
436                                 break;
437 #endif
438
439                 } /* switch over the operator of the check item */
440
441                 /*
442                  *      This attribute didn't match, but maybe there's
443                  *      another of the same attribute, which DOES match.
444                  */
445                 if (result != 0) {
446                         auth_item = auth_item->next;
447                         result = 0;
448                         goto try_again;
449                 }
450
451         } /* for every entry in the check item list */
452
453         return 0;               /* it matched */
454 }
455
456 /*
457  *      Compare two attributes simply.  Calls paircompare.
458  */
459
460 int simplepaircmp(REQUEST *req, VALUE_PAIR *first, VALUE_PAIR *second)
461 {
462         return paircompare( req, first, second, NULL, NULL );
463 }
464
465
466 /*
467  *      Compare a Connect-Info and a Connect-Rate
468  */
469 static int connectcmp(void *instance,
470                       REQUEST *req UNUSED,
471                       VALUE_PAIR *request,
472                       VALUE_PAIR *check,
473                       VALUE_PAIR *check_pairs,
474                       VALUE_PAIR **reply_pairs)
475 {
476         int rate;
477
478         instance = instance;
479         check_pairs = check_pairs; /* shut the compiler up */
480         reply_pairs = reply_pairs;
481
482         rate = atoi((char *)request->strvalue);
483         return rate - check->lvalue;
484 }
485
486
487 /*
488  *      Compare a portno with a range.
489  */
490 static int portcmp(void *instance,
491                    REQUEST *req UNUSED, VALUE_PAIR *request, VALUE_PAIR *check,
492         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
493 {
494         char buf[MAX_STRING_LEN];
495         char *s, *p;
496         uint32_t lo, hi;
497         uint32_t port = request->lvalue;
498
499         instance = instance;
500         check_pairs = check_pairs; /* shut the compiler up */
501         reply_pairs = reply_pairs;
502
503         if ((strchr((char *)check->strvalue, ',') == NULL) &&
504                         (strchr((char *)check->strvalue, '-') == NULL)) {
505                 return (request->lvalue - check->lvalue);
506         }
507
508         /* Same size */
509         strcpy(buf, (char *)check->strvalue);
510         s = strtok(buf, ",");
511
512         while (s != NULL) {
513                 if ((p = strchr(s, '-')) != NULL)
514                         p++;
515                 else
516                         p = s;
517                 lo = strtoul(s, NULL, 10);
518                 hi = strtoul(p, NULL, 10);
519                 if (lo <= port && port <= hi) {
520                         return 0;
521                 }
522                 s = strtok(NULL, ",");
523         }
524
525         return -1;
526 }
527
528 /*
529  *      Compare prefix/suffix.
530  *
531  *      If they compare:
532  *      - if PW_STRIP_USER_NAME is present in check_pairs,
533  *        strip the username of prefix/suffix.
534  *      - if PW_STRIP_USER_NAME is not present in check_pairs,
535  *        add a PW_STRIPPED_USER_NAME to the request.
536  */
537 static int presufcmp(void *instance,
538                      REQUEST *req UNUSED,
539                      VALUE_PAIR *request, VALUE_PAIR *check,
540         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
541 {
542         VALUE_PAIR *vp;
543         char *name = (char *)request->strvalue;
544         char rest[MAX_STRING_LEN];
545         int len, namelen;
546         int ret = -1;
547
548         instance = instance;
549         reply_pairs = reply_pairs; /* shut the compiler up */
550
551 #if 0 /* DEBUG */
552         printf("Comparing %s and %s, check->attr is %d\n",
553                 name, check->strvalue, check->attribute);
554 #endif
555
556         len = strlen((char *)check->strvalue);
557         switch (check->attribute) {
558                 case PW_PREFIX:
559                         ret = strncmp(name, (char *)check->strvalue, len);
560                         if (ret == 0 && rest)
561                                 strcpy(rest, name + len);
562                         break;
563                 case PW_SUFFIX:
564                         namelen = strlen(name);
565                         if (namelen < len)
566                                 break;
567                         ret = strcmp(name + namelen - len,
568                                         (char *)check->strvalue);
569                         if (ret == 0 && rest) {
570                                 strNcpy(rest, name, namelen - len + 1);
571                         }
572                         break;
573         }
574         if (ret != 0)
575                 return ret;
576
577         if (pairfind(check_pairs, PW_STRIP_USER_NAME)) {
578                 /*
579                  *      I don't think we want to update the User-Name
580                  *      attribute in place... - atd
581                  */
582                 strcpy((char *)request->strvalue, rest);
583                 request->length = strlen(rest);
584         } else {
585                 if ((vp = pairfind(check_pairs, PW_STRIPPED_USER_NAME)) != NULL){
586                         strcpy((char *)vp->strvalue, rest);
587                         vp->length = strlen(rest);
588                 } else if ((vp = paircreate(PW_STRIPPED_USER_NAME,
589                                 PW_TYPE_STRING)) != NULL) {
590                         strcpy((char *)vp->strvalue, rest);
591                         vp->length = strlen(rest);
592                         pairadd(&request, vp);
593                 } /* else no memory! Die, die!: FIXME!! */
594         }
595
596         return ret;
597 }
598
599
600 /*
601  *      Compare the current time to a range.
602  */
603 static int timecmp(void *instance,
604                    REQUEST *req,
605                    VALUE_PAIR *request, VALUE_PAIR *check,
606         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
607 {
608         instance = instance;
609         request = request;      /* shut the compiler up */
610         check_pairs = check_pairs;
611         reply_pairs = reply_pairs;
612
613         /*
614          *      If there's a request, use that timestamp.
615          */
616         if (timestr_match((char *)check->strvalue,
617                           req ? req->timestamp : time(NULL)) >= 0) {
618                 return 0;
619         }
620         return -1;
621 }
622
623 /*
624  *      Matches if there is NO SUCH ATTRIBUTE as the one named
625  *      in check->strvalue.  If there IS such an attribute, it
626  *      doesn't match.
627  *
628  *      This is ugly, and definitely non-optimal.  We should be
629  *      doing the lookup only ONCE, and storing the result
630  *      in check->lvalue...
631  */
632 static int attrcmp(void *instance,
633                    REQUEST *req UNUSED,
634                    VALUE_PAIR *request, VALUE_PAIR *check,
635                    VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
636 {
637         VALUE_PAIR *pair;
638         DICT_ATTR  *dict;
639         int attr;
640
641         instance = instance;
642         check_pairs = check_pairs; /* shut the compiler up */
643         reply_pairs = reply_pairs;
644
645         if (check->lvalue == 0) {
646                 dict = dict_attrbyname((char *)check->strvalue);
647                 if (dict == NULL) {
648                         return -1;
649                 }
650                 attr = dict->attr;
651         } else {
652                 attr = check->lvalue;
653         }
654
655         /*
656          *      If there's no such attribute, then return MATCH,
657          *      else FAILURE.
658          */
659         pair = pairfind(request, attr);
660         if (pair == NULL) {
661                 return 0;
662         }
663
664         return -1;
665 }
666
667 /*
668  *      Compare the expiration date.
669  */
670 static int expirecmp(void *instance, REQUEST *req UNUSED,
671                      VALUE_PAIR *request, VALUE_PAIR *check,
672                      VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
673 {
674         time_t now;
675
676         instance = instance;
677         request = request;      /* shut the compiler up */
678         check_pairs = check_pairs;
679         reply_pairs = reply_pairs;
680
681         /*
682          *  FIXME!  This should be request->timestamp!
683          */
684         now = time(NULL);
685
686         if (now <= (signed)check->lvalue) {
687                 return 0;
688         }
689
690         return +1;
691 }
692
693 /*
694  *      Compare the request packet type.
695  */
696 static int packetcmp(void *instance UNUSED, REQUEST *req,
697                      VALUE_PAIR *request UNUSED,
698                      VALUE_PAIR *check,
699                      VALUE_PAIR *check_pairs UNUSED,
700                      VALUE_PAIR **reply_pairs UNUSED)
701 {
702         if (req->packet->code == check->lvalue) {
703                 return 0;
704         }
705
706         return 1;
707 }
708
709 /*
710  *      Compare the response packet type.
711  */
712 static int responsecmp(void *instance UNUSED,
713                        REQUEST *req,
714                        VALUE_PAIR *request UNUSED,
715                        VALUE_PAIR *check,
716                        VALUE_PAIR *check_pairs UNUSED,
717                        VALUE_PAIR **reply_pairs UNUSED)
718 {
719         if (req->reply->code == check->lvalue) {
720                 return 0;
721         }
722
723         return 1;
724 }
725
726 /*
727  *      Register server-builtin special attributes.
728  */
729 void pair_builtincompare_init(void)
730 {
731         paircompare_register(PW_NAS_PORT, -1, portcmp, NULL);
732         paircompare_register(PW_PREFIX, PW_USER_NAME, presufcmp, NULL);
733         paircompare_register(PW_SUFFIX, PW_USER_NAME, presufcmp, NULL);
734         paircompare_register(PW_CONNECT_RATE, PW_CONNECT_INFO, connectcmp, NULL);
735         paircompare_register(PW_CURRENT_TIME, 0, timecmp, NULL);
736         paircompare_register(PW_NO_SUCH_ATTRIBUTE, 0, attrcmp, NULL);
737         paircompare_register(PW_EXPIRATION, 0, expirecmp, NULL);
738         paircompare_register(PW_PACKET_TYPE, 0, packetcmp, NULL);
739         paircompare_register(PW_RESPONSE_PACKET_TYPE, 0, responsecmp, NULL);
740 }
741
742 /*
743  *      Move pairs, replacing/over-writing them, and doing xlat.
744  */
745 /*
746  *      Move attributes from one list to the other
747  *      if not already present.
748  */
749 void pairxlatmove(REQUEST *req, VALUE_PAIR **to, VALUE_PAIR **from)
750 {
751         VALUE_PAIR **tailto, *i, *j, *next;
752         VALUE_PAIR *tailfrom = NULL;
753         VALUE_PAIR *found;
754
755         /*
756          *      Point "tailto" to the end of the "to" list.
757          */
758         tailto = to;
759         for(i = *to; i; i = i->next) {
760                 tailto = &i->next;
761         }
762
763         /*
764          *      Loop over the "from" list.
765          */
766         for(i = *from; i; i = next) {
767                 next = i->next;
768
769                 /*
770                  *      Don't move 'fallthrough' over.
771                  */
772                 if (i->attribute == PW_FALL_THROUGH) {
773                         continue;
774                 }
775
776                 /*
777                  *      We've got to xlat the string before moving
778                  *      it over.
779                  */
780                 if (i->flags.do_xlat) {
781                         int rcode;
782                         char buffer[sizeof(i->strvalue)];
783
784                         i->flags.do_xlat = 0;
785                         rcode = radius_xlat(buffer, sizeof(buffer),
786                                             i->strvalue,
787                                             req, NULL);
788
789                         /*
790                          *      Parse the string into a new value.
791                          */
792                         pairparsevalue(i, buffer);
793                 }
794
795                 found = pairfind(*to, i->attribute);
796                 switch (i->operator) {
797
798                         /*
799                          *  If a similar attribute is found,
800                          *  delete it.
801                          */
802                 case T_OP_SUB:          /* -= */
803                         if (found) {
804                                 if (!i->strvalue[0] ||
805                                     (strcmp((char *)found->strvalue,
806                                             (char *)i->strvalue) == 0)){
807                                         pairdelete(to, found->attribute);
808
809                                         /*
810                                          *      'tailto' may have been
811                                          *      deleted...
812                                          */
813                                         tailto = to;
814                                         for(j = *to; j; j = j->next) {
815                                                 tailto = &j->next;
816                                         }
817                                 }
818                         }
819                         tailfrom = i;
820                         continue;
821                         break;
822
823                         /*
824                          *  Add it, if it's not already there.
825                          */
826                 case T_OP_EQ:           /* = */
827                         if (found) {
828                                 tailfrom = i;
829                                 continue; /* with the loop */
830                         }
831                         break;
832
833                         /*
834                          *  If a similar attribute is found,
835                          *  replace it with the new one.  Otherwise,
836                          *  add the new one to the list.
837                          */
838                 case T_OP_SET:          /* := */
839                         if (found) {
840                                 VALUE_PAIR *vp;
841
842                                 vp = found->next;
843                                 memcpy(found, i, sizeof(*found));
844                                 found->next = vp;
845                                 continue;
846                         }
847                         break;
848
849                         /*
850                          *  FIXME: Add support for <=, >=, <, >
851                          *
852                          *  which will mean (for integers)
853                          *  'make the attribute the smaller, etc'
854                          */
855
856                         /*
857                          *  Add the new element to the list, even
858                          *  if similar ones already exist.
859                          */
860                 default:
861                 case T_OP_ADD:          /* += */
862                         break;
863                 }
864
865                 if (tailfrom)
866                         tailfrom->next = next;
867                 else
868                         *from = next;
869
870                 /*
871                  *      If ALL of the 'to' attributes have been deleted,
872                  *      then ensure that the 'tail' is updated to point
873                  *      to the head.
874                  */
875                 if (!*to) {
876                         tailto = to;
877                 }
878                 *tailto = i;
879                 if (i) {
880                         i->next = NULL;
881                         tailto = &i->next;
882                 }
883         } /* loop over the 'from' list */
884 }