5e0676b84cf331c1bfbf926dfb618104603daaef
[citadel.git] / citadel / modules / network / serv_netmail.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2012 by the citadel.org team
6  *
7  *  This program is open source software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License, version 3.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
16  * This is a fairly high-level type of critical section.  It ensures that no
17  * two threads work on the netconfigs files at the same time.  Since we do
18  * so many things inside these, here are the rules:
19  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
20  *  2. Do *not* perform any I/O with the client during these sections.
21  *
22  */
23
24 /*
25  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
26  * requests that have not been confirmed will be deleted.
27  */
28 #define EXP     259200  /* three days */
29
30 #include "sysdep.h"
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <stdio.h>
34 #include <fcntl.h>
35 #include <ctype.h>
36 #include <signal.h>
37 #include <pwd.h>
38 #include <errno.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <dirent.h>
42 #if TIME_WITH_SYS_TIME
43 # include <sys/time.h>
44 # include <time.h>
45 #else
46 # if HAVE_SYS_TIME_H
47 #  include <sys/time.h>
48 # else
49 #  include <time.h>
50 # endif
51 #endif
52 #ifdef HAVE_SYSCALL_H
53 # include <syscall.h>
54 #else
55 # if HAVE_SYS_SYSCALL_H
56 #  include <sys/syscall.h>
57 # endif
58 #endif
59
60 #include <sys/wait.h>
61 #include <string.h>
62 #include <limits.h>
63 #include <libcitadel.h>
64 #include "citadel.h"
65 #include "server.h"
66 #include "citserver.h"
67 #include "support.h"
68 #include "config.h"
69 #include "user_ops.h"
70 #include "database.h"
71 #include "msgbase.h"
72 #include "internet_addressing.h"
73 #include "serv_network.h"
74 #include "clientsocket.h"
75 #include "file_ops.h"
76 #include "citadel_dirs.h"
77 #include "threads.h"
78 #include "context.h"
79 #include "ctdl_module.h"
80 #include "netspool.h"
81 #include "netmail.h"
82
83 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName);
84
85 void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg)
86 {
87         size_t recps_len = 0;
88         RoomNetCfgLine *nptr;
89         struct CitContext *CCC = CC;
90
91         /*
92          * Figure out how big a buffer we need to allocate
93          */
94         for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
95                 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
96         }
97
98         *recps = NewStrBufPlain(NULL, recps_len);
99
100         if (*recps == NULL) {
101                 QN_syslog(LOG_EMERG,
102                           "Cannot allocate %ld bytes for recps...\n",
103                           (long)recps_len);
104                 abort();
105         }
106
107         /* Each recipient */
108         for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
109                 if (nptr != OneRNCfg->NetConfigs[Which]) {
110                         StrBufAppendBufPlain(*recps, HKEY(","), 0);
111                 }
112                 StrBufAppendBuf(*recps, nptr->Value[0], 0);
113         }
114 }
115
116 /*
117  * Deliver digest messages
118  */
119 void network_deliver_digest(SpoolControl *sc)
120 {
121         char buf[SIZ];
122         int i;
123         struct CtdlMessage *msg = NULL;
124         long msglen;
125         StrBuf *recps = NULL;
126         char *precps;
127         struct recptypes *valid;
128         char bounce_to[256];
129
130         if (sc->num_msgs_spooled < 1) {
131                 fclose(sc->digestfp);
132                 sc->digestfp = NULL;
133                 return;
134         }
135
136         msg = malloc(sizeof(struct CtdlMessage));
137         memset(msg, 0, sizeof(struct CtdlMessage));
138         msg->cm_magic = CTDLMESSAGE_MAGIC;
139         msg->cm_format_type = FMT_RFC822;
140         msg->cm_anon_type = MES_NORMAL;
141
142         sprintf(buf, "%ld", time(NULL));
143         msg->cm_fields['T'] = strdup(buf);
144         msg->cm_fields['A'] = strdup(CC->room.QRname);
145         snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
146         msg->cm_fields['U'] = strdup(buf);
147         sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
148         for (i=0; buf[i]; ++i) {
149                 if (isspace(buf[i])) buf[i]='_';
150                 buf[i] = tolower(buf[i]);
151         }
152         msg->cm_fields['F'] = strdup(buf);
153         msg->cm_fields['R'] = strdup(buf);
154
155         /* Set the 'List-ID' header */
156         msg->cm_fields['L'] = malloc(1024);
157         snprintf(msg->cm_fields['L'], 1024,
158                  "%s <%ld.list-id.%s>",
159                  CC->room.QRname,
160                  CC->room.QRnumber,
161                  config.c_fqdn
162                 );
163
164         /*
165          * Go fetch the contents of the digest
166          */
167         fseek(sc->digestfp, 0L, SEEK_END);
168         msglen = ftell(sc->digestfp);
169
170         msg->cm_fields['M'] = malloc(msglen + 1);
171         fseek(sc->digestfp, 0L, SEEK_SET);
172         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
173         msg->cm_fields['M'][msglen] = '\0';
174
175         fclose(sc->digestfp);
176         sc->digestfp = NULL;
177
178         /* Now generate the delivery instructions */
179         aggregate_recipients(&recps, digestrecp, sc->RNCfg);
180
181         /* Where do we want bounces and other noise to be heard?
182          *Surely not the list members! */
183         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
184
185         /* Now submit the message */
186         precps = SmashStrBuf(&recps);
187         valid = validate_recipients(precps, NULL, 0);
188         free(precps);
189         if (valid != NULL) {
190                 valid->bounce_to = strdup(bounce_to);
191                 valid->envelope_from = strdup(bounce_to);
192                 CtdlSubmitMsg(msg, valid, NULL, 0);
193         }
194         CtdlFreeMessage(msg);
195         free_recipients(valid);
196 }
197
198
199 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
200 {
201
202         struct CtdlMessage *msg = NULL;
203
204         /*
205          * Process digest recipients
206          */
207         if ((sc->RNCfg->NetConfigs[digestrecp] == NULL) || 
208             (sc->digestfp == NULL))
209                 return;
210
211         msg = CtdlDuplicateMessage(omsg);
212         if (msg != NULL) {
213                 fprintf(sc->digestfp,
214                         " -----------------------------------"
215                         "------------------------------------"
216                         "-------\n");
217                 fprintf(sc->digestfp, "From: ");
218                 if (msg->cm_fields['A'] != NULL) {
219                         fprintf(sc->digestfp,
220                                 "%s ",
221                                 msg->cm_fields['A']);
222                 }
223                 if (msg->cm_fields['F'] != NULL) {
224                         fprintf(sc->digestfp,
225                                 "<%s> ",
226                                 msg->cm_fields['F']);
227                 }
228                 else if (msg->cm_fields['N'] != NULL) {
229                         fprintf(sc->digestfp,
230                                 "@%s ",
231                                 msg->cm_fields['N']);
232                 }
233                 fprintf(sc->digestfp, "\n");
234                 if (msg->cm_fields['U'] != NULL) {
235                         fprintf(sc->digestfp,
236                                 "Subject: %s\n",
237                                 msg->cm_fields['U']);
238                 }
239
240                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
241
242                 safestrncpy(CC->preferred_formats,
243                             "text/plain",
244                             sizeof CC->preferred_formats);
245
246                 CtdlOutputPreLoadedMsg(msg,
247                                        MT_CITADEL,
248                                        HEADERS_NONE,
249                                        0, 0, 0);
250
251                 StrBufTrim(CC->redirect_buffer);
252                 fwrite(HKEY("\n"), 1, sc->digestfp);
253                 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
254                 fwrite(HKEY("\n"), 1, sc->digestfp);
255
256                 FreeStrBuf(&CC->redirect_buffer);
257
258                 sc->num_msgs_spooled += 1;
259                 CtdlFreeMessage(msg);
260         }
261 }
262
263
264 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
265 {
266         int rlen;
267         char *pCh;
268         StrBuf *Subject, *FlatSubject;
269         struct CtdlMessage *msg = NULL;
270         int i;
271
272         /*
273          * Process mailing list recipients
274          */
275         if (sc->RNCfg->NetConfigs[listrecp] == NULL) 
276                 return;
277
278         /* create our own copy of the message.
279          *  We're going to need to modify it
280          * in order to insert the [list name] in it, etc.
281          */
282
283         msg = CtdlDuplicateMessage(omsg);
284
285         if (msg->cm_fields['K'] != NULL)
286                 free(msg->cm_fields['K']);
287         if (msg->cm_fields['V'] == NULL){
288                 /* local message, no enVelope */
289                 StrBuf *Buf;
290                 Buf = NewStrBuf();
291                 StrBufAppendBufPlain(Buf,
292                                      msg->cm_fields['O']
293                                      , -1, 0);
294                 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
295                 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
296
297                 msg->cm_fields['K'] = SmashStrBuf(&Buf);
298         }
299         else {
300                 msg->cm_fields['K'] =
301                         strdup (msg->cm_fields['V']);
302         }
303         /* Set the 'List-ID' header */
304         if (msg->cm_fields['L'] != NULL) {
305                 free(msg->cm_fields['L']);
306         }
307         msg->cm_fields['L'] = malloc(1024);
308         snprintf(msg->cm_fields['L'], 1024,
309                  "%s <%ld.list-id.%s>",
310                  CC->room.QRname,
311                  CC->room.QRnumber,
312                  config.c_fqdn
313                 );
314
315         /* Prepend "[List name]" to the subject */
316         if (msg->cm_fields['U'] == NULL) {
317                 Subject = NewStrBufPlain(HKEY("(no subject)"));
318         }
319         else {
320                 Subject = NewStrBufPlain(
321                         msg->cm_fields['U'], -1);
322         }
323         FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
324         StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
325
326         rlen = strlen(CC->room.QRname);
327         pCh  = strstr(ChrPtr(FlatSubject), CC->room.QRname);
328         if ((pCh == NULL) ||
329             (*(pCh + rlen) != ']') ||
330             (pCh == ChrPtr(FlatSubject)) ||
331             (*(pCh - 1) != '[')
332                 )
333         {
334                 StrBuf *tmp;
335                 StrBufPlain(Subject, HKEY("["));
336                 StrBufAppendBufPlain(Subject,
337                                      CC->room.QRname,
338                                      rlen, 0);
339                 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
340                 StrBufAppendBuf(Subject, FlatSubject, 0);
341                 /* so we can free the right one swap them */
342                 tmp = Subject;
343                 Subject = FlatSubject;
344                 FlatSubject = tmp;
345                 StrBufRFC2047encode(&Subject, FlatSubject);
346         }
347
348         if (msg->cm_fields['U'] != NULL)
349                 free (msg->cm_fields['U']);
350         msg->cm_fields['U'] = SmashStrBuf(&Subject);
351
352         FreeStrBuf(&FlatSubject);
353
354         /* else we won't modify the buffer, since the
355          * roomname is already here.
356          */
357
358         /* if there is no other recipient, Set the recipient
359          * of the list message to the email address of the
360          * room itself.
361          */
362         if ((msg->cm_fields['R'] == NULL) ||
363             IsEmptyStr(msg->cm_fields['R']))
364         {
365                 if (msg->cm_fields['R'] != NULL)
366                         free(msg->cm_fields['R']);
367
368                 msg->cm_fields['R'] = malloc(256);
369                 snprintf(msg->cm_fields['R'], 256,
370                          "room_%s@%s", CC->room.QRname,
371                          config.c_fqdn);
372                 for (i=0; msg->cm_fields['R'][i]; ++i) {
373                         if (isspace(msg->cm_fields['R'][i])) {
374                                 msg->cm_fields['R'][i] = '_';
375                         }
376                 }
377         }
378
379         /* Handle delivery */
380         network_deliver_list(msg, sc, CC->room.QRname);
381         CtdlFreeMessage(msg);
382 }
383
384 /*
385  * Deliver list messages to everyone on the list ... efficiently
386  */
387 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
388 {
389         StrBuf *recps = NULL;
390         char *precps = NULL;
391         struct recptypes *valid;
392         char bounce_to[256];
393
394         /* Don't do this if there were no recipients! */
395         if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
396
397         /* Now generate the delivery instructions */
398
399         /*
400          * Figure out how big a buffer we need to allocate
401          */
402         aggregate_recipients(&recps, listrecp, sc->RNCfg);
403
404         /* Where do we want bounces and other noise to be heard?
405          *  Surely not the list members! */
406         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
407
408         /* Now submit the message */
409         precps = SmashStrBuf(&recps);
410         valid = validate_recipients(precps, NULL, 0);
411         free(precps);
412         if (valid != NULL) {
413                 valid->bounce_to = strdup(bounce_to);
414                 valid->envelope_from = strdup(bounce_to);
415                 valid->sending_room = strdup(RoomName);
416                 CtdlSubmitMsg(msg, valid, NULL, 0);
417                 free_recipients(valid);
418         }
419         /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
420 }
421
422
423 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
424 {
425         struct CtdlMessage *msg = NULL;
426         int i;
427         int ok_to_participate = 0;
428         StrBuf *Buf = NULL;
429         RoomNetCfgLine *nptr;
430         struct recptypes *valid;
431
432         /*
433          * Process client-side list participations for this room
434          */
435         if (sc->RNCfg->NetConfigs[participate] == NULL)
436                 return;
437
438         msg = CtdlDuplicateMessage(omsg);
439
440         /* Only send messages which originated on our own
441          * Citadel network, otherwise we'll end up sending the
442          * remote mailing list's messages back to it, which
443          * is rude...
444          */
445         ok_to_participate = 0;
446         if (msg->cm_fields['N'] != NULL) {
447                 if (!strcasecmp(msg->cm_fields['N'],
448                                 config.c_nodename)) {
449                         ok_to_participate = 1;
450                 }
451                 
452                 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
453                 if (CtdlIsValidNode(NULL,
454                                     NULL,
455                                     Buf,
456                                     sc->working_ignetcfg,
457                                     sc->the_netmap) == 0)
458                 {
459                         ok_to_participate = 1;
460                 }
461         }
462         if (ok_to_participate) {
463                 if (msg->cm_fields['F'] != NULL) {
464                         free(msg->cm_fields['F']);
465                 }
466                 msg->cm_fields['F'] = malloc(SIZ);
467                 /* Replace the Internet email address of the
468                  * actual author with the email address of the
469                  * room itself, so the remote listserv doesn't
470                  * reject us.
471                  * FIXME  I want to be able to pick any address
472                  */
473                 snprintf(msg->cm_fields['F'], SIZ,
474                          "room_%s@%s", CC->room.QRname,
475                          config.c_fqdn);
476                 for (i=0; msg->cm_fields['F'][i]; ++i) {
477                         if (isspace(msg->cm_fields['F'][i])) {
478                                 msg->cm_fields['F'][i] = '_';
479                         }
480                 }
481                 
482                 /*
483                  * Figure out how big a buffer we need to alloc
484                  */
485                 for (nptr = sc->RNCfg->NetConfigs[participate];
486                      nptr != NULL;
487                      nptr = nptr->next)
488                 {
489                         if (msg->cm_fields['R'] != NULL) {
490                                 free(msg->cm_fields['R']);
491                         }
492                         msg->cm_fields['R'] =
493                                 strdup(ChrPtr(nptr->Value[0]));
494                         
495                         valid = validate_recipients(msg->cm_fields['R'],
496                                                     NULL, 0);
497                         
498                         CtdlSubmitMsg(msg, valid, "", 0);
499                         free_recipients(valid);
500                 }
501         }
502         FreeStrBuf(&Buf);
503         CtdlFreeMessage(msg);
504 }
505
506 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
507 {
508         struct CtdlMessage *msg = NULL;
509         struct CitContext *CCC = CC;
510         struct ser_ret sermsg;
511         char buf[SIZ];
512         char filename[PATH_MAX];
513         FILE *fp;
514         size_t newpath_len;
515         char *newpath = NULL;
516         RoomNetCfgLine* mptr;
517         StrBuf *Buf = NULL;
518         int i;
519         int bang = 0;
520         int send = 1;
521
522         if (sc->RNCfg->NetConfigs[ignet_push_share] == NULL)
523                 return;
524         /*
525          * Process IGnet push shares
526          */
527         msg = CtdlDuplicateMessage(omsg);
528
529         /* Prepend our node name to the Path field whenever
530          * sending a message to another IGnet node
531          */
532         if (msg->cm_fields['P'] == NULL)
533         {
534                 msg->cm_fields['P'] = strdup("username");
535         }
536         newpath_len = strlen(msg->cm_fields['P']) +
537                 strlen(config.c_nodename) + 2;
538         newpath = malloc(newpath_len);
539         snprintf(newpath, newpath_len, "%s!%s",
540                  config.c_nodename, msg->cm_fields['P']);
541         free(msg->cm_fields['P']);
542         msg->cm_fields['P'] = newpath;
543         
544         /*
545          * Determine if this message is set to be deleted
546          * after sending out on the network
547          */
548         if (msg->cm_fields['S'] != NULL) {
549                 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
550                         *delete_after_send = 1;
551                 }
552         }
553
554         /* Now send it to every node */
555         for (mptr = sc->RNCfg->NetConfigs[ignet_push_share];
556              mptr != NULL;
557              mptr = mptr->next)
558         {
559                 send = 1;
560                 NewStrBufDupAppendFlush(&Buf, mptr->Value[0], NULL, 1);
561                         
562                 /* Check for valid node name */
563                 if (CtdlIsValidNode(NULL,
564                                     NULL,
565                                     Buf,
566                                     sc->working_ignetcfg,
567                                     sc->the_netmap) != 0)
568                 {
569                         QN_syslog(LOG_ERR,
570                                   "Invalid node <%s>\n",
571                                   ChrPtr(mptr->Value[0]));
572                         
573                         send = 0;
574                 }
575                 
576                 /* Check for split horizon */
577                 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
578                 bang = num_tokens(msg->cm_fields['P'], '!');
579                 if (bang > 1) {
580                         for (i=0; i<(bang-1); ++i) {
581                                 extract_token(buf,
582                                               msg->cm_fields['P'],
583                                               i, '!',
584                                               sizeof buf);
585
586                                 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
587                                           buf, ChrPtr(mptr->Value[0])) ;
588                                 if (!strcasecmp(buf, ChrPtr(mptr->Value[0]))) {
589                                         send = 0;
590                                         break;
591                                 }
592                         }
593                         
594                         QN_syslog(LOG_INFO,
595                                   "%sSending to %s\n",
596                                   (send)?"":"Not ",
597                                   ChrPtr(mptr->Value[0]));
598                 }
599                 
600                 /* Send the message */
601                 if (send == 1)
602                 {
603                         /*
604                          * Force the message to appear in the correct
605                          * room on the far end by setting the C field
606                          * correctly
607                          */
608                         if (msg->cm_fields['C'] != NULL) {
609                                 free(msg->cm_fields['C']);
610                         }
611                         if (StrLength(mptr->Value[0]) > 0) {
612                                 msg->cm_fields['C'] =
613                                         strdup(ChrPtr(mptr->Value[0]));
614                         }
615                         else {
616                                 msg->cm_fields['C'] =
617                                         strdup(CC->room.QRname);
618                         }
619                         
620                         /* serialize it for transmission */
621                         serialize_message(&sermsg, msg);
622                         if (sermsg.len > 0) {
623                                 
624                                 /* write it to a spool file */
625                                 snprintf(filename,
626                                          sizeof(filename),
627                                          "%s/%s@%lx%x",
628                                          ctdl_netout_dir,
629                                          ChrPtr(mptr->Value[0]),
630                                          time(NULL),
631                                          rand()
632                                         );
633                                         
634                                 QN_syslog(LOG_DEBUG,
635                                           "Appending to %s\n",
636                                           filename);
637                                 
638                                 fp = fopen(filename, "ab");
639                                 if (fp != NULL) {
640                                         fwrite(sermsg.ser,
641                                                sermsg.len, 1, fp);
642                                         fclose(fp);
643                                 }
644                                 else {
645                                         QN_syslog(LOG_ERR,
646                                                   "%s: %s\n",
647                                                   filename,
648                                                   strerror(errno));
649                                 }
650
651                                 /* free the serialized version */
652                                 free(sermsg.ser);
653                         }
654                 }
655         }
656         FreeStrBuf(&Buf);
657         CtdlFreeMessage(msg);
658 }
659
660
661 /*
662  * Spools out one message from the list.
663  */
664 void network_spool_msg(long msgnum,
665                        void *userdata)
666 {
667         struct CtdlMessage *msg = NULL;
668         long delete_after_send = 0;     /* Set to 1 to delete after spooling */
669         SpoolControl *sc;
670
671         sc = (SpoolControl *)userdata;
672
673         msg = CtdlFetchMessage(msgnum, 1);
674
675         network_process_list(sc, msg, &delete_after_send);
676         network_process_digest(sc, msg, &delete_after_send);
677         network_process_participate(sc, msg, &delete_after_send);
678         network_process_ignetpush(sc, msg, &delete_after_send);
679         
680         CtdlFreeMessage(msg);
681
682         /* update lastsent */
683         sc->lastsent = msgnum;
684
685         /* Delete this message if delete-after-send is set */
686         if (delete_after_send) {
687                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
688         }
689 }