"Fossies" - the Fresh Open Source Software Archive

Member "citadel/internet_addressing.c" (5 Jun 2021, 42915 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "internet_addressing.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

    1 /*
    2  * This file contains functions which handle the mapping of Internet addresses
    3  * to users on the Citadel system.
    4  *
    5  * Copyright (c) 1987-2021 by the citadel.org team
    6  *
    7  * This program is open source software; you can redistribute it and/or modify
    8  * it under the terms of the GNU General Public License version 3.
    9  *
   10  * This program is distributed in the hope that it will be useful,
   11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13  * GNU General Public License for more details.
   14  */
   15 
   16 #include "sysdep.h"
   17 #include <stdlib.h>
   18 #include <unistd.h>
   19 #include <stdio.h>
   20 #include <fcntl.h>
   21 #include <ctype.h>
   22 #include <signal.h>
   23 #include <pwd.h>
   24 #include <errno.h>
   25 #include <sys/types.h>
   26 #include <time.h>
   27 #include <sys/wait.h>
   28 #include <string.h>
   29 #include <limits.h>
   30 #include <libcitadel.h>
   31 #include "citadel.h"
   32 #include "server.h"
   33 #include "sysdep_decls.h"
   34 #include "citserver.h"
   35 #include "support.h"
   36 #include "config.h"
   37 #include "msgbase.h"
   38 #include "internet_addressing.h"
   39 #include "user_ops.h"
   40 #include "room_ops.h"
   41 #include "parsedate.h"
   42 #include "database.h"
   43 #include "ctdl_module.h"
   44 #ifdef HAVE_ICONV
   45 #include <iconv.h>
   46 
   47 #if 0
   48 /* This is the non-define version in case it is needed for debugging */
   49 inline void FindNextEnd (char *bptr, char *end)
   50 {
   51     /* Find the next ?Q? */
   52     end = strchr(bptr + 2, '?');
   53     if (end == NULL) return NULL;
   54     if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && 
   55         (*(end + 2) == '?')) {
   56         /* skip on to the end of the cluster, the next ?= */
   57         end = strstr(end + 3, "?=");
   58     }
   59     else
   60         /* sort of half valid encoding, try to find an end. */
   61         end = strstr(bptr, "?=");
   62 }
   63 #endif
   64 
   65 #define FindNextEnd(bptr, end) { \
   66     end = strchr(bptr + 2, '?'); \
   67     if (end != NULL) { \
   68         if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && (*(end + 2) == '?')) { \
   69             end = strstr(end + 3, "?="); \
   70         } else end = strstr(bptr, "?="); \
   71     } \
   72 }
   73 
   74 /*
   75  * Handle subjects with RFC2047 encoding such as:
   76  * =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
   77  */
   78 void utf8ify_rfc822_string(char *buf) {
   79     char *start, *end, *next, *nextend, *ptr;
   80     char newbuf[1024];
   81     char charset[128];
   82     char encoding[16];
   83     char istr[1024];
   84     iconv_t ic = (iconv_t)(-1) ;
   85     char *ibuf;         // Buffer of characters to be converted
   86     char *obuf;         // Buffer for converted characters
   87     size_t ibuflen;         // Length of input buffer
   88     size_t obuflen;         // Length of output buffer
   89     char *isav;         // Saved pointer to input buffer
   90     char *osav;         // Saved pointer to output buffer
   91     int passes = 0;
   92     int i, len, delta;
   93     int illegal_non_rfc2047_encoding = 0;
   94 
   95     /* Sometimes, badly formed messages contain strings which were simply
   96      * written out directly in some foreign character set instead of
   97      * using RFC2047 encoding.  This is illegal but we will attempt to
   98      * handle it anyway by converting from a user-specified default
   99      * charset to UTF-8 if we see any nonprintable characters.
  100      */
  101     len = strlen(buf);
  102     for (i=0; i<len; ++i) {
  103         if ((buf[i] < 32) || (buf[i] > 126)) {
  104             illegal_non_rfc2047_encoding = 1;
  105             i = len;    // take a shortcut, it won't be more than one.
  106         }
  107     }
  108     if (illegal_non_rfc2047_encoding) {
  109         const char *default_header_charset = "iso-8859-1";
  110         if ( (strcasecmp(default_header_charset, "UTF-8")) && (strcasecmp(default_header_charset, "us-ascii")) ) {
  111             ctdl_iconv_open("UTF-8", default_header_charset, &ic);
  112             if (ic != (iconv_t)(-1) ) {
  113                 ibuf = malloc(1024);
  114                 isav = ibuf;
  115                 safestrncpy(ibuf, buf, 1024);
  116                 ibuflen = strlen(ibuf);
  117                 obuflen = 1024;
  118                 obuf = (char *) malloc(obuflen);
  119                 osav = obuf;
  120                 iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
  121                 osav[1024-obuflen] = 0;
  122                 strcpy(buf, osav);
  123                 free(osav);
  124                 iconv_close(ic);
  125                 free(isav);
  126             }
  127         }
  128     }
  129 
  130     /* pre evaluate the first pair */
  131     nextend = end = NULL;
  132     len = strlen(buf);
  133     start = strstr(buf, "=?");
  134     if (start != NULL) 
  135         FindNextEnd (start, end);
  136 
  137     while ((start != NULL) && (end != NULL)) {
  138         next = strstr(end, "=?");
  139         if (next != NULL)
  140             FindNextEnd(next, nextend);
  141         if (nextend == NULL)
  142             next = NULL;
  143 
  144         /* did we find two partitions */
  145         if ((next != NULL) && ((next - end) > 2)) {
  146             ptr = end + 2;
  147             while ((ptr < next) && 
  148                    (isspace(*ptr) ||
  149                 (*ptr == '\r') ||
  150                 (*ptr == '\n') || 
  151                 (*ptr == '\t')))
  152                 ptr ++;
  153             /* did we find a gab just filled with blanks? */
  154             if (ptr == next)
  155             {
  156                 memmove (end + 2,
  157                      next,
  158                      len - (next - start));
  159 
  160                 /* now terminate the gab at the end */
  161                 delta = (next - end) - 2;
  162                 len -= delta;
  163                 buf[len] = '\0';
  164 
  165                 /* move next to its new location. */
  166                 next -= delta;
  167                 nextend -= delta;
  168             }
  169         }
  170         /* our next-pair is our new first pair now. */
  171         start = next;
  172         end = nextend;
  173     }
  174 
  175     /* Now we handle foreign character sets properly encoded
  176      * in RFC2047 format.
  177      */
  178     start = strstr(buf, "=?");
  179     FindNextEnd((start != NULL)? start : buf, end);
  180     while (start != NULL && end != NULL && end > start) {
  181         extract_token(charset, start, 1, '?', sizeof charset);
  182         extract_token(encoding, start, 2, '?', sizeof encoding);
  183         extract_token(istr, start, 3, '?', sizeof istr);
  184 
  185         ibuf = malloc(1024);
  186         isav = ibuf;
  187         if (!strcasecmp(encoding, "B")) {   // base64
  188             ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
  189         }
  190         else if (!strcasecmp(encoding, "Q")) {  // quoted-printable
  191             size_t len;
  192             unsigned long pos;
  193             
  194             len = strlen(istr);
  195             pos = 0;
  196             while (pos < len) {
  197                 if (istr[pos] == '_') istr[pos] = ' ';
  198                 pos++;
  199             }
  200             ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, len);
  201         }
  202         else {
  203             strcpy(ibuf, istr);     // unknown encoding
  204             ibuflen = strlen(istr);
  205         }
  206 
  207         ctdl_iconv_open("UTF-8", charset, &ic);
  208         if (ic != (iconv_t)(-1) ) {
  209             obuflen = 1024;
  210             obuf = (char *) malloc(obuflen);
  211             osav = obuf;
  212             iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
  213             osav[1024-obuflen] = 0;
  214 
  215             end = start;
  216             end++;
  217             strcpy(start, "");
  218             remove_token(end, 0, '?');
  219             remove_token(end, 0, '?');
  220             remove_token(end, 0, '?');
  221             remove_token(end, 0, '?');
  222             strcpy(end, &end[1]);
  223 
  224             snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
  225             strcpy(buf, newbuf);
  226             free(osav);
  227             iconv_close(ic);
  228         }
  229         else {
  230             end = start;
  231             end++;
  232             strcpy(start, "");
  233             remove_token(end, 0, '?');
  234             remove_token(end, 0, '?');
  235             remove_token(end, 0, '?');
  236             remove_token(end, 0, '?');
  237             strcpy(end, &end[1]);
  238 
  239             snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
  240             strcpy(buf, newbuf);
  241         }
  242 
  243         free(isav);
  244 
  245         /*
  246          * Since spammers will go to all sorts of absurd lengths to get their
  247          * messages through, there are LOTS of corrupt headers out there.
  248          * So, prevent a really badly formed RFC2047 header from throwing
  249          * this function into an infinite loop.
  250          */
  251         ++passes;
  252         if (passes > 20) return;
  253 
  254         start = strstr(buf, "=?");
  255         FindNextEnd((start != NULL)? start : buf, end);
  256     }
  257 
  258 }
  259 #else
  260 inline void utf8ify_rfc822_string(char *a){};
  261 
  262 #endif
  263 
  264 
  265 struct trynamebuf {
  266     char buffer1[SIZ];
  267     char buffer2[SIZ];
  268 };
  269 
  270 char *inetcfg = NULL;
  271 
  272 /*
  273  * Return nonzero if the supplied name is an alias for this host.
  274  */
  275 int CtdlHostAlias(char *fqdn) {
  276     int config_lines;
  277     int i;
  278     char buf[256];
  279     char host[256], type[256];
  280     int found = 0;
  281 
  282     if (fqdn == NULL)                   return(hostalias_nomatch);
  283     if (IsEmptyStr(fqdn))                   return(hostalias_nomatch);
  284     if (!strcasecmp(fqdn, "localhost"))         return(hostalias_localhost);
  285     if (!strcasecmp(fqdn, CtdlGetConfigStr("c_fqdn")))  return(hostalias_localhost);
  286     if (!strcasecmp(fqdn, CtdlGetConfigStr("c_nodename")))  return(hostalias_localhost);
  287     if (inetcfg == NULL)                    return(hostalias_nomatch);
  288 
  289     config_lines = num_tokens(inetcfg, '\n');
  290     for (i=0; i<config_lines; ++i) {
  291         extract_token(buf, inetcfg, i, '\n', sizeof buf);
  292         extract_token(host, buf, 0, '|', sizeof host);
  293         extract_token(type, buf, 1, '|', sizeof type);
  294 
  295         found = 0;
  296 
  297         /* Process these in a specific order, in case there are multiple matches.
  298          * We want localhost to override masq, for example.
  299          */
  300 
  301         if ( (!strcasecmp(type, "masqdomain")) && (!strcasecmp(fqdn, host))) {
  302             found = hostalias_masq;
  303         }
  304 
  305         if ( (!strcasecmp(type, "localhost")) && (!strcasecmp(fqdn, host))) {
  306             found = hostalias_localhost;
  307         }
  308 
  309         // "directory" used to be a distributed version of "localhost" but they're both the same now
  310         if ( (!strcasecmp(type, "directory")) && (!strcasecmp(fqdn, host))) {
  311             found = hostalias_localhost;
  312         }
  313 
  314         if (found) return(found);
  315     }
  316     return(hostalias_nomatch);
  317 }
  318 
  319 
  320 /*
  321  * Determine whether a given Internet address belongs to the current user
  322  */
  323 int CtdlIsMe(char *addr, int addr_buf_len)
  324 {
  325     struct recptypes *recp;
  326     int i;
  327 
  328     recp = validate_recipients(addr, NULL, 0);
  329     if (recp == NULL) return(0);
  330 
  331     if (recp->num_local == 0) {
  332         free_recipients(recp);
  333         return(0);
  334     }
  335 
  336     for (i=0; i<recp->num_local; ++i) {
  337         extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
  338         if (!strcasecmp(addr, CC->user.fullname)) {
  339             free_recipients(recp);
  340             return(1);
  341         }
  342     }
  343 
  344     free_recipients(recp);
  345     return(0);
  346 }
  347 
  348 
  349 /* If the last item in a list of recipients was truncated to a partial address,
  350  * remove it completely in order to avoid choking library functions.
  351  */
  352 void sanitize_truncated_recipient(char *str)
  353 {
  354     if (!str) return;
  355     if (num_tokens(str, ',') < 2) return;
  356 
  357     int len = strlen(str);
  358     if (len < 900) return;
  359     if (len > 998) str[998] = 0;
  360 
  361     char *cptr = strrchr(str, ',');
  362     if (!cptr) return;
  363 
  364     char *lptr = strchr(cptr, '<');
  365     char *rptr = strchr(cptr, '>');
  366 
  367     if ( (lptr) && (rptr) && (rptr > lptr) ) return;
  368 
  369     *cptr = 0;
  370 }
  371 
  372 
  373 /*
  374  * This function is self explanatory.
  375  * (What can I say, I'm in a weird mood today...)
  376  */
  377 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
  378     char *ptr;
  379     if (!name) return;
  380 
  381     for (ptr=name; *ptr; ++ptr) {
  382         while ( (isspace(*ptr)) && (*(ptr+1)=='@') ) {
  383             strcpy(ptr, ptr+1);
  384             if (ptr > name) --ptr;
  385         }
  386         while ( (*ptr=='@') && (*(ptr+1)!=0) && (isspace(*(ptr+1))) ) {
  387             strcpy(ptr+1, ptr+2);
  388         }
  389     }
  390 }
  391 
  392 
  393 /*
  394  * Aliasing for network mail.
  395  */
  396 int alias(char *name) {             /* process alias and routing info for mail */
  397     int a;
  398     char aaa[SIZ];
  399     int at = 0;
  400     char node[64];
  401 
  402     char original_name[256];
  403     safestrncpy(original_name, name, sizeof original_name);
  404 
  405     striplt(name);
  406     remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
  407     stripallbut(name, '<', '>');
  408 
  409     /* Hit the email address directory */
  410     if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
  411         strcpy(name, aaa);
  412     }
  413 
  414     if (strcasecmp(original_name, name)) {
  415         syslog(LOG_INFO, "internet_addressing: %s is being forwarded to %s", original_name, name);
  416     }
  417 
  418     /* Change "user @ xxx" to "user" if xxx is an alias for this host */
  419     for (a=0; name[a] != '\0'; ++a) {
  420         if (name[a] == '@') {
  421             if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
  422                 name[a] = 0;
  423                 syslog(LOG_DEBUG, "internet_addressing: changed to <%s>", name);
  424                 break;
  425             }
  426         }
  427     }
  428 
  429     /* determine local or remote type, see citadel.h */
  430     at = haschar(name, '@');
  431     if (at == 0) return(MES_LOCAL);     /* no @'s - local address */
  432     if (at > 1) return(MES_ERROR);      /* >1 @'s - invalid address */
  433     remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
  434 
  435     /* figure out the delivery mode */
  436     extract_token(node, name, 1, '@', sizeof node);
  437 
  438     /* If there are one or more dots in the nodename, we assume that it
  439      * is an FQDN and will attempt SMTP delivery to the Internet.
  440      */
  441     if (haschar(node, '.') > 0) {
  442         return(MES_INTERNET);
  443     }
  444 
  445     /* If we get to this point it's an invalid node name */
  446     return (MES_ERROR);
  447 }
  448 
  449 
  450 /*
  451  * Validate recipients, count delivery types and errors, and handle aliasing
  452  * FIXME check for dupes!!!!!
  453  *
  454  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
  455  * were specified, or the number of addresses found invalid.
  456  *
  457  * Caller needs to free the result using free_recipients()
  458  */
  459 struct recptypes *validate_recipients(const char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
  460     struct CitContext *CCC = CC;
  461     struct recptypes *ret;
  462     char *recipients = NULL;
  463     char *org_recp;
  464     char this_recp[256];
  465     char this_recp_cooked[256];
  466     char append[SIZ];
  467     long len;
  468     int num_recps = 0;
  469     int i, j;
  470     int mailtype;
  471     int invalid;
  472     struct ctdluser tempUS;
  473     struct ctdlroom tempQR;
  474     struct ctdlroom tempQR2;
  475     int err = 0;
  476     char errmsg[SIZ];
  477     int in_quotes = 0;
  478 
  479     /* Initialize */
  480     ret = (struct recptypes *) malloc(sizeof(struct recptypes));
  481     if (ret == NULL) return(NULL);
  482 
  483     /* Set all strings to null and numeric values to zero */
  484     memset(ret, 0, sizeof(struct recptypes));
  485 
  486     if (supplied_recipients == NULL) {
  487         recipients = strdup("");
  488     }
  489     else {
  490         recipients = strdup(supplied_recipients);
  491     }
  492 
  493     /* Allocate some memory.  Yes, this allocates 500% more memory than we will
  494      * actually need, but it's healthier for the heap than doing lots of tiny
  495      * realloc() calls instead.
  496      */
  497     len = strlen(recipients) + 1024;
  498     ret->errormsg = malloc(len);
  499     ret->recp_local = malloc(len);
  500     ret->recp_internet = malloc(len);
  501     ret->recp_room = malloc(len);
  502     ret->display_recp = malloc(len);
  503     ret->recp_orgroom = malloc(len);
  504     org_recp = malloc(len);
  505 
  506     ret->errormsg[0] = 0;
  507     ret->recp_local[0] = 0;
  508     ret->recp_internet[0] = 0;
  509     ret->recp_room[0] = 0;
  510     ret->recp_orgroom[0] = 0;
  511     ret->display_recp[0] = 0;
  512 
  513     ret->recptypes_magic = RECPTYPES_MAGIC;
  514 
  515     /* Change all valid separator characters to commas */
  516     for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
  517         if ((recipients[i] == ';') || (recipients[i] == '|')) {
  518             recipients[i] = ',';
  519         }
  520     }
  521 
  522     /* Now start extracting recipients... */
  523 
  524     while (!IsEmptyStr(recipients)) {
  525         for (i=0; i<=strlen(recipients); ++i) {
  526             if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
  527             if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
  528                 safestrncpy(this_recp, recipients, i+1);
  529                 this_recp[i] = 0;
  530                 if (recipients[i] == ',') {
  531                     strcpy(recipients, &recipients[i+1]);
  532                 }
  533                 else {
  534                     strcpy(recipients, "");
  535                 }
  536                 break;
  537             }
  538         }
  539 
  540         striplt(this_recp);
  541         if (IsEmptyStr(this_recp))
  542             break;
  543         syslog(LOG_DEBUG, "internet_addressing: evaluating recipient #%d: %s", num_recps, this_recp);
  544         ++num_recps;
  545 
  546         strcpy(org_recp, this_recp);
  547         alias(this_recp);
  548         alias(this_recp);
  549         mailtype = alias(this_recp);
  550 
  551         for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
  552             if (this_recp[j]=='_') {
  553                 this_recp_cooked[j] = ' ';
  554             }
  555             else {
  556                 this_recp_cooked[j] = this_recp[j];
  557             }
  558         }
  559         this_recp_cooked[j] = '\0';
  560         invalid = 0;
  561         errmsg[0] = 0;
  562         switch(mailtype) {
  563         case MES_LOCAL:
  564             if (!strcasecmp(this_recp, "sysop")) {
  565                 ++ret->num_room;
  566                 strcpy(this_recp, CtdlGetConfigStr("c_aideroom"));
  567                 if (!IsEmptyStr(ret->recp_room)) {
  568                     strcat(ret->recp_room, "|");
  569                 }
  570                 strcat(ret->recp_room, this_recp);
  571             }
  572             else if ( (!strncasecmp(this_recp, "room_", 5)) && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
  573 
  574                 /* Save room so we can restore it later */
  575                 tempQR2 = CCC->room;
  576                 CCC->room = tempQR;
  577                     
  578                 /* Check permissions to send mail to this room */
  579                 err = CtdlDoIHavePermissionToPostInThisRoom(
  580                     errmsg, 
  581                     sizeof errmsg, 
  582                     RemoteIdentifier,
  583                     Flags,
  584                     0           /* 0 = not a reply */
  585                     );
  586                 if (err) {
  587                     ++ret->num_error;
  588                     invalid = 1;
  589                 } 
  590                 else {
  591                     ++ret->num_room;
  592                     if (!IsEmptyStr(ret->recp_room)) {
  593                         strcat(ret->recp_room, "|");
  594                     }
  595                     strcat(ret->recp_room, &this_recp_cooked[5]);
  596 
  597                     if (!IsEmptyStr(ret->recp_orgroom)) {
  598                         strcat(ret->recp_orgroom, "|");
  599                     }
  600                     strcat(ret->recp_orgroom, org_recp);
  601 
  602                 }
  603                     
  604                 /* Restore room in case something needs it */
  605                 CCC->room = tempQR2;
  606 
  607             }
  608             else if (CtdlGetUser(&tempUS, this_recp) == 0) {
  609                 ++ret->num_local;
  610                 strcpy(this_recp, tempUS.fullname);
  611                 if (!IsEmptyStr(ret->recp_local)) {
  612                     strcat(ret->recp_local, "|");
  613                 }
  614                 strcat(ret->recp_local, this_recp);
  615             }
  616             else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
  617                 ++ret->num_local;
  618                 strcpy(this_recp, tempUS.fullname);
  619                 if (!IsEmptyStr(ret->recp_local)) {
  620                     strcat(ret->recp_local, "|");
  621                 }
  622                 strcat(ret->recp_local, this_recp);
  623             }
  624             else {
  625                 ++ret->num_error;
  626                 invalid = 1;
  627             }
  628             break;
  629         case MES_INTERNET:
  630             /* Yes, you're reading this correctly: if the target
  631              * domain points back to the local system,
  632              * the address is invalid.  That's
  633              * because if the address were valid, we would have
  634              * already translated it to a local address by now.
  635              */
  636             if (IsDirectory(this_recp, 0)) {
  637                 ++ret->num_error;
  638                 invalid = 1;
  639             }
  640             else {
  641                 ++ret->num_internet;
  642                 if (!IsEmptyStr(ret->recp_internet)) {
  643                     strcat(ret->recp_internet, "|");
  644                 }
  645                 strcat(ret->recp_internet, this_recp);
  646             }
  647             break;
  648         case MES_ERROR:
  649             ++ret->num_error;
  650             invalid = 1;
  651             break;
  652         }
  653         if (invalid) {
  654             if (IsEmptyStr(errmsg)) {
  655                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
  656             }
  657             else {
  658                 snprintf(append, sizeof append, "%s", errmsg);
  659             }
  660             if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
  661                 if (!IsEmptyStr(ret->errormsg)) {
  662                     strcat(ret->errormsg, "; ");
  663                 }
  664                 strcat(ret->errormsg, append);
  665             }
  666         }
  667         else {
  668             if (IsEmptyStr(ret->display_recp)) {
  669                 strcpy(append, this_recp);
  670             }
  671             else {
  672                 snprintf(append, sizeof append, ", %s", this_recp);
  673             }
  674             if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
  675                 strcat(ret->display_recp, append);
  676             }
  677         }
  678     }
  679     free(org_recp);
  680 
  681     if ( (ret->num_local + ret->num_internet + ret->num_room + ret->num_error) == 0) {
  682         ret->num_error = (-1);
  683         strcpy(ret->errormsg, "No recipients specified.");
  684     }
  685 
  686     syslog(LOG_DEBUG, "internet_addressing: validate_recipients() = %d local, %d room, %d SMTP, %d error",
  687         ret->num_local, ret->num_room, ret->num_internet, ret->num_error
  688     );
  689 
  690     free(recipients);
  691     return(ret);
  692 }
  693 
  694 
  695 /*
  696  * Destructor for recptypes
  697  */
  698 void free_recipients(struct recptypes *valid) {
  699 
  700     if (valid == NULL) {
  701         return;
  702     }
  703 
  704     if (valid->recptypes_magic != RECPTYPES_MAGIC) {
  705         syslog(LOG_ERR, "internet_addressing: attempt to call free_recipients() on some other data type!");
  706         abort();
  707     }
  708 
  709     if (valid->errormsg != NULL)        free(valid->errormsg);
  710     if (valid->recp_local != NULL)      free(valid->recp_local);
  711     if (valid->recp_internet != NULL)   free(valid->recp_internet);
  712     if (valid->recp_room != NULL)       free(valid->recp_room);
  713     if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
  714     if (valid->display_recp != NULL)    free(valid->display_recp);
  715     if (valid->bounce_to != NULL)       free(valid->bounce_to);
  716     if (valid->envelope_from != NULL)   free(valid->envelope_from);
  717     if (valid->sending_room != NULL)    free(valid->sending_room);
  718     free(valid);
  719 }
  720 
  721 
  722 char *qp_encode_email_addrs(char *source) {
  723     char *user, *node, *name;
  724     const char headerStr[] = "=?UTF-8?Q?";
  725     char *Encoded;
  726     char *EncodedName;
  727     char *nPtr;
  728     int need_to_encode = 0;
  729     long SourceLen;
  730     long EncodedMaxLen;
  731     long nColons = 0;
  732     long *AddrPtr;
  733     long *AddrUtf8;
  734     long nAddrPtrMax = 50;
  735     long nmax;
  736     int InQuotes = 0;
  737     int i, n;
  738 
  739     if (source == NULL) return source;
  740     if (IsEmptyStr(source)) return source;
  741     syslog(LOG_DEBUG, "internet_addressing: qp_encode_email_addrs <%s>", source);
  742 
  743     AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
  744     AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
  745     memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
  746     *AddrPtr = 0;
  747     i = 0;
  748     while (!IsEmptyStr (&source[i])) {
  749         if (nColons >= nAddrPtrMax){
  750             long *ptr;
  751 
  752             ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
  753             memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
  754             free (AddrPtr), AddrPtr = ptr;
  755 
  756             ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
  757             memset(&ptr[nAddrPtrMax], 0, sizeof (long) * nAddrPtrMax);
  758 
  759             memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
  760             free (AddrUtf8), AddrUtf8 = ptr;
  761             nAddrPtrMax *= 2;               
  762         }
  763         if (((unsigned char) source[i] < 32) || ((unsigned char) source[i] > 126)) {
  764             need_to_encode = 1;
  765             AddrUtf8[nColons] = 1;
  766         }
  767         if (source[i] == '"') {
  768             InQuotes = !InQuotes;
  769         }
  770         if (!InQuotes && source[i] == ',') {
  771             AddrPtr[nColons] = i;
  772             nColons++;
  773         }
  774         i++;
  775     }
  776     if (need_to_encode == 0) {
  777         free(AddrPtr);
  778         free(AddrUtf8);
  779         return source;
  780     }
  781 
  782     SourceLen = i;
  783     EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
  784     Encoded = (char*) malloc (EncodedMaxLen);
  785 
  786     for (i = 0; i < nColons; i++) {
  787         source[AddrPtr[i]++] = '\0';
  788     }
  789     /* TODO: if libidn, this might get larger*/
  790     user = malloc(SourceLen + 1);
  791     node = malloc(SourceLen + 1);
  792     name = malloc(SourceLen + 1);
  793 
  794     nPtr = Encoded;
  795     *nPtr = '\0';
  796     for (i = 0; i < nColons && nPtr != NULL; i++) {
  797         nmax = EncodedMaxLen - (nPtr - Encoded);
  798         if (AddrUtf8[i]) {
  799             process_rfc822_addr(&source[AddrPtr[i]], user, node, name);
  800             /* TODO: libIDN here ! */
  801             if (IsEmptyStr(name)) {
  802                 n = snprintf(nPtr, nmax, (i==0)?"%s@%s" : ",%s@%s", user, node);
  803             }
  804             else {
  805                 EncodedName = rfc2047encode(name, strlen(name));            
  806                 n = snprintf(nPtr, nmax, (i==0)?"%s <%s@%s>" : ",%s <%s@%s>", EncodedName, user, node);
  807                 free(EncodedName);
  808             }
  809         }
  810         else { 
  811             n = snprintf(nPtr, nmax, (i==0)?"%s" : ",%s", &source[AddrPtr[i]]);
  812         }
  813         if (n > 0 )
  814             nPtr += n;
  815         else { 
  816             char *ptr, *nnPtr;
  817             ptr = (char*) malloc(EncodedMaxLen * 2);
  818             memcpy(ptr, Encoded, EncodedMaxLen);
  819             nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
  820             free(Encoded), Encoded = ptr;
  821             EncodedMaxLen *= 2;
  822             i--; /* do it once more with properly lengthened buffer */
  823         }
  824     }
  825     for (i = 0; i < nColons; i++)
  826         source[--AddrPtr[i]] = ',';
  827 
  828     free(user);
  829     free(node);
  830     free(name);
  831     free(AddrUtf8);
  832     free(AddrPtr);
  833     return Encoded;
  834 }
  835 
  836 
  837 /*
  838  * Unfold a multi-line field into a single line, removing multi-whitespaces
  839  */
  840 void unfold_rfc822_field(char **field, char **FieldEnd) 
  841 {
  842     int quote = 0;
  843     char *pField = *field;
  844     char *sField;
  845     char *pFieldEnd = *FieldEnd;
  846 
  847     while (isspace(*pField))
  848         pField++;
  849     /* remove leading/trailing whitespace */
  850     ;
  851 
  852     while (isspace(*pFieldEnd))
  853         pFieldEnd --;
  854 
  855     *FieldEnd = pFieldEnd;
  856     /* convert non-space whitespace to spaces, and remove double blanks */
  857     for (sField = *field = pField; 
  858          sField < pFieldEnd; 
  859          pField++, sField++)
  860     {
  861         if ((*sField=='\r') || (*sField=='\n'))
  862         {
  863             int offset = 1;
  864             while ( ( (*(sField + offset) == '\r') || (*(sField + offset) == '\n' )) && (sField + offset < pFieldEnd) ) {
  865                 offset ++;
  866             }
  867             sField += offset;
  868             *pField = *sField;
  869         }
  870         else {
  871             if (*sField=='\"') quote = 1 - quote;
  872             if (!quote) {
  873                 if (isspace(*sField)) {
  874                     *pField = ' ';
  875                     pField++;
  876                     sField++;
  877                     
  878                     while ((sField < pFieldEnd) && 
  879                            isspace(*sField))
  880                         sField++;
  881                     *pField = *sField;
  882                 }
  883                 else *pField = *sField;
  884             }
  885             else *pField = *sField;
  886         }
  887     }
  888     *pField = '\0';
  889     *FieldEnd = pField - 1;
  890 }
  891 
  892 
  893 /*
  894  * Split an RFC822-style address into userid, host, and full name
  895  *
  896  */
  897 void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name) {
  898     int a;
  899 
  900     strcpy(user, "");
  901     strcpy(node, CtdlGetConfigStr("c_fqdn"));
  902     strcpy(name, "");
  903 
  904     if (rfc822 == NULL) return;
  905 
  906     /* extract full name - first, it's From minus <userid> */
  907     strcpy(name, rfc822);
  908     stripout(name, '<', '>');
  909 
  910     /* strip anything to the left of a bang */
  911     while ((!IsEmptyStr(name)) && (haschar(name, '!') > 0))
  912         strcpy(name, &name[1]);
  913 
  914     /* and anything to the right of a @ or % */
  915     for (a = 0; name[a] != '\0'; ++a) {
  916         if (name[a] == '@') {
  917             name[a] = 0;
  918             break;
  919         }
  920         if (name[a] == '%') {
  921             name[a] = 0;
  922             break;
  923         }
  924     }
  925 
  926     /* but if there are parentheses, that changes the rules... */
  927     if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
  928         strcpy(name, rfc822);
  929         stripallbut(name, '(', ')');
  930     }
  931 
  932     /* but if there are a set of quotes, that supersedes everything */
  933     if (haschar(rfc822, 34) == 2) {
  934         strcpy(name, rfc822);
  935         while ((!IsEmptyStr(name)) && (name[0] != 34)) {
  936             strcpy(&name[0], &name[1]);
  937         }
  938         strcpy(&name[0], &name[1]);
  939         for (a = 0; name[a] != '\0'; ++a)
  940             if (name[a] == 34) {
  941                 name[a] = 0;
  942                 break;
  943             }
  944     }
  945     /* extract user id */
  946     strcpy(user, rfc822);
  947 
  948     /* first get rid of anything in parens */
  949     stripout(user, '(', ')');
  950 
  951     /* if there's a set of angle brackets, strip it down to that */
  952     if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
  953         stripallbut(user, '<', '>');
  954     }
  955 
  956     /* strip anything to the left of a bang */
  957     while ((!IsEmptyStr(user)) && (haschar(user, '!') > 0))
  958         strcpy(user, &user[1]);
  959 
  960     /* and anything to the right of a @ or % */
  961     for (a = 0; user[a] != '\0'; ++a) {
  962         if (user[a] == '@') {
  963             user[a] = 0;
  964             break;
  965         }
  966         if (user[a] == '%') {
  967             user[a] = 0;
  968             break;
  969         }
  970     }
  971 
  972 
  973     /* extract node name */
  974     strcpy(node, rfc822);
  975 
  976     /* first get rid of anything in parens */
  977     stripout(node, '(', ')');
  978 
  979     /* if there's a set of angle brackets, strip it down to that */
  980     if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
  981         stripallbut(node, '<', '>');
  982     }
  983 
  984     /* If no node specified, tack ours on instead */
  985     if (
  986         (haschar(node, '@')==0)
  987         && (haschar(node, '%')==0)
  988         && (haschar(node, '!')==0)
  989     ) {
  990         strcpy(node, CtdlGetConfigStr("c_nodename"));
  991     }
  992     else {
  993 
  994         /* strip anything to the left of a @ */
  995         while ((!IsEmptyStr(node)) && (haschar(node, '@') > 0))
  996             strcpy(node, &node[1]);
  997     
  998         /* strip anything to the left of a % */
  999         while ((!IsEmptyStr(node)) && (haschar(node, '%') > 0))
 1000             strcpy(node, &node[1]);
 1001     
 1002         /* reduce multiple system bang paths to node!user */
 1003         while ((!IsEmptyStr(node)) && (haschar(node, '!') > 1))
 1004             strcpy(node, &node[1]);
 1005     
 1006         /* now get rid of the user portion of a node!user string */
 1007         for (a = 0; node[a] != '\0'; ++a)
 1008             if (node[a] == '!') {
 1009                 node[a] = 0;
 1010                 break;
 1011             }
 1012     }
 1013 
 1014     /* strip leading and trailing spaces in all strings */
 1015     striplt(user);
 1016     striplt(node);
 1017     striplt(name);
 1018 
 1019     /* If we processed a string that had the address in angle brackets
 1020      * but no name outside the brackets, we now have an empty name.  In
 1021      * this case, use the user portion of the address as the name.
 1022      */
 1023     if ((IsEmptyStr(name)) && (!IsEmptyStr(user))) {
 1024         strcpy(name, user);
 1025     }
 1026 }
 1027 
 1028 
 1029 /*
 1030  * convert_field() is a helper function for convert_internet_message().
 1031  * Given start/end positions for an rfc822 field, it converts it to a Citadel
 1032  * field if it wants to, and unfolds it if necessary.
 1033  *
 1034  * Returns 1 if the field was converted and inserted into the Citadel message
 1035  * structure, implying that the source field should be removed from the
 1036  * message text.
 1037  */
 1038 int convert_field(struct CtdlMessage *msg, const char *beg, const char *end) {
 1039     char *key, *value, *valueend;
 1040     long len;
 1041     const char *pos;
 1042     int i;
 1043     const char *colonpos = NULL;
 1044     int processed = 0;
 1045     char user[1024];
 1046     char node[1024];
 1047     char name[1024];
 1048     char addr[1024];
 1049     time_t parsed_date;
 1050     long valuelen;
 1051 
 1052     for (pos = end; pos >= beg; pos--) {
 1053         if (*pos == ':') colonpos = pos;
 1054     }
 1055 
 1056     if (colonpos == NULL) return(0);    /* no colon? not a valid header line */
 1057 
 1058     len = end - beg;
 1059     key = malloc(len + 2);
 1060     memcpy(key, beg, len + 1);
 1061     key[len] = '\0';
 1062     valueend = key + len;
 1063     * ( key + (colonpos - beg) ) = '\0';
 1064     value = &key[(colonpos - beg) + 1];
 1065 /*  printf("Header: [%s]\nValue: [%s]\n", key, value); */
 1066     unfold_rfc822_field(&value, &valueend);
 1067     valuelen = valueend - value + 1;
 1068 /*  printf("UnfoldedValue: [%s]\n", value); */
 1069 
 1070     /*
 1071      * Here's the big rfc822-to-citadel loop.
 1072      */
 1073 
 1074     /* Date/time is converted into a unix timestamp.  If the conversion
 1075      * fails, we replace it with the time the message arrived locally.
 1076      */
 1077     if (!strcasecmp(key, "Date")) {
 1078         parsed_date = parsedate(value);
 1079         if (parsed_date < 0L) parsed_date = time(NULL);
 1080 
 1081         if (CM_IsEmpty(msg, eTimestamp))
 1082             CM_SetFieldLONG(msg, eTimestamp, parsed_date);
 1083         processed = 1;
 1084     }
 1085 
 1086     else if (!strcasecmp(key, "From")) {
 1087         process_rfc822_addr(value, user, node, name);
 1088         syslog(LOG_DEBUG, "internet_addressing: converted to <%s@%s> (%s)", user, node, name);
 1089         snprintf(addr, sizeof(addr), "%s@%s", user, node);
 1090         if (CM_IsEmpty(msg, eAuthor) && !IsEmptyStr(name)) {
 1091             CM_SetField(msg, eAuthor, name, -1);
 1092         }
 1093         if (CM_IsEmpty(msg, erFc822Addr) && !IsEmptyStr(addr)) {
 1094             CM_SetField(msg, erFc822Addr, addr, -1);
 1095         }
 1096         processed = 1;
 1097     }
 1098 
 1099     else if (!strcasecmp(key, "Subject")) {
 1100         if (CM_IsEmpty(msg, eMsgSubject))
 1101             CM_SetField(msg, eMsgSubject, value, valuelen);
 1102         processed = 1;
 1103     }
 1104 
 1105     else if (!strcasecmp(key, "List-ID")) {
 1106         if (CM_IsEmpty(msg, eListID))
 1107             CM_SetField(msg, eListID, value, valuelen);
 1108         processed = 1;
 1109     }
 1110 
 1111     else if (!strcasecmp(key, "To")) {
 1112         if (CM_IsEmpty(msg, eRecipient))
 1113             CM_SetField(msg, eRecipient, value, valuelen);
 1114         processed = 1;
 1115     }
 1116 
 1117     else if (!strcasecmp(key, "CC")) {
 1118         if (CM_IsEmpty(msg, eCarbonCopY))
 1119             CM_SetField(msg, eCarbonCopY, value, valuelen);
 1120         processed = 1;
 1121     }
 1122 
 1123     else if (!strcasecmp(key, "Message-ID")) {
 1124         if (!CM_IsEmpty(msg, emessageId)) {
 1125             syslog(LOG_WARNING, "internet_addressing: duplicate message id");
 1126         }
 1127         else {
 1128             char *pValue;
 1129             long pValueLen;
 1130 
 1131             pValue = value;
 1132             pValueLen = valuelen;
 1133             /* Strip angle brackets */
 1134             while (haschar(pValue, '<') > 0) {
 1135                 pValue ++;
 1136                 pValueLen --;
 1137             }
 1138 
 1139             for (i = 0; i <= pValueLen; ++i)
 1140                 if (pValue[i] == '>') {
 1141                     pValueLen = i;
 1142                     break;
 1143                 }
 1144 
 1145             CM_SetField(msg, emessageId, pValue, pValueLen);
 1146         }
 1147 
 1148         processed = 1;
 1149     }
 1150 
 1151     else if (!strcasecmp(key, "Return-Path")) {
 1152         if (CM_IsEmpty(msg, eMessagePath))
 1153             CM_SetField(msg, eMessagePath, value, valuelen);
 1154         processed = 1;
 1155     }
 1156 
 1157     else if (!strcasecmp(key, "Envelope-To")) {
 1158         if (CM_IsEmpty(msg, eenVelopeTo))
 1159             CM_SetField(msg, eenVelopeTo, value, valuelen);
 1160         processed = 1;
 1161     }
 1162 
 1163     else if (!strcasecmp(key, "References")) {
 1164         CM_SetField(msg, eWeferences, value, valuelen);
 1165         processed = 1;
 1166     }
 1167 
 1168     else if (!strcasecmp(key, "Reply-To")) {
 1169         CM_SetField(msg, eReplyTo, value, valuelen);
 1170         processed = 1;
 1171     }
 1172 
 1173     else if (!strcasecmp(key, "In-reply-to")) {
 1174         if (CM_IsEmpty(msg, eWeferences)) /* References: supersedes In-reply-to: */
 1175             CM_SetField(msg, eWeferences, value, valuelen);
 1176         processed = 1;
 1177     }
 1178 
 1179 
 1180 
 1181     /* Clean up and move on. */
 1182     free(key);  /* Don't free 'value', it's actually the same buffer */
 1183     return processed;
 1184 }
 1185 
 1186 
 1187 /*
 1188  * Convert RFC822 references format (References) to Citadel references format (Weferences)
 1189  */
 1190 void convert_references_to_wefewences(char *str) {
 1191     int bracket_nesting = 0;
 1192     char *ptr = str;
 1193     char *moveptr = NULL;
 1194     char ch;
 1195 
 1196     while(*ptr) {
 1197         ch = *ptr;
 1198         if (ch == '>') {
 1199             --bracket_nesting;
 1200             if (bracket_nesting < 0) bracket_nesting = 0;
 1201         }
 1202         if ((ch == '>') && (bracket_nesting == 0) && (*(ptr+1)) && (ptr>str) ) {
 1203             *ptr = '|';
 1204             ++ptr;
 1205         }
 1206         else if (bracket_nesting > 0) {
 1207             ++ptr;
 1208         }
 1209         else {
 1210             moveptr = ptr;
 1211             while (*moveptr) {
 1212                 *moveptr = *(moveptr+1);
 1213                 ++moveptr;
 1214             }
 1215         }
 1216         if (ch == '<') ++bracket_nesting;
 1217     }
 1218 
 1219 }
 1220 
 1221 
 1222 /*
 1223  * Convert an RFC822 message (headers + body) to a CtdlMessage structure.
 1224  * NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
 1225  * will be deallocated when CM_Free() is called.  Therefore, the
 1226  * supplied buffer should be DEREFERENCED.  It should not be freed or used
 1227  * again.
 1228  */
 1229 struct CtdlMessage *convert_internet_message(char *rfc822) {
 1230     StrBuf *RFCBuf = NewStrBufPlain(rfc822, -1);
 1231     free (rfc822);
 1232     return convert_internet_message_buf(&RFCBuf);
 1233 }
 1234 
 1235 
 1236 struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822)
 1237 {
 1238     struct CtdlMessage *msg;
 1239     const char *pos, *beg, *end, *totalend;
 1240     int done, alldone = 0;
 1241     int converted;
 1242     StrBuf *OtherHeaders;
 1243 
 1244     msg = malloc(sizeof(struct CtdlMessage));
 1245     if (msg == NULL) return msg;
 1246 
 1247     memset(msg, 0, sizeof(struct CtdlMessage));
 1248     msg->cm_magic = CTDLMESSAGE_MAGIC;  /* self check */
 1249     msg->cm_anon_type = 0;          /* never anonymous */
 1250     msg->cm_format_type = FMT_RFC822;   /* internet message */
 1251 
 1252     pos = ChrPtr(*rfc822);
 1253     totalend = pos + StrLength(*rfc822);
 1254     done = 0;
 1255     OtherHeaders = NewStrBufPlain(NULL, StrLength(*rfc822));
 1256 
 1257     while (!alldone) {
 1258 
 1259         /* Locate beginning and end of field, keeping in mind that
 1260          * some fields might be multiline
 1261          */
 1262         end = beg = pos;
 1263 
 1264         while ((end < totalend) && 
 1265                (end == beg) && 
 1266                (done == 0) ) 
 1267         {
 1268 
 1269             if ( (*pos=='\n') && ((*(pos+1))!=0x20) && ((*(pos+1))!=0x09) )
 1270             {
 1271                 end = pos;
 1272             }
 1273 
 1274             /* done with headers? */
 1275             if ((*pos=='\n') &&
 1276                 ( (*(pos+1)=='\n') ||
 1277                   (*(pos+1)=='\r')) ) 
 1278             {
 1279                 alldone = 1;
 1280             }
 1281 
 1282             if (pos >= (totalend - 1) )
 1283             {
 1284                 end = pos;
 1285                 done = 1;
 1286             }
 1287 
 1288             ++pos;
 1289 
 1290         }
 1291 
 1292         /* At this point we have a field.  Are we interested in it? */
 1293         converted = convert_field(msg, beg, end);
 1294 
 1295         /* Strip the field out of the RFC822 header if we used it */
 1296         if (!converted) {
 1297             StrBufAppendBufPlain(OtherHeaders, beg, end - beg, 0);
 1298             StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
 1299         }
 1300 
 1301         /* If we've hit the end of the message, bail out */
 1302         if (pos >= totalend)
 1303             alldone = 1;
 1304     }
 1305     StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
 1306     if (pos < totalend)
 1307         StrBufAppendBufPlain(OtherHeaders, pos, totalend - pos, 0);
 1308     FreeStrBuf(rfc822);
 1309     CM_SetAsFieldSB(msg, eMesageText, &OtherHeaders);
 1310 
 1311     /* Follow-up sanity checks... */
 1312 
 1313     /* If there's no timestamp on this message, set it to now. */
 1314     if (CM_IsEmpty(msg, eTimestamp)) {
 1315         CM_SetFieldLONG(msg, eTimestamp, time(NULL));
 1316     }
 1317 
 1318     /* If a W (references, or rather, Wefewences) field is present, we
 1319      * have to convert it from RFC822 format to Citadel format.
 1320      */
 1321     if (!CM_IsEmpty(msg, eWeferences)) {
 1322         /// todo: API!
 1323         convert_references_to_wefewences(msg->cm_fields[eWeferences]);
 1324     }
 1325 
 1326     return msg;
 1327 }
 1328 
 1329 
 1330 /*
 1331  * Look for a particular header field in an RFC822 message text.  If the
 1332  * requested field is found, it is unfolded (if necessary) and returned to
 1333  * the caller.  The field name is stripped out, leaving only its contents.
 1334  * The caller is responsible for freeing the returned buffer.  If the requested
 1335  * field is not present, or anything else goes wrong, it returns NULL.
 1336  */
 1337 char *rfc822_fetch_field(const char *rfc822, const char *fieldname) {
 1338     char *fieldbuf = NULL;
 1339     const char *end_of_headers;
 1340     const char *field_start;
 1341     const char *ptr;
 1342     char *cont;
 1343     char fieldhdr[SIZ];
 1344 
 1345     /* Should never happen, but sometimes we get stupid */
 1346     if (rfc822 == NULL) return(NULL);
 1347     if (fieldname == NULL) return(NULL);
 1348 
 1349     snprintf(fieldhdr, sizeof fieldhdr, "%s:", fieldname);
 1350 
 1351     /* Locate the end of the headers, so we don't run past that point */
 1352     end_of_headers = cbmstrcasestr(rfc822, "\n\r\n");
 1353     if (end_of_headers == NULL) {
 1354         end_of_headers = cbmstrcasestr(rfc822, "\n\n");
 1355     }
 1356     if (end_of_headers == NULL) return (NULL);
 1357 
 1358     field_start = cbmstrcasestr(rfc822, fieldhdr);
 1359     if (field_start == NULL) return(NULL);
 1360     if (field_start > end_of_headers) return(NULL);
 1361 
 1362     fieldbuf = malloc(SIZ);
 1363     strcpy(fieldbuf, "");
 1364 
 1365     ptr = field_start;
 1366     ptr = cmemreadline(ptr, fieldbuf, SIZ-strlen(fieldbuf) );
 1367     while ( (isspace(ptr[0])) && (ptr < end_of_headers) ) {
 1368         strcat(fieldbuf, " ");
 1369         cont = &fieldbuf[strlen(fieldbuf)];
 1370         ptr = cmemreadline(ptr, cont, SIZ-strlen(fieldbuf) );
 1371         striplt(cont);
 1372     }
 1373 
 1374     strcpy(fieldbuf, &fieldbuf[strlen(fieldhdr)]);
 1375     striplt(fieldbuf);
 1376 
 1377     return(fieldbuf);
 1378 }
 1379 
 1380 
 1381 /*****************************************************************************
 1382  *                      DIRECTORY MANAGEMENT FUNCTIONS                       *
 1383  *****************************************************************************/
 1384 
 1385 /*
 1386  * Generate the index key for an Internet e-mail address to be looked up
 1387  * in the database.
 1388  */
 1389 void directory_key(char *key, char *addr) {
 1390     int i;
 1391     int keylen = 0;
 1392 
 1393     for (i=0; !IsEmptyStr(&addr[i]); ++i) {
 1394         if (!isspace(addr[i])) {
 1395             key[keylen++] = tolower(addr[i]);
 1396         }
 1397     }
 1398     key[keylen++] = 0;
 1399 
 1400     syslog(LOG_DEBUG, "internet_addressing: directory key is <%s>", key);
 1401 }
 1402 
 1403 
 1404 /*
 1405  * Return nonzero if the supplied address is in one of "our" domains
 1406  */
 1407 int IsDirectory(char *addr, int allow_masq_domains) {
 1408     char domain[256];
 1409     int h;
 1410 
 1411     extract_token(domain, addr, 1, '@', sizeof domain);
 1412     striplt(domain);
 1413 
 1414     h = CtdlHostAlias(domain);
 1415 
 1416     if ( (h == hostalias_masq) && allow_masq_domains)
 1417         return(1);
 1418     
 1419     if (h == hostalias_localhost) {
 1420         return(1);
 1421     }
 1422     else {
 1423         return(0);
 1424     }
 1425 }
 1426 
 1427 
 1428 /*
 1429  * Add an Internet e-mail address to the directory for a user
 1430  */
 1431 int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr) {
 1432     char key[SIZ];
 1433 
 1434     if (IsDirectory(internet_addr, 0) == 0) {
 1435         return 0;
 1436     }
 1437     syslog(LOG_DEBUG, "internet_addressing: create directory entry: %s --> %s", internet_addr, citadel_addr);
 1438     directory_key(key, internet_addr);
 1439     cdb_store(CDB_DIRECTORY, key, strlen(key), citadel_addr, strlen(citadel_addr)+1 );
 1440     return 1;
 1441 }
 1442 
 1443 
 1444 /*
 1445  * Delete an Internet e-mail address from the directory.
 1446  *
 1447  * (NOTE: we don't actually use or need the citadel_addr variable; it's merely
 1448  * here because the callback API expects to be able to send it.)
 1449  */
 1450 int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr) {
 1451     char key[SIZ];
 1452     
 1453     syslog(LOG_DEBUG, "internet_addressing: delete directory entry: %s --> %s", internet_addr, citadel_addr);
 1454     directory_key(key, internet_addr);
 1455     return cdb_delete(CDB_DIRECTORY, key, strlen(key) ) == 0;
 1456 }
 1457 
 1458 
 1459 /*
 1460  * Look up an Internet e-mail address in the directory.
 1461  * On success: returns 0, and Citadel address stored in 'target'
 1462  * On failure: returns nonzero
 1463  */
 1464 int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen) {
 1465     struct cdbdata *cdbrec;
 1466     char key[SIZ];
 1467 
 1468     /* Dump it in there unchanged, just for kicks */
 1469     if (target != NULL) {
 1470         safestrncpy(target, internet_addr, targbuflen);
 1471     }
 1472 
 1473     /* Only do lookups for addresses with hostnames in them */
 1474     if (num_tokens(internet_addr, '@') != 2) return(-1);
 1475 
 1476     /* Only do lookups for domains in the directory */
 1477     if (IsDirectory(internet_addr, 0) == 0) return(-1);
 1478 
 1479     directory_key(key, internet_addr);
 1480     cdbrec = cdb_fetch(CDB_DIRECTORY, key, strlen(key) );
 1481     if (cdbrec != NULL) {
 1482         if (target != NULL) {
 1483             safestrncpy(target, cdbrec->ptr, targbuflen);
 1484         }
 1485         cdb_free(cdbrec);
 1486         return(0);
 1487     }
 1488 
 1489     return(-1);
 1490 }
 1491 
 1492 
 1493 /*
 1494  * Harvest any email addresses that someone might want to have in their
 1495  * "collected addresses" book.
 1496  */
 1497 char *harvest_collected_addresses(struct CtdlMessage *msg) {
 1498     char *coll = NULL;
 1499     char addr[256];
 1500     char user[256], node[256], name[256];
 1501     int is_harvestable;
 1502     int i, j, h;
 1503     eMsgField field = 0;
 1504 
 1505     if (msg == NULL) return(NULL);
 1506 
 1507     is_harvestable = 1;
 1508     strcpy(addr, "");   
 1509     if (!CM_IsEmpty(msg, eAuthor)) {
 1510         strcat(addr, msg->cm_fields[eAuthor]);
 1511     }
 1512     if (!CM_IsEmpty(msg, erFc822Addr)) {
 1513         strcat(addr, " <");
 1514         strcat(addr, msg->cm_fields[erFc822Addr]);
 1515         strcat(addr, ">");
 1516         if (IsDirectory(msg->cm_fields[erFc822Addr], 0)) {
 1517             is_harvestable = 0;
 1518         }
 1519     }
 1520 
 1521     if (is_harvestable) {
 1522         coll = strdup(addr);
 1523     }
 1524     else {
 1525         coll = strdup("");
 1526     }
 1527 
 1528     if (coll == NULL) return(NULL);
 1529 
 1530     /* Scan both the R (To) and Y (CC) fields */
 1531     for (i = 0; i < 2; ++i) {
 1532         if (i == 0) field = eRecipient;
 1533         if (i == 1) field = eCarbonCopY;
 1534 
 1535         if (!CM_IsEmpty(msg, field)) {
 1536             for (j=0; j<num_tokens(msg->cm_fields[field], ','); ++j) {
 1537                 extract_token(addr, msg->cm_fields[field], j, ',', sizeof addr);
 1538                 if (strstr(addr, "=?") != NULL)
 1539                     utf8ify_rfc822_string(addr);
 1540                 process_rfc822_addr(addr, user, node, name);
 1541                 h = CtdlHostAlias(node);
 1542                 if (h != hostalias_localhost) {
 1543                     coll = realloc(coll, strlen(coll) + strlen(addr) + 4);
 1544                     if (coll == NULL) return(NULL);
 1545                     if (!IsEmptyStr(coll)) {
 1546                         strcat(coll, ",");
 1547                     }
 1548                     striplt(addr);
 1549                     strcat(coll, addr);
 1550                 }
 1551             }
 1552         }
 1553     }
 1554 
 1555     if (IsEmptyStr(coll)) {
 1556         free(coll);
 1557         return(NULL);
 1558     }
 1559     return(coll);
 1560 }
 1561 
 1562 
 1563 /*
 1564  * Helper function for CtdlRebuildDirectoryIndex()
 1565  */
 1566 void CtdlRebuildDirectoryIndex_backend(char *username, void *data) {
 1567 
 1568     int j = 0;
 1569     struct ctdluser usbuf;
 1570 
 1571     if (CtdlGetUser(&usbuf, username) != 0) {
 1572         return;
 1573     }
 1574 
 1575     if ( (!IsEmptyStr(usbuf.fullname)) && (!IsEmptyStr(usbuf.emailaddrs)) ) {
 1576         for (j=0; j<num_tokens(usbuf.emailaddrs, '|'); ++j) {
 1577             char one_email[512];
 1578             extract_token(one_email, usbuf.emailaddrs, j, '|', sizeof one_email);
 1579             CtdlDirectoryAddUser(one_email, usbuf.fullname);
 1580         }
 1581     }
 1582 }
 1583 
 1584 
 1585 /*
 1586  * Initialize the directory database (erasing anything already there)
 1587  */
 1588 void CtdlRebuildDirectoryIndex(void) {
 1589     syslog(LOG_INFO, "internet_addressing: rebuilding email address directory index");
 1590     cdb_trunc(CDB_DIRECTORY);
 1591     ForEachUser(CtdlRebuildDirectoryIndex_backend, NULL);
 1592 }
 1593 
 1594 
 1595 /*
 1596  * Configure Internet email addresses for a user account, updating the Directory Index in the process
 1597  */
 1598 void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs)
 1599 {
 1600     struct ctdluser usbuf;
 1601     int i;
 1602     char buf[SIZ];
 1603 
 1604     if (CtdlGetUserLock(&usbuf, requested_user) != 0) { // We are relying on the fact that the DirectoryIndex functions don't lock.
 1605         return;                     // Silently fail here if we can't acquire a lock on the user record.
 1606     }
 1607 
 1608     syslog(LOG_DEBUG, "internet_addressing: setting email addresses for <%s> to <%s>", usbuf.fullname, new_emailaddrs);
 1609 
 1610     /* Delete all of the existing directory index records for the user (easier this way) */
 1611     for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
 1612         extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
 1613         CtdlDirectoryDelUser(buf, requested_user);
 1614     }
 1615 
 1616     strcpy(usbuf.emailaddrs, new_emailaddrs);       // make it official.
 1617 
 1618     /* Index all of the new email addresses (they've already been sanitized) */
 1619     for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
 1620         extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
 1621         CtdlDirectoryAddUser(buf, requested_user);
 1622     }
 1623 
 1624     CtdlPutUserLock(&usbuf);
 1625 }
 1626 
 1627 
 1628 /*
 1629  * Auto-generate an Internet email address for a user account
 1630  */
 1631 void AutoGenerateEmailAddressForUser(struct ctdluser *user)
 1632 {
 1633     char synthetic_email_addr[1024];
 1634     int i, j;
 1635     int u = 0;
 1636 
 1637     for (i=0; u==0; ++i) {
 1638         if (i == 0) {
 1639             // first try just converting the user name to lowercase and replacing spaces with underscores
 1640             snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "%s@%s", user->fullname, CtdlGetConfigStr("c_fqdn"));
 1641             for (j=0; ((synthetic_email_addr[j] != '\0')&&(synthetic_email_addr[j] != '@')); j++) {
 1642                 synthetic_email_addr[j] = tolower(synthetic_email_addr[j]);
 1643                 if (!isalnum(synthetic_email_addr[j])) {
 1644                     synthetic_email_addr[j] = '_';
 1645                 }
 1646             }
 1647         }
 1648         else if (i == 1) {
 1649             // then try 'ctdl' followed by the user number
 1650             snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08lx@%s", user->usernum, CtdlGetConfigStr("c_fqdn"));
 1651         }
 1652         else if (i > 1) {
 1653             // oof.  just keep trying other numbers until we find one
 1654             snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08x@%s", i, CtdlGetConfigStr("c_fqdn"));
 1655         }
 1656         u = CtdlDirectoryLookup(NULL, synthetic_email_addr, 0);
 1657         syslog(LOG_DEBUG, "user_ops: address <%s> lookup returned <%d>", synthetic_email_addr, u);
 1658     }
 1659 
 1660     CtdlSetEmailAddressesForUser(user->fullname, synthetic_email_addr);
 1661     strncpy(CC->user.emailaddrs, synthetic_email_addr, sizeof(user->emailaddrs));
 1662     syslog(LOG_DEBUG, "user_ops: auto-generated email address <%s> for <%s>", synthetic_email_addr, user->fullname);
 1663 }