c2131cbff46ce421a37225fb78b21772dbd9ceba
[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                         unsigned 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         unsigned 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; aaa[a] != '\0'; ++a) {
435                         if (aaa[a] == ',') {
436                                 strcpy(bbb, &aaa[a + 1]);
437                                 aaa[a] = 0;
438                                 break;
439                         }
440                 }
441                 if (!strcasecmp(name, aaa))
442                         strcpy(name, bbb);
443         }
444         fclose(fp);
445
446         /* Hit the Global Address Book */
447         if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
448                 strcpy(name, aaa);
449         }
450
451         if (strcasecmp(original_name, name)) {
452                 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
453         }
454
455         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
456         for (a=0; name[a] != '\0'; ++a) {
457                 if (name[a] == '@') {
458                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
459                                 name[a] = 0;
460                                 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
461                                 break;
462                         }
463                 }
464         }
465
466         /* determine local or remote type, see citadel.h */
467         at = haschar(name, '@');
468         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
469         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
470         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
471
472         /* figure out the delivery mode */
473         extract_token(node, name, 1, '@', sizeof node);
474
475         /* If there are one or more dots in the nodename, we assume that it
476          * is an FQDN and will attempt SMTP delivery to the Internet.
477          */
478         if (haschar(node, '.') > 0) {
479                 return(MES_INTERNET);
480         }
481
482         /* Otherwise we look in the IGnet maps for a valid Citadel node.
483          * Try directly-connected nodes first...
484          */
485         ignetcfg = CtdlGetSysConfig(IGNETCFG);
486         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
487                 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
488                 extract_token(testnode, buf, 0, '|', sizeof testnode);
489                 if (!strcasecmp(node, testnode)) {
490                         free(ignetcfg);
491                         return(MES_IGNET);
492                 }
493         }
494         free(ignetcfg);
495
496         /*
497          * Then try nodes that are two or more hops away.
498          */
499         ignetmap = CtdlGetSysConfig(IGNETMAP);
500         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
501                 extract_token(buf, ignetmap, i, '\n', sizeof buf);
502                 extract_token(testnode, buf, 0, '|', sizeof testnode);
503                 if (!strcasecmp(node, testnode)) {
504                         free(ignetmap);
505                         return(MES_IGNET);
506                 }
507         }
508         free(ignetmap);
509
510         /* If we get to this point it's an invalid node name */
511         return (MES_ERROR);
512 }
513
514
515
516 /*
517  * Validate recipients, count delivery types and errors, and handle aliasing
518  * FIXME check for dupes!!!!!
519  *
520  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
521  * were specified, or the number of addresses found invalid.
522  *
523  * Caller needs to free the result using free_recipients()
524  */
525 recptypes *validate_recipients(const char *supplied_recipients, 
526                                const char *RemoteIdentifier, 
527                                int Flags) {
528         struct CitContext *CCC = CC;
529         recptypes *ret;
530         char *recipients = NULL;
531         char *org_recp;
532         char this_recp[256];
533         char this_recp_cooked[256];
534         char append[SIZ];
535         long len;
536         int num_recps = 0;
537         int i, j;
538         int mailtype;
539         int invalid;
540         struct ctdluser tempUS;
541         struct ctdlroom tempQR;
542         struct ctdlroom tempQR2;
543         int err = 0;
544         char errmsg[SIZ];
545         int in_quotes = 0;
546
547         /* Initialize */
548         ret = (recptypes *) malloc(sizeof(recptypes));
549         if (ret == NULL) return(NULL);
550
551         /* Set all strings to null and numeric values to zero */
552         memset(ret, 0, sizeof(recptypes));
553
554         if (supplied_recipients == NULL) {
555                 recipients = strdup("");
556         }
557         else {
558                 recipients = strdup(supplied_recipients);
559         }
560
561         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
562          * actually need, but it's healthier for the heap than doing lots of tiny
563          * realloc() calls instead.
564          */
565         len = strlen(recipients) + 1024;
566         ret->errormsg = malloc(len);
567         ret->recp_local = malloc(len);
568         ret->recp_internet = malloc(len);
569         ret->recp_ignet = malloc(len);
570         ret->recp_room = malloc(len);
571         ret->display_recp = malloc(len);
572         ret->recp_orgroom = malloc(len);
573         org_recp = malloc(len);
574
575         ret->errormsg[0] = 0;
576         ret->recp_local[0] = 0;
577         ret->recp_internet[0] = 0;
578         ret->recp_ignet[0] = 0;
579         ret->recp_room[0] = 0;
580         ret->recp_orgroom[0] = 0;
581         ret->display_recp[0] = 0;
582
583         ret->recptypes_magic = RECPTYPES_MAGIC;
584
585         /* Change all valid separator characters to commas */
586         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
587                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
588                         recipients[i] = ',';
589                 }
590         }
591
592         /* Now start extracting recipients... */
593
594         while (!IsEmptyStr(recipients)) {
595                 for (i=0; i<=strlen(recipients); ++i) {
596                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
597                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
598                                 safestrncpy(this_recp, recipients, i+1);
599                                 this_recp[i] = 0;
600                                 if (recipients[i] == ',') {
601                                         strcpy(recipients, &recipients[i+1]);
602                                 }
603                                 else {
604                                         strcpy(recipients, "");
605                                 }
606                                 break;
607                         }
608                 }
609
610                 striplt(this_recp);
611                 if (IsEmptyStr(this_recp))
612                         break;
613                 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
614                 ++num_recps;
615
616                 strcpy(org_recp, this_recp);
617                 alias(this_recp);
618                 alias(this_recp);
619                 mailtype = alias(this_recp);
620
621                 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
622                         if (this_recp[j]=='_') {
623                                 this_recp_cooked[j] = ' ';
624                         }
625                         else {
626                                 this_recp_cooked[j] = this_recp[j];
627                         }
628                 }
629                 this_recp_cooked[j] = '\0';
630                 invalid = 0;
631                 errmsg[0] = 0;
632                 switch(mailtype) {
633                 case MES_LOCAL:
634                         if (!strcasecmp(this_recp, "sysop")) {
635                                 ++ret->num_room;
636                                 strcpy(this_recp, config.c_aideroom);
637                                 if (!IsEmptyStr(ret->recp_room)) {
638                                         strcat(ret->recp_room, "|");
639                                 }
640                                 strcat(ret->recp_room, this_recp);
641                         }
642                         else if ( (!strncasecmp(this_recp, "room_", 5))
643                                   && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
644
645                                 /* Save room so we can restore it later */
646                                 tempQR2 = CCC->room;
647                                 CCC->room = tempQR;
648                                         
649                                 /* Check permissions to send mail to this room */
650                                 err = CtdlDoIHavePermissionToPostInThisRoom(
651                                         errmsg, 
652                                         sizeof errmsg, 
653                                         RemoteIdentifier,
654                                         Flags,
655                                         0                       /* 0 = not a reply */
656                                         );
657                                 if (err)
658                                 {
659                                         ++ret->num_error;
660                                         invalid = 1;
661                                 } 
662                                 else {
663                                         ++ret->num_room;
664                                         if (!IsEmptyStr(ret->recp_room)) {
665                                                 strcat(ret->recp_room, "|");
666                                         }
667                                         strcat(ret->recp_room, &this_recp_cooked[5]);
668
669                                         if (!IsEmptyStr(ret->recp_orgroom)) {
670                                                 strcat(ret->recp_orgroom, "|");
671                                         }
672                                         strcat(ret->recp_orgroom, org_recp);
673
674                                 }
675                                         
676                                 /* Restore room in case something needs it */
677                                 CCC->room = tempQR2;
678
679                         }
680                         else if (CtdlGetUser(&tempUS, this_recp) == 0) {
681                                 ++ret->num_local;
682                                 strcpy(this_recp, tempUS.fullname);
683                                 if (!IsEmptyStr(ret->recp_local)) {
684                                         strcat(ret->recp_local, "|");
685                                 }
686                                 strcat(ret->recp_local, this_recp);
687                         }
688                         else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
689                                 ++ret->num_local;
690                                 strcpy(this_recp, tempUS.fullname);
691                                 if (!IsEmptyStr(ret->recp_local)) {
692                                         strcat(ret->recp_local, "|");
693                                 }
694                                 strcat(ret->recp_local, this_recp);
695                         }
696                         else {
697                                 ++ret->num_error;
698                                 invalid = 1;
699                         }
700                         break;
701                 case MES_INTERNET:
702                         /* Yes, you're reading this correctly: if the target
703                          * domain points back to the local system or an attached
704                          * Citadel directory, the address is invalid.  That's
705                          * because if the address were valid, we would have
706                          * already translated it to a local address by now.
707                          */
708                         if (IsDirectory(this_recp, 0)) {
709                                 ++ret->num_error;
710                                 invalid = 1;
711                         }
712                         else {
713                                 ++ret->num_internet;
714                                 if (!IsEmptyStr(ret->recp_internet)) {
715                                         strcat(ret->recp_internet, "|");
716                                 }
717                                 strcat(ret->recp_internet, this_recp);
718                         }
719                         break;
720                 case MES_IGNET:
721                         ++ret->num_ignet;
722                         if (!IsEmptyStr(ret->recp_ignet)) {
723                                 strcat(ret->recp_ignet, "|");
724                         }
725                         strcat(ret->recp_ignet, this_recp);
726                         break;
727                 case MES_ERROR:
728                         ++ret->num_error;
729                         invalid = 1;
730                         break;
731                 }
732                 if (invalid) {
733                         if (IsEmptyStr(errmsg)) {
734                                 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
735                         }
736                         else {
737                                 snprintf(append, sizeof append, "%s", errmsg);
738                         }
739                         if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
740                                 if (!IsEmptyStr(ret->errormsg)) {
741                                         strcat(ret->errormsg, "; ");
742                                 }
743                                 strcat(ret->errormsg, append);
744                         }
745                 }
746                 else {
747                         if (IsEmptyStr(ret->display_recp)) {
748                                 strcpy(append, this_recp);
749                         }
750                         else {
751                                 snprintf(append, sizeof append, ", %s", this_recp);
752                         }
753                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
754                                 strcat(ret->display_recp, append);
755                         }
756                 }
757         }
758         free(org_recp);
759
760         if ((ret->num_local + ret->num_internet + ret->num_ignet +
761              ret->num_room + ret->num_error) == 0) {
762                 ret->num_error = (-1);
763                 strcpy(ret->errormsg, "No recipients specified.");
764         }
765
766         MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
767         MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
768         MSG_syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
769         MSG_syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
770         MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
771         MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
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                 struct CitContext *CCC = CC;
789                 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
790                 abort();
791         }
792
793         if (valid->errormsg != NULL)            free(valid->errormsg);
794         if (valid->recp_local != NULL)          free(valid->recp_local);
795         if (valid->recp_internet != NULL)       free(valid->recp_internet);
796         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
797         if (valid->recp_room != NULL)           free(valid->recp_room);
798         if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
799         if (valid->display_recp != NULL)        free(valid->display_recp);
800         if (valid->bounce_to != NULL)           free(valid->bounce_to);
801         if (valid->envelope_from != NULL)       free(valid->envelope_from);
802         if (valid->sending_room != NULL)        free(valid->sending_room);
803         free(valid);
804 }
805
806
807 char *qp_encode_email_addrs(char *source)
808 {
809         struct CitContext *CCC = CC;
810         char *user, *node, *name;
811         const char headerStr[] = "=?UTF-8?Q?";
812         char *Encoded;
813         char *EncodedName;
814         char *nPtr;
815         int need_to_encode = 0;
816         long SourceLen;
817         long EncodedMaxLen;
818         long nColons = 0;
819         long *AddrPtr;
820         long *AddrUtf8;
821         long nAddrPtrMax = 50;
822         long nmax;
823         int InQuotes = 0;
824         int i, n;
825
826         if (source == NULL) return source;
827         if (IsEmptyStr(source)) return source;
828         if (MessageDebugEnabled != 0) cit_backtrace();
829         MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
830
831         AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
832         AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
833         memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
834         *AddrPtr = 0;
835         i = 0;
836         while (!IsEmptyStr (&source[i])) {
837                 if (nColons >= nAddrPtrMax){
838                         long *ptr;
839
840                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
841                         memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
842                         free (AddrPtr), AddrPtr = ptr;
843
844                         ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
845                         memset(&ptr[nAddrPtrMax], 0, 
846                                sizeof (long) * nAddrPtrMax);
847
848                         memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
849                         free (AddrUtf8), AddrUtf8 = ptr;
850                         nAddrPtrMax *= 2;                               
851                 }
852                 if (((unsigned char) source[i] < 32) || 
853                     ((unsigned char) source[i] > 126)) {
854                         need_to_encode = 1;
855                         AddrUtf8[nColons] = 1;
856                 }
857                 if (source[i] == '"')
858                         InQuotes = !InQuotes;
859                 if (!InQuotes && source[i] == ',') {
860                         AddrPtr[nColons] = i;
861                         nColons++;
862                 }
863                 i++;
864         }
865         if (need_to_encode == 0) {
866                 free(AddrPtr);
867                 free(AddrUtf8);
868                 return source;
869         }
870
871         SourceLen = i;
872         EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
873         Encoded = (char*) malloc (EncodedMaxLen);
874
875         for (i = 0; i < nColons; i++)
876                 source[AddrPtr[i]++] = '\0';
877         /* TODO: if libidn, this might get larger*/
878         user = malloc(SourceLen + 1);
879         node = malloc(SourceLen + 1);
880         name = malloc(SourceLen + 1);
881
882         nPtr = Encoded;
883         *nPtr = '\0';
884         for (i = 0; i < nColons && nPtr != NULL; i++) {
885                 nmax = EncodedMaxLen - (nPtr - Encoded);
886                 if (AddrUtf8[i]) {
887                         process_rfc822_addr(&source[AddrPtr[i]], 
888                                             user,
889                                             node,
890                                             name);
891                         /* TODO: libIDN here ! */
892                         if (IsEmptyStr(name)) {
893                                 n = snprintf(nPtr, nmax, 
894                                              (i==0)?"%s@%s" : ",%s@%s",
895                                              user, node);
896                         }
897                         else {
898                                 EncodedName = rfc2047encode(name, strlen(name));                        
899                                 n = snprintf(nPtr, nmax, 
900                                              (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
901                                              EncodedName, user, node);
902                                 free(EncodedName);
903                         }
904                 }
905                 else { 
906                         n = snprintf(nPtr, nmax, 
907                                      (i==0)?"%s" : ",%s",
908                                      &source[AddrPtr[i]]);
909                 }
910                 if (n > 0 )
911                         nPtr += n;
912                 else { 
913                         char *ptr, *nnPtr;
914                         ptr = (char*) malloc(EncodedMaxLen * 2);
915                         memcpy(ptr, Encoded, EncodedMaxLen);
916                         nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
917                         free(Encoded), Encoded = ptr;
918                         EncodedMaxLen *= 2;
919                         i--; /* do it once more with properly lengthened buffer */
920                 }
921         }
922         for (i = 0; i < nColons; i++)
923                 source[--AddrPtr[i]] = ',';
924
925         free(user);
926         free(node);
927         free(name);
928         free(AddrUtf8);
929         free(AddrPtr);
930         return Encoded;
931 }
932
933
934 /*
935  * Return 0 if a given string fuzzy-matches a Citadel user account
936  *
937  * FIXME ... this needs to be updated to handle aliases.
938  */
939 int fuzzy_match(struct ctdluser *us, char *matchstring) {
940         int a;
941         long len;
942
943         if ( (!strncasecmp(matchstring, "cit", 3)) 
944            && (atol(&matchstring[3]) == us->usernum)) {
945                 return 0;
946         }
947
948         len = strlen(matchstring);
949         for (a=0; !IsEmptyStr(&us->fullname[a]); ++a) {
950                 if (!strncasecmp(&us->fullname[a],
951                    matchstring, len)) {
952                         return 0;
953                 }
954         }
955         return -1;
956 }
957
958
959 /*
960  * Unfold a multi-line field into a single line, removing multi-whitespaces
961  */
962 void unfold_rfc822_field(char **field, char **FieldEnd) 
963 {
964         int quote = 0;
965         char *pField = *field;
966         char *sField;
967         char *pFieldEnd = *FieldEnd;
968
969         while (isspace(*pField))
970                 pField++;
971         /* remove leading/trailing whitespace */
972         ;
973
974         while (isspace(*pFieldEnd))
975                 pFieldEnd --;
976
977         *FieldEnd = pFieldEnd;
978         /* convert non-space whitespace to spaces, and remove double blanks */
979         for (sField = *field = pField; 
980              sField < pFieldEnd; 
981              pField++, sField++)
982         {
983                 if ((*sField=='\r') || (*sField=='\n'))
984                 {
985                         int Offset = 1;
986                         while (((*(sField + Offset) == '\r') ||
987                                 (*(sField + Offset) == '\n') ||
988                                 (isspace(*(sField + Offset)))) && 
989                                (sField + Offset < pFieldEnd))
990                                 Offset ++;
991                         sField += Offset;
992                         *pField = *sField;
993                 }
994                 else {
995                         if (*sField=='\"') quote = 1 - quote;
996                         if (!quote) {
997                                 if (isspace(*sField))
998                                 {
999                                         *pField = ' ';
1000                                         pField++;
1001                                         sField++;
1002                                         
1003                                         while ((sField < pFieldEnd) && 
1004                                                isspace(*sField))
1005                                                 sField++;
1006                                         *pField = *sField;
1007                                 }
1008                                 else *pField = *sField;
1009                         }
1010                         else *pField = *sField;
1011                 }
1012         }
1013         *pField = '\0';
1014         *FieldEnd = pField - 1;
1015 }
1016
1017
1018
1019 /*
1020  * Split an RFC822-style address into userid, host, and full name
1021  *
1022  */
1023 void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name)
1024 {
1025         int a;
1026
1027         strcpy(user, "");
1028         strcpy(node, config.c_fqdn);
1029         strcpy(name, "");
1030
1031         if (rfc822 == NULL) return;
1032
1033         /* extract full name - first, it's From minus <userid> */
1034         strcpy(name, rfc822);
1035         stripout(name, '<', '>');
1036
1037         /* strip anything to the left of a bang */
1038         while ((!IsEmptyStr(name)) && (haschar(name, '!') > 0))
1039                 strcpy(name, &name[1]);
1040
1041         /* and anything to the right of a @ or % */
1042         for (a = 0; name[a] != '\0'; ++a) {
1043                 if (name[a] == '@') {
1044                         name[a] = 0;
1045                         break;
1046                 }
1047                 if (name[a] == '%') {
1048                         name[a] = 0;
1049                         break;
1050                 }
1051         }
1052
1053         /* but if there are parentheses, that changes the rules... */
1054         if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
1055                 strcpy(name, rfc822);
1056                 stripallbut(name, '(', ')');
1057         }
1058
1059         /* but if there are a set of quotes, that supersedes everything */
1060         if (haschar(rfc822, 34) == 2) {
1061                 strcpy(name, rfc822);
1062                 while ((!IsEmptyStr(name)) && (name[0] != 34)) {
1063                         strcpy(&name[0], &name[1]);
1064                 }
1065                 strcpy(&name[0], &name[1]);
1066                 for (a = 0; name[a] != '\0'; ++a)
1067                         if (name[a] == 34) {
1068                                 name[a] = 0;
1069                                 break;
1070                         }
1071         }
1072         /* extract user id */
1073         strcpy(user, rfc822);
1074
1075         /* first get rid of anything in parens */
1076         stripout(user, '(', ')');
1077
1078         /* if there's a set of angle brackets, strip it down to that */
1079         if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
1080                 stripallbut(user, '<', '>');
1081         }
1082
1083         /* strip anything to the left of a bang */
1084         while ((!IsEmptyStr(user)) && (haschar(user, '!') > 0))
1085                 strcpy(user, &user[1]);
1086
1087         /* and anything to the right of a @ or % */
1088         for (a = 0; user[a] != '\0'; ++a) {
1089                 if (user[a] == '@') {
1090                         user[a] = 0;
1091                         break;
1092                 }
1093                 if (user[a] == '%') {
1094                         user[a] = 0;
1095                         break;
1096                 }
1097         }
1098
1099
1100         /* extract node name */
1101         strcpy(node, rfc822);
1102
1103         /* first get rid of anything in parens */
1104         stripout(node, '(', ')');
1105
1106         /* if there's a set of angle brackets, strip it down to that */
1107         if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
1108                 stripallbut(node, '<', '>');
1109         }
1110
1111         /* If no node specified, tack ours on instead */
1112         if (
1113                 (haschar(node, '@')==0)
1114                 && (haschar(node, '%')==0)
1115                 && (haschar(node, '!')==0)
1116         ) {
1117                 strcpy(node, config.c_nodename);
1118         }
1119
1120         else {
1121
1122                 /* strip anything to the left of a @ */
1123                 while ((!IsEmptyStr(node)) && (haschar(node, '@') > 0))
1124                         strcpy(node, &node[1]);
1125         
1126                 /* strip anything to the left of a % */
1127                 while ((!IsEmptyStr(node)) && (haschar(node, '%') > 0))
1128                         strcpy(node, &node[1]);
1129         
1130                 /* reduce multiple system bang paths to node!user */
1131                 while ((!IsEmptyStr(node)) && (haschar(node, '!') > 1))
1132                         strcpy(node, &node[1]);
1133         
1134                 /* now get rid of the user portion of a node!user string */
1135                 for (a = 0; node[a] != '\0'; ++a)
1136                         if (node[a] == '!') {
1137                                 node[a] = 0;
1138                                 break;
1139                         }
1140         }
1141
1142         /* strip leading and trailing spaces in all strings */
1143         striplt(user);
1144         striplt(node);
1145         striplt(name);
1146
1147         /* If we processed a string that had the address in angle brackets
1148          * but no name outside the brackets, we now have an empty name.  In
1149          * this case, use the user portion of the address as the name.
1150          */
1151         if ((IsEmptyStr(name)) && (!IsEmptyStr(user))) {
1152                 strcpy(name, user);
1153         }
1154 }
1155
1156
1157
1158 /*
1159  * convert_field() is a helper function for convert_internet_message().
1160  * Given start/end positions for an rfc822 field, it converts it to a Citadel
1161  * field if it wants to, and unfolds it if necessary.
1162  *
1163  * Returns 1 if the field was converted and inserted into the Citadel message
1164  * structure, implying that the source field should be removed from the
1165  * message text.
1166  */
1167 int convert_field(struct CtdlMessage *msg, const char *beg, const char *end) {
1168         char *key, *value, *valueend;
1169         long len;
1170         const char *pos;
1171         int i;
1172         const char *colonpos = NULL;
1173         int processed = 0;
1174         char user[1024];
1175         char node[1024];
1176         char name[1024];
1177         char addr[1024];
1178         time_t parsed_date;
1179         long valuelen;
1180
1181         for (pos = end; pos >= beg; pos--) {
1182                 if (*pos == ':') colonpos = pos;
1183         }
1184
1185         if (colonpos == NULL) return(0);        /* no colon? not a valid header line */
1186
1187         len = end - beg;
1188         key = malloc(len + 2);
1189         memcpy(key, beg, len + 1);
1190         key[len] = '\0';
1191         valueend = key + len;
1192         * ( key + (colonpos - beg) ) = '\0';
1193         value = &key[(colonpos - beg) + 1];
1194 /*      printf("Header: [%s]\nValue: [%s]\n", key, value); */
1195         unfold_rfc822_field(&value, &valueend);
1196         valuelen = valueend - value + 1;
1197 /*      printf("UnfoldedValue: [%s]\n", value); */
1198
1199         /*
1200          * Here's the big rfc822-to-citadel loop.
1201          */
1202
1203         /* Date/time is converted into a unix timestamp.  If the conversion
1204          * fails, we replace it with the time the message arrived locally.
1205          */
1206         if (!strcasecmp(key, "Date")) {
1207                 parsed_date = parsedate(value);
1208                 if (parsed_date < 0L) parsed_date = time(NULL);
1209
1210                 if (CM_IsEmpty(msg, eTimestamp))
1211                         CM_SetFieldLONG(msg, eTimestamp, parsed_date);
1212                 processed = 1;
1213         }
1214
1215         else if (!strcasecmp(key, "From")) {
1216                 process_rfc822_addr(value, user, node, name);
1217                 syslog(LOG_DEBUG, "Converted to <%s@%s> (%s)\n", user, node, name);
1218                 snprintf(addr, sizeof(addr), "%s@%s", user, node);
1219                 if (CM_IsEmpty(msg, eAuthor))
1220                         CM_SetField(msg, eAuthor, name, strlen(name));
1221                 if (CM_IsEmpty(msg, erFc822Addr))
1222                         CM_SetField(msg, erFc822Addr, addr, strlen(addr));
1223                 processed = 1;
1224         }
1225
1226         else if (!strcasecmp(key, "Subject")) {
1227                 if (CM_IsEmpty(msg, eMsgSubject))
1228                         CM_SetField(msg, eMsgSubject, value, valuelen);
1229                 processed = 1;
1230         }
1231
1232         else if (!strcasecmp(key, "List-ID")) {
1233                 if (CM_IsEmpty(msg, eListID))
1234                         CM_SetField(msg, eListID, value, valuelen);
1235                 processed = 1;
1236         }
1237
1238         else if (!strcasecmp(key, "To")) {
1239                 if (CM_IsEmpty(msg, eRecipient))
1240                         CM_SetField(msg, eRecipient, value, valuelen);
1241                 processed = 1;
1242         }
1243
1244         else if (!strcasecmp(key, "CC")) {
1245                 if (CM_IsEmpty(msg, eCarbonCopY))
1246                         CM_SetField(msg, eCarbonCopY, value, valuelen);
1247                 processed = 1;
1248         }
1249
1250         else if (!strcasecmp(key, "Message-ID")) {
1251                 if (!CM_IsEmpty(msg, emessageId)) {
1252                         syslog(LOG_WARNING, "duplicate message id\n");
1253                 }
1254                 else {
1255                         char *pValue;
1256                         long pValueLen;
1257
1258                         pValue = value;
1259                         pValueLen = valuelen;
1260                         /* Strip angle brackets */
1261                         while (haschar(pValue, '<') > 0) {
1262                                 pValue ++;
1263                                 pValueLen --;
1264                         }
1265
1266                         for (i = 0; i <= pValueLen; ++i)
1267                                 if (pValue[i] == '>') {
1268                                         pValueLen = i;
1269                                         break;
1270                                 }
1271
1272                         CM_SetField(msg, emessageId, pValue, pValueLen);
1273                 }
1274
1275                 processed = 1;
1276         }
1277
1278         else if (!strcasecmp(key, "Return-Path")) {
1279                 if (CM_IsEmpty(msg, eMessagePath))
1280                         CM_SetField(msg, eMessagePath, value, valuelen);
1281                 processed = 1;
1282         }
1283
1284         else if (!strcasecmp(key, "Envelope-To")) {
1285                 if (CM_IsEmpty(msg, eenVelopeTo))
1286                         CM_SetField(msg, eenVelopeTo, value, valuelen);
1287                 processed = 1;
1288         }
1289
1290         else if (!strcasecmp(key, "References")) {
1291                 CM_SetField(msg, eWeferences, value, valuelen);
1292                 processed = 1;
1293         }
1294
1295         else if (!strcasecmp(key, "Reply-To")) {
1296                 CM_SetField(msg, eReplyTo, value, valuelen);
1297                 processed = 1;
1298         }
1299
1300         else if (!strcasecmp(key, "In-reply-to")) {
1301                 if (CM_IsEmpty(msg, eWeferences)) /* References: supersedes In-reply-to: */
1302                         CM_SetField(msg, eWeferences, value, valuelen);
1303                 processed = 1;
1304         }
1305
1306
1307
1308         /* Clean up and move on. */
1309         free(key);      /* Don't free 'value', it's actually the same buffer */
1310         return processed;
1311 }
1312
1313
1314 /*
1315  * Convert RFC822 references format (References) to Citadel references format (Weferences)
1316  */
1317 void convert_references_to_wefewences(char *str) {
1318         int bracket_nesting = 0;
1319         char *ptr = str;
1320         char *moveptr = NULL;
1321         char ch;
1322
1323         while(*ptr) {
1324                 ch = *ptr;
1325                 if (ch == '>') {
1326                         --bracket_nesting;
1327                         if (bracket_nesting < 0) bracket_nesting = 0;
1328                 }
1329                 if ((ch == '>') && (bracket_nesting == 0) && (*(ptr+1)) && (ptr>str) ) {
1330                         *ptr = '|';
1331                         ++ptr;
1332                 }
1333                 else if (bracket_nesting > 0) {
1334                         ++ptr;
1335                 }
1336                 else {
1337                         moveptr = ptr;
1338                         while (*moveptr) {
1339                                 *moveptr = *(moveptr+1);
1340                                 ++moveptr;
1341                         }
1342                 }
1343                 if (ch == '<') ++bracket_nesting;
1344         }
1345
1346 }
1347
1348
1349 /*
1350  * Convert an RFC822 message (headers + body) to a CtdlMessage structure.
1351  * NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
1352  * will be deallocated when CM_Free() is called.  Therefore, the
1353  * supplied buffer should be DEREFERENCED.  It should not be freed or used
1354  * again.
1355  */
1356 struct CtdlMessage *convert_internet_message(char *rfc822) {
1357         StrBuf *RFCBuf = NewStrBufPlain(rfc822, -1);
1358         free (rfc822);
1359         return convert_internet_message_buf(&RFCBuf);
1360 }
1361
1362
1363
1364 struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822)
1365 {
1366         struct CtdlMessage *msg;
1367         const char *pos, *beg, *end, *totalend;
1368         int done, alldone = 0;
1369         int converted;
1370         StrBuf *OtherHeaders;
1371
1372         msg = malloc(sizeof(struct CtdlMessage));
1373         if (msg == NULL) return msg;
1374
1375         memset(msg, 0, sizeof(struct CtdlMessage));
1376         msg->cm_magic = CTDLMESSAGE_MAGIC;      /* self check */
1377         msg->cm_anon_type = 0;                  /* never anonymous */
1378         msg->cm_format_type = FMT_RFC822;       /* internet message */
1379
1380         pos = ChrPtr(*rfc822);
1381         totalend = pos + StrLength(*rfc822);
1382         done = 0;
1383         OtherHeaders = NewStrBufPlain(NULL, StrLength(*rfc822));
1384
1385         while (!alldone) {
1386
1387                 /* Locate beginning and end of field, keeping in mind that
1388                  * some fields might be multiline
1389                  */
1390                 end = beg = pos;
1391
1392                 while ((end < totalend) && 
1393                        (end == beg) && 
1394                        (done == 0) ) 
1395                 {
1396
1397                         if ( (*pos=='\n') && ((*(pos+1))!=0x20) && ((*(pos+1))!=0x09) )
1398                         {
1399                                 end = pos;
1400                         }
1401
1402                         /* done with headers? */
1403                         if ((*pos=='\n') &&
1404                             ( (*(pos+1)=='\n') ||
1405                               (*(pos+1)=='\r')) ) 
1406                         {
1407                                 alldone = 1;
1408                         }
1409
1410                         if (pos >= (totalend - 1) )
1411                         {
1412                                 end = pos;
1413                                 done = 1;
1414                         }
1415
1416                         ++pos;
1417
1418                 }
1419
1420                 /* At this point we have a field.  Are we interested in it? */
1421                 converted = convert_field(msg, beg, end);
1422
1423                 /* Strip the field out of the RFC822 header if we used it */
1424                 if (!converted) {
1425                         StrBufAppendBufPlain(OtherHeaders, beg, end - beg, 0);
1426                         StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
1427                 }
1428
1429                 /* If we've hit the end of the message, bail out */
1430                 if (pos >= totalend)
1431                         alldone = 1;
1432         }
1433         StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
1434         if (pos < totalend)
1435                 StrBufAppendBufPlain(OtherHeaders, pos, totalend - pos, 0);
1436         FreeStrBuf(rfc822);
1437         CM_SetAsFieldSB(msg, eMesageText, &OtherHeaders);
1438
1439         /* Follow-up sanity checks... */
1440
1441         /* If there's no timestamp on this message, set it to now. */
1442         if (CM_IsEmpty(msg, eTimestamp)) {
1443                 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
1444         }
1445
1446         /* If a W (references, or rather, Wefewences) field is present, we
1447          * have to convert it from RFC822 format to Citadel format.
1448          */
1449         if (!CM_IsEmpty(msg, eWeferences)) {
1450                 /// todo: API!
1451                 convert_references_to_wefewences(msg->cm_fields[eWeferences]);
1452         }
1453
1454         return msg;
1455 }
1456
1457
1458
1459 /*
1460  * Look for a particular header field in an RFC822 message text.  If the
1461  * requested field is found, it is unfolded (if necessary) and returned to
1462  * the caller.  The field name is stripped out, leaving only its contents.
1463  * The caller is responsible for freeing the returned buffer.  If the requested
1464  * field is not present, or anything else goes wrong, it returns NULL.
1465  */
1466 char *rfc822_fetch_field(const char *rfc822, const char *fieldname) {
1467         char *fieldbuf = NULL;
1468         const char *end_of_headers;
1469         const char *field_start;
1470         const char *ptr;
1471         char *cont;
1472         char fieldhdr[SIZ];
1473
1474         /* Should never happen, but sometimes we get stupid */
1475         if (rfc822 == NULL) return(NULL);
1476         if (fieldname == NULL) return(NULL);
1477
1478         snprintf(fieldhdr, sizeof fieldhdr, "%s:", fieldname);
1479
1480         /* Locate the end of the headers, so we don't run past that point */
1481         end_of_headers = cbmstrcasestr(rfc822, "\n\r\n");
1482         if (end_of_headers == NULL) {
1483                 end_of_headers = cbmstrcasestr(rfc822, "\n\n");
1484         }
1485         if (end_of_headers == NULL) return (NULL);
1486
1487         field_start = cbmstrcasestr(rfc822, fieldhdr);
1488         if (field_start == NULL) return(NULL);
1489         if (field_start > end_of_headers) return(NULL);
1490
1491         fieldbuf = malloc(SIZ);
1492         strcpy(fieldbuf, "");
1493
1494         ptr = field_start;
1495         ptr = cmemreadline(ptr, fieldbuf, SIZ-strlen(fieldbuf) );
1496         while ( (isspace(ptr[0])) && (ptr < end_of_headers) ) {
1497                 strcat(fieldbuf, " ");
1498                 cont = &fieldbuf[strlen(fieldbuf)];
1499                 ptr = cmemreadline(ptr, cont, SIZ-strlen(fieldbuf) );
1500                 striplt(cont);
1501         }
1502
1503         strcpy(fieldbuf, &fieldbuf[strlen(fieldhdr)]);
1504         striplt(fieldbuf);
1505
1506         return(fieldbuf);
1507 }
1508
1509
1510
1511 /*****************************************************************************
1512  *                      DIRECTORY MANAGEMENT FUNCTIONS                       *
1513  *****************************************************************************/
1514
1515 /*
1516  * Generate the index key for an Internet e-mail address to be looked up
1517  * in the database.
1518  */
1519 void directory_key(char *key, char *addr) {
1520         int i;
1521         int keylen = 0;
1522
1523         for (i=0; !IsEmptyStr(&addr[i]); ++i) {
1524                 if (!isspace(addr[i])) {
1525                         key[keylen++] = tolower(addr[i]);
1526                 }
1527         }
1528         key[keylen++] = 0;
1529
1530         syslog(LOG_DEBUG, "Directory key is <%s>\n", key);
1531 }
1532
1533
1534
1535 /* Return nonzero if the supplied address is in a domain we keep in
1536  * the directory
1537  */
1538 int IsDirectory(char *addr, int allow_masq_domains) {
1539         char domain[256];
1540         int h;
1541
1542         extract_token(domain, addr, 1, '@', sizeof domain);
1543         striplt(domain);
1544
1545         h = CtdlHostAlias(domain);
1546
1547         if ( (h == hostalias_masq) && allow_masq_domains)
1548                 return(1);
1549         
1550         if ( (h == hostalias_localhost) || (h == hostalias_directory) ) {
1551                 return(1);
1552         }
1553         else {
1554                 return(0);
1555         }
1556 }
1557
1558
1559 /*
1560  * Initialize the directory database (erasing anything already there)
1561  */
1562 void CtdlDirectoryInit(void) {
1563         cdb_trunc(CDB_DIRECTORY);
1564 }
1565
1566
1567 /*
1568  * Add an Internet e-mail address to the directory for a user
1569  */
1570 int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr) {
1571         char key[SIZ];
1572
1573         if (IsDirectory(internet_addr, 0) == 0) 
1574                 return 0;
1575         syslog(LOG_DEBUG, "Create directory entry: %s --> %s\n", internet_addr, citadel_addr);
1576         directory_key(key, internet_addr);
1577         cdb_store(CDB_DIRECTORY, key, strlen(key), citadel_addr, strlen(citadel_addr)+1 );
1578         return 1;
1579 }
1580
1581
1582 /*
1583  * Delete an Internet e-mail address from the directory.
1584  *
1585  * (NOTE: we don't actually use or need the citadel_addr variable; it's merely
1586  * here because the callback API expects to be able to send it.)
1587  */
1588 int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr) {
1589         char key[SIZ];
1590
1591         syslog(LOG_DEBUG, "Delete directory entry: %s --> %s\n", internet_addr, citadel_addr);
1592         directory_key(key, internet_addr);
1593         return cdb_delete(CDB_DIRECTORY, key, strlen(key) ) == 0;
1594 }
1595
1596
1597 /*
1598  * Look up an Internet e-mail address in the directory.
1599  * On success: returns 0, and Citadel address stored in 'target'
1600  * On failure: returns nonzero
1601  */
1602 int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen) {
1603         struct cdbdata *cdbrec;
1604         char key[SIZ];
1605
1606         /* Dump it in there unchanged, just for kicks */
1607         safestrncpy(target, internet_addr, targbuflen);
1608
1609         /* Only do lookups for addresses with hostnames in them */
1610         if (num_tokens(internet_addr, '@') != 2) return(-1);
1611
1612         /* Only do lookups for domains in the directory */
1613         if (IsDirectory(internet_addr, 0) == 0) return(-1);
1614
1615         directory_key(key, internet_addr);
1616         cdbrec = cdb_fetch(CDB_DIRECTORY, key, strlen(key) );
1617         if (cdbrec != NULL) {
1618                 safestrncpy(target, cdbrec->ptr, targbuflen);
1619                 cdb_free(cdbrec);
1620                 return(0);
1621         }
1622
1623         return(-1);
1624 }
1625
1626
1627 /*
1628  * Harvest any email addresses that someone might want to have in their
1629  * "collected addresses" book.
1630  */
1631 char *harvest_collected_addresses(struct CtdlMessage *msg) {
1632         char *coll = NULL;
1633         char addr[256];
1634         char user[256], node[256], name[256];
1635         int is_harvestable;
1636         int i, j, h;
1637         eMsgField field = 0;
1638
1639         if (msg == NULL) return(NULL);
1640
1641         is_harvestable = 1;
1642         strcpy(addr, "");       
1643         if (!CM_IsEmpty(msg, eAuthor)) {
1644                 strcat(addr, msg->cm_fields[eAuthor]);
1645         }
1646         if (!CM_IsEmpty(msg, erFc822Addr)) {
1647                 strcat(addr, " <");
1648                 strcat(addr, msg->cm_fields[erFc822Addr]);
1649                 strcat(addr, ">");
1650                 if (IsDirectory(msg->cm_fields[erFc822Addr], 0)) {
1651                         is_harvestable = 0;
1652                 }
1653         }
1654
1655         if (is_harvestable) {
1656                 coll = strdup(addr);
1657         }
1658         else {
1659                 coll = strdup("");
1660         }
1661
1662         if (coll == NULL) return(NULL);
1663
1664         /* Scan both the R (To) and Y (CC) fields */
1665         for (i = 0; i < 2; ++i) {
1666                 if (i == 0) field = eRecipient;
1667                 if (i == 1) field = eCarbonCopY;
1668
1669                 if (!CM_IsEmpty(msg, field)) {
1670                         for (j=0; j<num_tokens(msg->cm_fields[field], ','); ++j) {
1671                                 extract_token(addr, msg->cm_fields[field], j, ',', sizeof addr);
1672                                 if (strstr(addr, "=?") != NULL)
1673                                         utf8ify_rfc822_string(addr);
1674                                 process_rfc822_addr(addr, user, node, name);
1675                                 h = CtdlHostAlias(node);
1676                                 if ( (h != hostalias_localhost) && (h != hostalias_directory) ) {
1677                                         coll = realloc(coll, strlen(coll) + strlen(addr) + 4);
1678                                         if (coll == NULL) return(NULL);
1679                                         if (!IsEmptyStr(coll)) {
1680                                                 strcat(coll, ",");
1681                                         }
1682                                         striplt(addr);
1683                                         strcat(coll, addr);
1684                                 }
1685                         }
1686                 }
1687         }
1688
1689         if (IsEmptyStr(coll)) {
1690                 free(coll);
1691                 return(NULL);
1692         }
1693         return(coll);
1694 }