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