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