I got the bug to stop biting me, and even made the code more beautiful in the process.
[citadel.git] / citadel / internet_addressing.c
index 68daf88542c6e11b0dd974de5a195c91605a2045..72b52b6808e20b17f35986ffe7743986f05aa2c3 100644 (file)
@@ -44,8 +44,8 @@
 #ifdef HAVE_ICONV
 #include <iconv.h>
 
-#if 0
 /* This is the non-define version in case it is needed for debugging */
+#if 0
 inline void FindNextEnd (char *bptr, char *end)
 {
        /* Find the next ?Q? */
@@ -172,9 +172,7 @@ void utf8ify_rfc822_string(char *buf) {
                end = nextend;
        }
 
-       /* Now we handle foreign character sets properly encoded
-        * in RFC2047 format.
-        */
+       // Now we handle foreign character sets properly encoded in RFC2047 format.
        start = strstr(buf, "=?");
        FindNextEnd((start != NULL)? start : buf, end);
        while (start != NULL && end != NULL && end > start) {
@@ -262,11 +260,6 @@ inline void utf8ify_rfc822_string(char *a){};
 #endif
 
 
-struct trynamebuf {
-       char buffer1[SIZ];
-       char buffer2[SIZ];
-};
-
 char *inetcfg = NULL;
 
 /*
@@ -320,8 +313,7 @@ int CtdlHostAlias(char *fqdn) {
 /*
  * Determine whether a given Internet address belongs to the current user
  */
-int CtdlIsMe(char *addr, int addr_buf_len)
-{
+int CtdlIsMe(char *addr, int addr_buf_len) {
        struct recptypes *recp;
        int i;
 
@@ -390,18 +382,46 @@ void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
 }
 
 
-/*
- * Aliasing for network mail.
- */
-int alias(char *name) {                                /* process alias and routing info for mail */
+// values that can be returned by expand_aliases()
+enum {
+       EA_ERROR,               // Can't send message due to bad address
+       EA_MULTIPLE,            // Alias expanded into multiple recipients -- run me again!
+       EA_LOCAL,               // Local message, do no network processing
+       EA_INTERNET             // Convert msg and send as Internet mail
+};
+
+
+// Process alias and routing info for email addresses
+int expand_aliases(char *name, char *aliases) {
        int a;
        char aaa[SIZ];
        int at = 0;
-       char node[64];
 
-       char original_name[256];
+       if (aliases) {
+               int num_aliases = num_tokens(aliases, '\n');
+               for (a=0; a<num_aliases; ++a) {
+                       extract_token(aaa, aliases, a, '\n', sizeof aaa);
+                       char *bar = strchr(aaa, '|');
+                       if (bar) {
+                               bar[0] = 0;
+                               ++bar;
+                               striplt(aaa);
+                               striplt(bar);
+                               if ( (!IsEmptyStr(aaa)) && (!strcasecmp(name, aaa)) ) {
+                                       syslog(LOG_DEBUG, "internet_addressing: global alias <%s> to <%s>", name, bar);
+                                       strcpy(name, bar);
+                               }
+                       }
+               }
+               if (strchr(name, ',')) {
+                       return(EA_MULTIPLE);
+               }
+       }
+
+       char original_name[256];                                // Now go for the regular aliases
        safestrncpy(original_name, name, sizeof original_name);
 
+       // should these checks still be here, or maybe move them to split_recps() ?
        striplt(name);
        remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
        stripallbut(name, '<', '>');
@@ -412,7 +432,7 @@ int alias(char *name) {                             /* process alias and routing info for mail */
        }
 
        if (strcasecmp(original_name, name)) {
-               syslog(LOG_INFO, "internet_addressing: %s is being forwarded to %s", original_name, name);
+               syslog(LOG_INFO, "internet_addressing: directory alias <%s> to <%s>", original_name, name);
        }
 
        /* Change "user @ xxx" to "user" if xxx is an alias for this host */
@@ -420,68 +440,110 @@ int alias(char *name) {                          /* process alias and routing info for mail */
                if (name[a] == '@') {
                        if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
                                name[a] = 0;
-                               syslog(LOG_DEBUG, "internet_addressing: changed to <%s>", name);
+                               syslog(LOG_DEBUG, "internet_addressing: host is local, recipient is <%s>", name);
                                break;
                        }
                }
        }
 
-       /* determine local or remote type, see citadel.h */
+       /* Is this a local or remote recipient? */
        at = haschar(name, '@');
-       if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
-       if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
-       remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
+       if (at == 0) {
+               return(EA_LOCAL);                       /* no @'s = local address */
+       }
+       else if (at == 1) {
+               return(EA_INTERNET);                    /* one @ = internet address */
+       }
+       else {
+               return(EA_ERROR);                       /* more than one @ = badly formed address */
+       }
+}
 
-       /* figure out the delivery mode */
-       extract_token(node, name, 1, '@', sizeof node);
 
-       /* If there are one or more dots in the nodename, we assume that it
-        * is an FQDN and will attempt SMTP delivery to the Internet.
-        */
-       if (haschar(node, '.') > 0) {
-               return(MES_INTERNET);
+// Return a supplied list of email addresses as an array, removing superfluous information and syntax.
+// If an existing Array is supplied as "append_to" it will do so; otherwise a new Array is allocated.
+Array *split_recps(char *addresses, Array *append_to) {
+
+       if (IsEmptyStr(addresses)) {            // nothing supplied, nothing returned
+               return(NULL);
+       }
+
+       // Copy the supplied address list into our own memory space, because we are going to modify it.
+       char *a = strdup(addresses);
+       if (a == NULL) {
+               syslog(LOG_ERR, "internet_addressing: malloc() failed: %m");
+               return(NULL);
        }
 
-       /* If we get to this point it's an invalid node name */
-       return (MES_ERROR);
+       // Strip out anything in double quotes
+       char *l = NULL;
+       char *r = NULL;
+       do {
+               l = strchr(a, '\"');
+               r = strrchr(a, '\"');
+               if (r > l) {
+                       strcpy(l, r+1);
+               }
+       } while (r > l);
+
+       // Transform all qualifying delimiters to commas
+       for (char *t=a; t[0]; ++t) {
+               if ((t[0]==';') || (t[0]=='|')) {
+                       t[0]=',';
+               }
+       }
+
+       // Tokenize the recipients into an array.  No single recipient should be larger than 256 bytes.
+       Array *recipients_array = NULL;
+       if (append_to) {
+               recipients_array = append_to;                   // Append to an existing array of recipients
+       }
+       else {
+               recipients_array = array_new(256);              // This is a new array of recipients
+       }
+
+       int num_addresses = num_tokens(a, ',');
+       for (int i=0; i<num_addresses; ++i) {
+               char this_address[256];
+               extract_token(this_address, a, i, ',', sizeof this_address);
+               striplt(this_address);                          // strip leading and trailing whitespace
+               stripout(this_address, '(', ')');               // remove any portion in parentheses
+               stripallbut(this_address, '<', '>');            // if angle brackets are present, keep only what is inside them
+               if (!IsEmptyStr(this_address)) {
+                       array_append(recipients_array, this_address);
+               }
+       }
+
+       free(a);                                                // We don't need this buffer anymore.
+       return(recipients_array);                               // Return the completed array to the caller.
 }
 
 
-/*
- * Validate recipients, count delivery types and errors, and handle aliasing
- * FIXME check for dupes!!!!!
- *
- * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
- * were specified, or the number of addresses found invalid.
- *
- * Caller needs to free the result using free_recipients()
- */
-struct recptypes *validate_recipients(const char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
-       struct CitContext *CCC = CC;
+// Validate recipients, count delivery types and errors, and handle aliasing
+// FIXME check for dupes!!!!!
+//
+// Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
+// were specified, or the number of addresses found invalid.
+//
+// Caller needs to free the result using free_recipients()
+//
+struct recptypes *validate_recipients(char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
        struct recptypes *ret;
        char *recipients = NULL;
-       char *org_recp;
-       char this_recp[256];
-       char this_recp_cooked[256];
        char append[SIZ];
        long len;
-       int num_recps = 0;
-       int i, j;
        int mailtype;
        int invalid;
        struct ctdluser tempUS;
-       struct ctdlroom tempQR;
-       struct ctdlroom tempQR2;
+       struct ctdlroom original_room;
        int err = 0;
        char errmsg[SIZ];
-       int in_quotes = 0;
+       char *org_recp;
+       char this_recp[256];
 
-       /* Initialize */
-       ret = (struct recptypes *) malloc(sizeof(struct recptypes));
+       ret = (struct recptypes *) malloc(sizeof(struct recptypes));                    // Initialize
        if (ret == NULL) return(NULL);
-
-       /* Set all strings to null and numeric values to zero */
-       memset(ret, 0, sizeof(struct recptypes));
+       memset(ret, 0, sizeof(struct recptypes));                                       // set all values to null/zero
 
        if (supplied_recipients == NULL) {
                recipients = strdup("");
@@ -490,18 +552,13 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
                recipients = strdup(supplied_recipients);
        }
 
-       /* Allocate some memory.  Yes, this allocates 500% more memory than we will
-        * actually need, but it's healthier for the heap than doing lots of tiny
-        * realloc() calls instead.
-        */
-       len = strlen(recipients) + 1024;
+       len = strlen(recipients) + 1024;                                                // allocate memory
        ret->errormsg = malloc(len);
        ret->recp_local = malloc(len);
        ret->recp_internet = malloc(len);
        ret->recp_room = malloc(len);
        ret->display_recp = malloc(len);
        ret->recp_orgroom = malloc(len);
-       org_recp = malloc(len);
 
        ret->errormsg[0] = 0;
        ret->recp_local[0] = 0;
@@ -509,58 +566,30 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
        ret->recp_room[0] = 0;
        ret->recp_orgroom[0] = 0;
        ret->display_recp[0] = 0;
-
        ret->recptypes_magic = RECPTYPES_MAGIC;
 
-       /* Change all valid separator characters to commas */
-       for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
-               if ((recipients[i] == ';') || (recipients[i] == '|')) {
-                       recipients[i] = ',';
-               }
-       }
+       Array *recp_array = split_recps(supplied_recipients, NULL);
 
-       /* Now start extracting recipients... */
+       char *aliases = CtdlGetSysConfig(GLOBAL_ALIASES);                               // First hit the Global Alias Table
 
-       while (!IsEmptyStr(recipients)) {
-               for (i=0; i<=strlen(recipients); ++i) {
-                       if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
-                       if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
-                               safestrncpy(this_recp, recipients, i+1);
-                               this_recp[i] = 0;
-                               if (recipients[i] == ',') {
-                                       strcpy(recipients, &recipients[i+1]);
-                               }
-                               else {
-                                       strcpy(recipients, "");
-                               }
-                               break;
-                       }
-               }
+       for (int r=0; (recp_array && r<array_len(recp_array)); ++r) {
+               org_recp = (char *)array_get_element_at(recp_array, r);
+               strncpy(this_recp, org_recp, sizeof this_recp);
+               mailtype = expand_aliases(this_recp, aliases);
+               syslog(LOG_DEBUG, "Recipient #%d of type %d is <%s>", r, mailtype, this_recp);
 
-               striplt(this_recp);
-               if (IsEmptyStr(this_recp))
-                       break;
-               syslog(LOG_DEBUG, "internet_addressing: evaluating recipient #%d: %s", num_recps, this_recp);
-               ++num_recps;
-
-               strcpy(org_recp, this_recp);
-               alias(this_recp);
-               alias(this_recp);
-               mailtype = alias(this_recp);
-
-               for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
-                       if (this_recp[j]=='_') {
-                               this_recp_cooked[j] = ' ';
-                       }
-                       else {
-                               this_recp_cooked[j] = this_recp[j];
-                       }
+               // If an alias expanded to multiple recipients, strip off those recipients and append them
+               // to the end of the array.  This loop will hit those again when it gets there.
+               if (mailtype == EA_MULTIPLE) {
+                       recp_array = split_recps(this_recp, recp_array);
                }
-               this_recp_cooked[j] = '\0';
+
                invalid = 0;
                errmsg[0] = 0;
                switch(mailtype) {
-               case MES_LOCAL:
+               case EA_LOCAL:                                  // There are several types of "local" recipients.
+
+                       // Old BBS conventions require mail to "sysop" to go somewhere.  Send it to the admin room.
                        if (!strcasecmp(this_recp, "sysop")) {
                                ++ret->num_room;
                                strcpy(this_recp, CtdlGetConfigStr("c_aideroom"));
@@ -569,42 +598,55 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
                                }
                                strcat(ret->recp_room, this_recp);
                        }
-                       else if ( (!strncasecmp(this_recp, "room_", 5)) && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
 
-                               /* Save room so we can restore it later */
-                               tempQR2 = CCC->room;
-                               CCC->room = tempQR;
-                                       
-                               /* Check permissions to send mail to this room */
-                               err = CtdlDoIHavePermissionToPostInThisRoom(
-                                       errmsg, 
-                                       sizeof errmsg, 
-                                       RemoteIdentifier,
-                                       Flags,
-                                       0                       /* 0 = not a reply */
+                       // This handles rooms which can receive posts via email.
+                       else if (!strncasecmp(this_recp, "room_", 5)) {
+                               original_room = CC->room;                               // Remember where we parked
+
+                               char mail_to_room[ROOMNAMELEN];
+                               char *m;
+                               strncpy(mail_to_room, &this_recp[5], sizeof mail_to_room);
+                               for (m = mail_to_room; *m; ++m) {
+                                       if (m[0] == '_') m[0]=' ';
+                               }
+                               if (!CtdlGetRoom(&CC->room, mail_to_room)) {            // Find the room they asked for
+
+                                       err = CtdlDoIHavePermissionToPostInThisRoom(    // check for write permissions to room
+                                               errmsg, 
+                                               sizeof errmsg, 
+                                               RemoteIdentifier,
+                                               Flags,
+                                               0                                       // 0 means "this is not a reply"
                                        );
-                               if (err) {
+                                       if (err) {
+                                               ++ret->num_error;
+                                               invalid = 1;
+                                       } 
+                                       else {
+                                               ++ret->num_room;
+                                               if (!IsEmptyStr(ret->recp_room)) {
+                                                       strcat(ret->recp_room, "|");
+                                               }
+                                               strcat(ret->recp_room, &this_recp[5]);
+       
+                                               if (!IsEmptyStr(ret->recp_orgroom)) {
+                                                       strcat(ret->recp_orgroom, "|");
+                                               }
+                                               strcat(ret->recp_orgroom, org_recp);
+       
+                                       }
+                               }
+                               else {                                                  // no such room exists
                                        ++ret->num_error;
                                        invalid = 1;
-                               } 
-                               else {
-                                       ++ret->num_room;
-                                       if (!IsEmptyStr(ret->recp_room)) {
-                                               strcat(ret->recp_room, "|");
-                                       }
-                                       strcat(ret->recp_room, &this_recp_cooked[5]);
-
-                                       if (!IsEmptyStr(ret->recp_orgroom)) {
-                                               strcat(ret->recp_orgroom, "|");
-                                       }
-                                       strcat(ret->recp_orgroom, org_recp);
-
                                }
-                                       
-                               /* Restore room in case something needs it */
-                               CCC->room = tempQR2;
+                                               
+                               // Restore this session's original room location.
+                               CC->room = original_room;
 
                        }
+
+                       // This handles the most common case, which is mail to a user's inbox.
                        else if (CtdlGetUser(&tempUS, this_recp) == 0) {
                                ++ret->num_local;
                                strcpy(this_recp, tempUS.fullname);
@@ -613,26 +655,17 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
                                }
                                strcat(ret->recp_local, this_recp);
                        }
-                       else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
-                               ++ret->num_local;
-                               strcpy(this_recp, tempUS.fullname);
-                               if (!IsEmptyStr(ret->recp_local)) {
-                                       strcat(ret->recp_local, "|");
-                               }
-                               strcat(ret->recp_local, this_recp);
-                       }
+
+                       // No match for this recipient
                        else {
                                ++ret->num_error;
                                invalid = 1;
                        }
                        break;
-               case MES_INTERNET:
-                       /* Yes, you're reading this correctly: if the target
-                        * domain points back to the local system,
-                        * the address is invalid.  That's
-                        * because if the address were valid, we would have
-                        * already translated it to a local address by now.
-                        */
+               case EA_INTERNET:
+                       // Yes, you're reading this correctly: if the target domain points back to the local system,
+                       // the address is invalid.  That's because if the address were valid, we would have
+                       // already translated it to a local address by now.
                        if (IsDirectory(this_recp, 0)) {
                                ++ret->num_error;
                                invalid = 1;
@@ -645,7 +678,10 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
                                strcat(ret->recp_internet, this_recp);
                        }
                        break;
-               case MES_ERROR:
+               case EA_MULTIPLE:
+                       // just skip the multiple here because we've already expanded it
+                       break;
+               case EA_ERROR:
                        ++ret->num_error;
                        invalid = 1;
                        break;
@@ -676,7 +712,10 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
                        }
                }
        }
-       free(org_recp);
+
+       if (aliases != NULL) {          // ok, we're done with the global alias list now
+               free(aliases);
+       }
 
        if ( (ret->num_local + ret->num_internet + ret->num_room + ret->num_error) == 0) {
                ret->num_error = (-1);
@@ -688,6 +727,10 @@ struct recptypes *validate_recipients(const char *supplied_recipients, const cha
        );
 
        free(recipients);
+       if (recp_array) {
+               array_free(recp_array);
+       }
+
        return(ret);
 }