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