Massively cleaned up #include's, so they're in a consistent
[freeradius.git] / src / main / valuepair.c
index 75b2e65..76e962c 100644 (file)
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * Copyright 2000  The FreeRADIUS server project
+ * Copyright 2000,2006  The FreeRADIUS server project
  * Copyright 2000  Alan DeKok <aland@ox.org>
  */
 
-static const char rcsid[] = "$Id$";
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
 
-#include "autoconf.h"
-#include "libradius.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#if HAVE_NETINET_IN_H
-#      include <netinet/in.h>
-#endif
+#include <freeradius-devel/radiusd.h>
 
 #ifdef HAVE_REGEX_H
 #      include <regex.h>
@@ -46,9 +38,11 @@ static const char rcsid[] = "$Id$";
 #ifndef REG_EXTENDED
 #define REG_EXTENDED (0)
 #endif
-#endif
 
-#include "radiusd.h"
+#ifndef REG_NOSUB
+#define REG_NOSUB (0)
+#endif
+#endif
 
 struct cmp {
        int attribute;
@@ -63,21 +57,13 @@ static struct cmp *cmp;
 /*
  *     Compare 2 attributes. May call the attribute compare function.
  */
-static int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-       VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
+static int compare_pair(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
+                      VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
 {
        int ret = -2;
        struct cmp *c;
 
        /*
-        *      Sanity check.
-        */
-#if 0
-       if (request->attribute != check->attribute)
-               return -2;
-#endif
-
-       /*
         *      Check for =* and !* and return appropriately
         */
        if( check->operator == T_OP_CMP_TRUE )
@@ -87,12 +73,16 @@ static int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
 
        /*
         *      See if there is a special compare function.
+        *
+        *      FIXME: use new RB-Tree code.
         */
        for (c = cmp; c; c = c->next)
                if (c->attribute == check->attribute)
                        return (c->compare)(c->instance, req, request, check,
                                check_pairs, reply_pairs);
 
+       if (!request) return -1; /* doesn't exist, don't compare it */
+
        switch(check->type) {
 #ifdef ASCEND_BINARY
                /*
@@ -106,20 +96,40 @@ static int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
                                ret = 1; /* NOT equal */
                                break;
                        }
-                       ret = memcmp(request->strvalue, check->strvalue,
+                       ret = memcmp(request->vp_strvalue, check->vp_strvalue,
                                        request->length);
                        break;
                case PW_TYPE_STRING:
-                       ret = strcmp((char *)request->strvalue,
-                                       (char *)check->strvalue);
+                       if (check->flags.caseless) {
+                               ret = strcasecmp((char *)request->vp_strvalue,
+                                                (char *)check->vp_strvalue);
+                       } else {
+                               ret = strcmp((char *)request->vp_strvalue,
+                                            (char *)check->vp_strvalue);
+                       }
                        break;
                case PW_TYPE_INTEGER:
                case PW_TYPE_DATE:
                        ret = request->lvalue - check->lvalue;
                        break;
                case PW_TYPE_IPADDR:
-                       ret = ntohl(request->lvalue) - ntohl(check->lvalue);
+                       ret = ntohl(request->vp_ipaddr) - ntohl(check->vp_ipaddr);
+                       break;
+               case PW_TYPE_IPV6ADDR:
+                       ret = memcmp(&request->vp_ipv6addr, &check->vp_ipv6addr,
+                                    sizeof(request->vp_ipv6addr));
                        break;
+                       
+               case PW_TYPE_IPV6PREFIX:
+                       ret = memcmp(&request->vp_ipv6prefix, &check->vp_ipv6prefix,
+                                    sizeof(request->vp_ipv6prefix));
+                       break;
+               
+               case PW_TYPE_IFID:
+                       ret = memcmp(&request->vp_ifid, &check->vp_ifid,
+                                    sizeof(request->vp_ifid));
+                       break;
+
                default:
                        break;
        }
@@ -164,7 +174,7 @@ int paircompare_register(int attr, int compare_attr, RAD_COMPARE_FUNC fun, void
 
        c = rad_malloc(sizeof(struct cmp));
 
-       if (compare_attr < 0) 
+       if (compare_attr < 0)
                compare_attr = attr;
        c->compare = fun;
        c->attribute = attr;
@@ -207,7 +217,7 @@ void paircompare_unregister(int attr, RAD_COMPARE_FUNC fun)
  *
  *     Return 0 on match.
  */
-int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **reply)
+int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **reply)
 {
        VALUE_PAIR *check_item;
        VALUE_PAIR *auth_item;
@@ -235,6 +245,11 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
                         *      These are "server" check items.
                         */
                        case PW_CRYPT_PASSWORD:
+                       case PW_AUTH_TYPE:
+                       case PW_AUTZ_TYPE:
+                       case PW_ACCT_TYPE:
+                       case PW_SESSION_TYPE:
+                       case PW_STRIP_USER_NAME:
                                continue;
                                break;
 
@@ -247,8 +262,8 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
                         *
                         *      This hack makes CHAP-Password work..
                         */
-                       case PW_PASSWORD:
-                               if (pairfind(request, PW_PASSWORD) == NULL) {
+                       case PW_USER_PASSWORD:
+                               if (pairfind(request, PW_USER_PASSWORD) == NULL) {
                                        continue;
                                }
                                break;
@@ -258,6 +273,7 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
                 *      See if this item is present in the request.
                 */
                other = otherattr(check_item->attribute);
+
                auth_item = request;
        try_again:
                for (; auth_item != NULL; auth_item = auth_item->next) {
@@ -269,36 +285,52 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
                 *      Not found, it's not a match.
                 */
                if (auth_item == NULL) {
-                       return -1;
+                       /*
+                        *      Didn't find it.  If we were *trying*
+                        *      to not find it, then we succeeded.
+                        */
+                       if (check_item->operator == T_OP_CMP_FALSE)
+                               return 0;
+                       else
+                               return -1;
                }
 
-#if 0
-               if ((check_item->strvalue[0] == '%') &&
-                   (check_item->strvalue[1] == '{')) {
+               /*
+                *      Else we found it, but we were trying to not
+                *      find it, so we failed.
+                */
+               if (check_item->operator == T_OP_CMP_FALSE)
+                       return -1;
+
+
+               /*
+                *      We've got to xlat the string before doing
+                *      the comparison.
+                */
+               if (check_item->flags.do_xlat) {
                        int rcode;
-                       char buffer[sizeof(check_item->strvalue)];
+                       char buffer[sizeof(check_item->vp_strvalue)];
 
+                       check_item->flags.do_xlat = 0;
                        rcode = radius_xlat(buffer, sizeof(buffer),
-                                           check_item->strvalue,
+                                           check_item->vp_strvalue,
                                            req, NULL);
-                       strNcpy(check_item->strvalue, buffer,
-                               sizeof(check_item->strvalue));
 
                        /*
-                        *  Parse it into an ipaddr, integer, or date.
+                        *      Parse the string into a new value.
                         */
+                       pairparsevalue(check_item, buffer);
                }
-#endif
 
                /*
                 *      OK it is present now compare them.
                 */
-               compare = paircompare(req, auth_item, check_item, check, reply);
+               compare = compare_pair(req, auth_item, check_item, check, reply);
 
                switch (check_item->operator) {
                        case T_OP_EQ:
                        default:
-                               radlog(L_ERR,  "Invalid operator for item %s: "
+                               radlog(L_INFO,  "Invalid operator for item %s: "
                                                "reverting to '=='", check_item->name);
                                /*FALLTHRU*/
                        case T_OP_CMP_TRUE:    /* compare always == 0 */
@@ -318,7 +350,7 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
                        case T_OP_GT:
                                if (compare <= 0) result = -1;
                                break;
-                   
+
                        case T_OP_LE:
                                if (compare > 0) result = -1;
                                break;
@@ -329,16 +361,91 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
 
 #ifdef HAVE_REGEX_H
                        case T_OP_REG_EQ:
-                               regcomp(&reg, (char *)check_item->strvalue, REG_EXTENDED);
-                               compare = regexec(&reg, (char *)auth_item->strvalue,
-                                               0, NULL, 0);
+                       {
+                               int i;
+                               regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
+
+                               if ((auth_item->type == PW_TYPE_IPADDR) &&
+                                   (auth_item->vp_strvalue[0] == '\0')) {
+                                 inet_ntop(AF_INET, &(auth_item->lvalue),
+                                           auth_item->vp_strvalue,
+                                           sizeof(auth_item->vp_strvalue));
+                               }
+
+                               /*
+                                *      Include substring matches.
+                                */
+                               regcomp(&reg, (char *)check_item->vp_strvalue,
+                                       REG_EXTENDED);
+                               compare = regexec(&reg,
+                                                 (char *)auth_item->vp_strvalue,
+                                                 REQUEST_MAX_REGEX + 1,
+                                                 rxmatch, 0);
                                regfree(&reg);
+
+                               /*
+                                *      Add %{0}, %{1}, etc.
+                                */
+                               for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
+                                       char *p;
+                                       char buffer[sizeof(check_item->vp_strvalue)];
+
+                                       /*
+                                        *      Didn't match: delete old
+                                        *      match, if it existed.
+                                        */
+                                       if ((compare != 0) ||
+                                           (rxmatch[i].rm_so == -1)) {
+                                               p = request_data_get(req, req,
+                                                                    REQUEST_DATA_REGEX | i);
+                                               if (p) {
+                                                       free(p);
+                                                       continue;
+                                               }
+
+                                               /*
+                                                *      No previous match
+                                                *      to delete, stop.
+                                                */
+                                               break;
+                                       }
+                                       
+                                       /*
+                                        *      Copy substring into buffer.
+                                        */
+                                       memcpy(buffer,
+                                              auth_item->vp_strvalue + rxmatch[i].rm_so,
+                                              rxmatch[i].rm_eo - rxmatch[i].rm_so);
+                                       buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
+
+                                       /*
+                                        *      Copy substring, and add it to
+                                        *      the request.
+                                        *
+                                        *      Note that we don't check
+                                        *      for out of memory, which is
+                                        *      the only error we can get...
+                                        */
+                                       p = strdup(buffer);
+                                       request_data_add(req,
+                                                        req,
+                                                        REQUEST_DATA_REGEX | i,
+                                                        p, free);
+                               }
+                       }                               
                                if (compare != 0) result = -1;
                                break;
 
                        case T_OP_REG_NE:
-                               regcomp(&reg, (char *)check_item->strvalue, REG_EXTENDED);
-                               compare = regexec(&reg, (char *)auth_item->strvalue,
+                               if ((auth_item->type == PW_TYPE_IPADDR) &&
+                                   (auth_item->vp_strvalue[0] == '\0')) {
+                                 inet_ntop(AF_INET, &(auth_item->lvalue),
+                                           auth_item->vp_strvalue,
+                                           sizeof(auth_item->vp_strvalue));
+                               }
+
+                               regcomp(&reg, (char *)check_item->vp_strvalue, REG_EXTENDED|REG_NOSUB);
+                               compare = regexec(&reg, (char *)auth_item->vp_strvalue,
                                                0, NULL, 0);
                                regfree(&reg);
                                if (compare == 0) result = -1;
@@ -363,242 +470,155 @@ int paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **r
 }
 
 /*
- *      Compare two attributes simply.  Calls paircompare.
+ *      Compare two attributes simply.  Calls compare_pair.
  */
 
 int simplepaircmp(REQUEST *req, VALUE_PAIR *first, VALUE_PAIR *second)
 {
-       return paircompare( req, first, second, NULL, NULL );
+       return compare_pair( req, first, second, NULL, NULL );
 }
 
 
 /*
- *     Compare a Connect-Info and a Connect-Rate
+ *     Move pairs, replacing/over-writing them, and doing xlat.
  */
-static int connectcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-               VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
-{
-       int rate;
-
-       instance = instance;
-       check_pairs = check_pairs; /* shut the compiler up */
-       reply_pairs = reply_pairs;
-
-       rate = atoi((char *)request->strvalue);
-       return rate - check->lvalue;
-}
-
-
 /*
- *     Compare a portno with a range.
+ *     Move attributes from one list to the other
+ *     if not already present.
  */
-static int portcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-       VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
+void pairxlatmove(REQUEST *req, VALUE_PAIR **to, VALUE_PAIR **from)
 {
-       char buf[MAX_STRING_LEN];
-       char *s, *p;
-       uint32_t lo, hi;
-       uint32_t port = request->lvalue;
-
-       instance = instance;
-       check_pairs = check_pairs; /* shut the compiler up */
-       reply_pairs = reply_pairs;
-
-       if ((strchr((char *)check->strvalue, ',') == NULL) &&
-                       (strchr((char *)check->strvalue, '-') == NULL)) {
-               return (request->lvalue - check->lvalue);
+       VALUE_PAIR **tailto, *i, *j, *next;
+       VALUE_PAIR *tailfrom = NULL;
+       VALUE_PAIR *found;
+
+       /*
+        *      Point "tailto" to the end of the "to" list.
+        */
+       tailto = to;
+       for(i = *to; i; i = i->next) {
+               tailto = &i->next;
        }
 
-       /* Same size */
-       strcpy(buf, (char *)check->strvalue);
-       s = strtok(buf, ",");
+       /*
+        *      Loop over the "from" list.
+        */
+       for(i = *from; i; i = next) {
+               next = i->next;
 
-       while (s != NULL) {
-               if ((p = strchr(s, '-')) != NULL)
-                       p++;
-               else
-                       p = s;
-               lo = strtoul(s, NULL, 10);
-               hi = strtoul(p, NULL, 10);
-               if (lo <= port && port <= hi) {
-                       return 0;
+               /*
+                *      Don't move 'fallthrough' over.
+                */
+               if (i->attribute == PW_FALL_THROUGH) {
+                       continue;
                }
-               s = strtok(NULL, ",");
-       } 
-
-       return -1;
-}
-
-/*
- *     Compare prefix/suffix.
- *
- *     If they compare: 
- *     - if PW_STRIP_USER_NAME is present in check_pairs,
- *       strip the username of prefix/suffix.
- *     - if PW_STRIP_USER_NAME is not present in check_pairs,
- *       add a PW_STRIPPED_USER_NAME to the request.
- */
-static int presufcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-       VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
-{
-       VALUE_PAIR *vp;
-       char *name = (char *)request->strvalue;
-       char rest[MAX_STRING_LEN];
-       int len, namelen;
-       int ret = -1;
-       
-       instance = instance;
-       reply_pairs = reply_pairs; /* shut the compiler up */
-
-#if 0 /* DEBUG */
-       printf("Comparing %s and %s, check->attr is %d\n",
-               name, check->strvalue, check->attribute);
-#endif
-
-       len = strlen((char *)check->strvalue);
-       switch (check->attribute) {
-               case PW_PREFIX:
-                       ret = strncmp(name, (char *)check->strvalue, len);
-                       if (ret == 0 && rest)
-                               strcpy(rest, name + len);
-                       break;
-               case PW_SUFFIX:
-                       namelen = strlen(name);
-                       if (namelen < len)
-                               break;
-                       ret = strcmp(name + namelen - len,
-                                       (char *)check->strvalue);
-                       if (ret == 0 && rest) {
-                               strncpy(rest, name, namelen - len);
-                               rest[namelen - len] = 0;
-                       }
-                       break;
-       }
-       if (ret != 0)
-               return ret;
 
-       if (pairfind(check_pairs, PW_STRIP_USER_NAME)) {
                /*
-                *      I don't think we want to update the User-Name
-                *      attribute in place... - atd
+                *      We've got to xlat the string before moving
+                *      it over.
                 */
-               strcpy((char *)request->strvalue, rest);
-               request->length = strlen(rest);
-       } else {
-               if ((vp = pairfind(check_pairs, PW_STRIPPED_USER_NAME)) != NULL){
-                       strcpy((char *)vp->strvalue, rest);
-                       vp->length = strlen(rest);
-               } else if ((vp = paircreate(PW_STRIPPED_USER_NAME,
-                               PW_TYPE_STRING)) != NULL) {
-                       strcpy((char *)vp->strvalue, rest);
-                       vp->length = strlen(rest);
-                       pairadd(&request, vp);
-               } /* else no memory! Die, die!: FIXME!! */
-       }
-
-       return ret;
-}
-
-
-/*
- *     Compare the current time to a range.
- *     Hmm... it would save work, and probably be better,
- *     if we were passed the REQUEST data structure, so we
- *     could use it's 'timestamp' element.  That way, we could
- *     do the comparison against when the packet came in, not now,
- *     and have one less system call to do.
- */
-static int timecmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-       VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
-{
-       instance = instance;
-       request = request;      /* shut the compiler up */
-       check_pairs = check_pairs;
-       reply_pairs = reply_pairs;
-
-       if (timestr_match((char *)check->strvalue, time(NULL)) >= 0) {
-               return 0;
-       }
-       return -1;
-}
-
-/*
- *     Matches if there is NO SUCH ATTRIBUTE as the one named
- *     in check->strvalue.  If there IS such an attribute, it
- *     doesn't match.
- *
- *     This is ugly, and definitely non-optimal.  We should be
- *     doing the lookup only ONCE, and storing the result
- *     in check->lvalue...
- */
-static int attrcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-       VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
-{
-       VALUE_PAIR *pair;
-       DICT_ATTR  *dict;
-       int attr;
+               if (i->flags.do_xlat) {
+                       int rcode;
+                       char buffer[sizeof(i->vp_strvalue)];
 
-       instance = instance;
-       check_pairs = check_pairs; /* shut the compiler up */
-       reply_pairs = reply_pairs;
+                       i->flags.do_xlat = 0;
+                       rcode = radius_xlat(buffer, sizeof(buffer),
+                                           i->vp_strvalue,
+                                           req, NULL);
 
-       if (check->lvalue == 0) {
-               dict = dict_attrbyname((char *)check->strvalue);
-               if (dict == NULL) {
-                       return -1;
+                       /*
+                        *      Parse the string into a new value.
+                        */
+                       pairparsevalue(i, buffer);
                }
-               attr = dict->attr;
-       } else {
-               attr = check->lvalue;
-       }
 
-       /*
-        *      If there's no such attribute, then return MATCH,
-        *      else FAILURE.
-        */
-       pair = pairfind(request, attr);
-       if (pair == NULL) {
-               return 0;
-       }
+               found = pairfind(*to, i->attribute);
+               switch (i->operator) {
 
-       return -1;
-}
+                       /*
+                        *  If a similar attribute is found,
+                        *  delete it.
+                        */
+               case T_OP_SUB:          /* -= */
+                       if (found) {
+                               if (!i->vp_strvalue[0] ||
+                                   (strcmp((char *)found->vp_strvalue,
+                                           (char *)i->vp_strvalue) == 0)){
+                                       pairdelete(to, found->attribute);
+
+                                       /*
+                                        *      'tailto' may have been
+                                        *      deleted...
+                                        */
+                                       tailto = to;
+                                       for(j = *to; j; j = j->next) {
+                                               tailto = &j->next;
+                                       }
+                               }
+                       }
+                       tailfrom = i;
+                       continue;
+                       break;
 
-/*
- *     Compare the expiration date.
- */
-static int expirecmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
-                    VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
-{
-       time_t now;
+                       /*
+                        *  Add it, if it's not already there.
+                        */
+               case T_OP_EQ:           /* = */
+                       if (found) {
+                               tailfrom = i;
+                               continue; /* with the loop */
+                       }
+                       break;
 
-       instance = instance;
-       request = request;      /* shut the compiler up */
-       check_pairs = check_pairs;
-       reply_pairs = reply_pairs;
+                       /*
+                        *  If a similar attribute is found,
+                        *  replace it with the new one.  Otherwise,
+                        *  add the new one to the list.
+                        */
+               case T_OP_SET:          /* := */
+                       if (found) {
+                               VALUE_PAIR *vp;
 
-       /*
-        *  FIXME!  This should be request->timestamp!
-        */
-       now = time(NULL);
+                               vp = found->next;
+                               memcpy(found, i, sizeof(*found));
+                               found->next = vp;
+                               continue;
+                       }
+                       break;
 
-       if (now <= check->lvalue) {
-               return 0;
-       }
+                       /*
+                        *  FIXME: Add support for <=, >=, <, >
+                        *
+                        *  which will mean (for integers)
+                        *  'make the attribute the smaller, etc'
+                        */
 
-       return +1;
-}
+                       /*
+                        *  Add the new element to the list, even
+                        *  if similar ones already exist.
+                        */
+               default:
+               case T_OP_ADD:          /* += */
+                       break;
+               }
 
-/*
- *     Register server-builtin special attributes.
- */
-void pair_builtincompare_init(void)
-{
-       paircompare_register(PW_NAS_PORT_ID, -1, portcmp, NULL);
-       paircompare_register(PW_PREFIX, PW_USER_NAME, presufcmp, NULL);
-       paircompare_register(PW_SUFFIX, PW_USER_NAME, presufcmp, NULL);
-       paircompare_register(PW_CONNECT_RATE, PW_CONNECT_INFO, connectcmp, NULL);
-       paircompare_register(PW_CURRENT_TIME, 0, timecmp, NULL);
-       paircompare_register(PW_NO_SUCH_ATTRIBUTE, 0, attrcmp, NULL);
-       paircompare_register(PW_EXPIRATION, 0, expirecmp, NULL);
+               if (tailfrom)
+                       tailfrom->next = next;
+               else
+                       *from = next;
+
+               /*
+                *      If ALL of the 'to' attributes have been deleted,
+                *      then ensure that the 'tail' is updated to point
+                *      to the head.
+                */
+               if (!*to) {
+                       tailto = to;
+               }
+               *tailto = i;
+               if (i) {
+                       i->next = NULL;
+                       tailto = &i->next;
+               }
+       } /* loop over the 'from' list */
 }