2782fbf8f23bf60a4b8a08fe98534b5fe362cac7
[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 "citadel_dirs.h"
76 #include "threads.h"
77 #include "context.h"
78 #include "ctdl_module.h"
79 #include "netspool.h"
80 #include "netmail.h"
81
82 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName);
83
84 void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg, long nSegments)
85 {
86         int i;
87         size_t recps_len = 0;
88         RoomNetCfgLine *nptr;
89         struct CitContext *CCC = CC;
90
91         *recps = NULL;
92         /*
93          * Figure out how big a buffer we need to allocate
94          */
95         for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
96                 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
97         }
98
99         /* Nothing todo... */
100         if (recps_len == 0)
101                 return;
102
103         *recps = NewStrBufPlain(NULL, recps_len);
104
105         if (*recps == NULL) {
106                 QN_syslog(LOG_EMERG,
107                           "Cannot allocate %ld bytes for recps...\n",
108                           (long)recps_len);
109                 abort();
110         }
111
112         /* Each recipient */
113         for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
114                 if (nptr != OneRNCfg->NetConfigs[Which]) {
115                         for (i = 0; i < nSegments; i++)
116                                 StrBufAppendBufPlain(*recps, HKEY(","), i);
117                 }
118                 StrBufAppendBuf(*recps, nptr->Value[0], 0);
119                 if (Which == ignet_push_share)
120                 {
121                         StrBufAppendBufPlain(*recps, HKEY(","), 0);
122                         StrBufAppendBuf(*recps, nptr->Value[1], 0);
123
124                 }
125         }
126 }
127
128 static void ListCalculateSubject(struct CtdlMessage *msg)
129 {
130         struct CitContext *CCC = CC;
131         StrBuf *Subject, *FlatSubject;
132         int rlen;
133         char *pCh;
134
135         if (CM_IsEmpty(msg, eMsgSubject)) {
136                 Subject = NewStrBufPlain(HKEY("(no subject)"));
137         }
138         else {
139                 Subject = NewStrBufPlain(CM_KEY(msg, eMsgSubject));
140         }
141         FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
142         StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
143
144         rlen = strlen(CCC->room.QRname);
145         pCh  = strstr(ChrPtr(FlatSubject), CCC->room.QRname);
146         if ((pCh == NULL) ||
147             (*(pCh + rlen) != ']') ||
148             (pCh == ChrPtr(FlatSubject)) ||
149             (*(pCh - 1) != '[')
150                 )
151         {
152                 StrBuf *tmp;
153                 StrBufPlain(Subject, HKEY("["));
154                 StrBufAppendBufPlain(Subject,
155                                      CCC->room.QRname,
156                                      rlen, 0);
157                 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
158                 StrBufAppendBuf(Subject, FlatSubject, 0);
159                 /* so we can free the right one swap them */
160                 tmp = Subject;
161                 Subject = FlatSubject;
162                 FlatSubject = tmp;
163                 StrBufRFC2047encode(&Subject, FlatSubject);
164         }
165
166         CM_SetAsFieldSB(msg, eMsgSubject, &Subject);
167
168         FreeStrBuf(&FlatSubject);
169 }
170
171 /*
172  * Deliver digest messages
173  */
174 void network_deliver_digest(SpoolControl *sc)
175 {
176         struct CitContext *CCC = CC;
177         long len;
178         char buf[SIZ];
179         char *pbuf;
180         struct CtdlMessage *msg = NULL;
181         long msglen;
182         recptypes *valid;
183         char bounce_to[256];
184
185         if (sc->Users[digestrecp] == NULL)
186                 return;
187
188         if (sc->num_msgs_spooled < 1) {
189                 fclose(sc->digestfp);
190                 sc->digestfp = NULL;
191                 return;
192         }
193
194         msg = malloc(sizeof(struct CtdlMessage));
195         memset(msg, 0, sizeof(struct CtdlMessage));
196         msg->cm_magic = CTDLMESSAGE_MAGIC;
197         msg->cm_format_type = FMT_RFC822;
198         msg->cm_anon_type = MES_NORMAL;
199
200         CM_SetFieldLONG(msg, eTimestamp, time(NULL));
201         CM_SetField(msg, eAuthor, CCC->room.QRname, strlen(CCC->room.QRname));
202         len = snprintf(buf, sizeof buf, "[%s]", CCC->room.QRname);
203         CM_SetField(msg, eMsgSubject, buf, len);
204
205         CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
206         CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
207
208         /* Set the 'List-ID' header */
209         CM_SetField(msg, eListID, SKEY(sc->ListID));
210
211         /*
212          * Go fetch the contents of the digest
213          */
214         fseek(sc->digestfp, 0L, SEEK_END);
215         msglen = ftell(sc->digestfp);
216
217         pbuf = malloc(msglen + 1);
218         fseek(sc->digestfp, 0L, SEEK_SET);
219         fread(pbuf, (size_t)msglen, 1, sc->digestfp);
220         pbuf[msglen] = '\0';
221         CM_SetAsField(msg, eMesageText, &pbuf, msglen);
222
223         /* Now generate the delivery instructions */
224         if (sc->Users[digestrecp] == NULL)
225                 return;
226
227         /* Where do we want bounces and other noise to be heard?
228          *Surely not the list members! */
229         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
230
231         /* Now submit the message */
232         valid = validate_recipients(ChrPtr(sc->Users[digestrecp]), NULL, 0);
233         if (valid != NULL) {
234                 valid->bounce_to = strdup(bounce_to);
235                 valid->envelope_from = strdup(bounce_to);
236                 CtdlSubmitMsg(msg, valid, NULL, 0);
237         }
238         CM_Free(msg);
239         free_recipients(valid);
240 }
241
242
243 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
244 {
245
246         struct CtdlMessage *msg = NULL;
247
248         /*
249          * Process digest recipients
250          */
251         if ((sc->Users[digestrecp] == NULL)||
252             (sc->digestfp == NULL))
253                 return;
254
255         msg = CM_Duplicate(omsg);
256         if (msg != NULL) {
257                 fprintf(sc->digestfp,
258                         " -----------------------------------"
259                         "------------------------------------"
260                         "-------\n");
261                 fprintf(sc->digestfp, "From: ");
262                 if (!CM_IsEmpty(msg, eAuthor)) {
263                         fprintf(sc->digestfp,
264                                 "%s ",
265                                 msg->cm_fields[eAuthor]);
266                 }
267                 if (!CM_IsEmpty(msg, erFc822Addr)) {
268                         fprintf(sc->digestfp,
269                                 "<%s> ",
270                                 msg->cm_fields[erFc822Addr]);
271                 }
272                 else if (!CM_IsEmpty(msg, eNodeName)) {
273                         fprintf(sc->digestfp,
274                                 "@%s ",
275                                 msg->cm_fields[eNodeName]);
276                 }
277                 fprintf(sc->digestfp, "\n");
278                 if (!CM_IsEmpty(msg, eMsgSubject)) {
279                         fprintf(sc->digestfp,
280                                 "Subject: %s\n",
281                                 msg->cm_fields[eMsgSubject]);
282                 }
283
284                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
285
286                 safestrncpy(CC->preferred_formats,
287                             "text/plain",
288                             sizeof CC->preferred_formats);
289
290                 CtdlOutputPreLoadedMsg(msg,
291                                        MT_CITADEL,
292                                        HEADERS_NONE,
293                                        0, 0, 0);
294
295                 StrBufTrim(CC->redirect_buffer);
296                 fwrite(HKEY("\n"), 1, sc->digestfp);
297                 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
298                 fwrite(HKEY("\n"), 1, sc->digestfp);
299
300                 FreeStrBuf(&CC->redirect_buffer);
301
302                 sc->num_msgs_spooled += 1;
303                 CM_Free(msg);
304         }
305 }
306
307
308 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
309 {
310         struct CtdlMessage *msg = NULL;
311
312         /*
313          * Process mailing list recipients
314          */
315         if (sc->Users[listrecp] == NULL)
316                 return;
317
318         /* create our own copy of the message.
319          *  We're going to need to modify it
320          * in order to insert the [list name] in it, etc.
321          */
322
323         msg = CM_Duplicate(omsg);
324
325
326         CM_SetField(msg, eReplyTo, SKEY(sc->Users[roommailalias]));
327
328         /* if there is no other recipient, Set the recipient
329          * of the list message to the email address of the
330          * room itself.
331          */
332         if (CM_IsEmpty(msg, eRecipient))
333         {
334                 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
335         }
336
337         /* Set the 'List-ID' header */
338         CM_SetField(msg, eListID, SKEY(sc->ListID));
339
340
341         /* Prepend "[List name]" to the subject */
342         ListCalculateSubject(msg);
343
344         /* Handle delivery */
345         network_deliver_list(msg, sc, CC->room.QRname);
346         CM_Free(msg);
347 }
348
349 /*
350  * Deliver list messages to everyone on the list ... efficiently
351  */
352 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
353 {
354         recptypes *valid;
355         char bounce_to[256];
356
357         /* Don't do this if there were no recipients! */
358         if (sc->Users[listrecp] == NULL)
359                 return;
360
361         /* Now generate the delivery instructions */
362
363         /* Where do we want bounces and other noise to be heard?
364          *  Surely not the list members! */
365         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
366
367         /* Now submit the message */
368         valid = validate_recipients(ChrPtr(sc->Users[listrecp]), NULL, 0);
369         if (valid != NULL) {
370                 valid->bounce_to = strdup(bounce_to);
371                 valid->envelope_from = strdup(bounce_to);
372                 valid->sending_room = strdup(RoomName);
373                 CtdlSubmitMsg(msg, valid, NULL, 0);
374                 free_recipients(valid);
375         }
376         /* Do not call CM_Free(msg) here; the caller will free it. */
377 }
378
379
380 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
381 {
382         struct CtdlMessage *msg = NULL;
383         int ok_to_participate = 0;
384         StrBuf *Buf = NULL;
385         recptypes *valid;
386
387         /*
388          * Process client-side list participations for this room
389          */
390         if (sc->Users[participate] == NULL)
391                 return;
392
393         msg = CM_Duplicate(omsg);
394
395         /* Only send messages which originated on our own
396          * Citadel network, otherwise we'll end up sending the
397          * remote mailing list's messages back to it, which
398          * is rude...
399          */
400         ok_to_participate = 0;
401         if (!CM_IsEmpty(msg, eNodeName)) {
402                 if (!strcasecmp(msg->cm_fields[eNodeName],
403                                 config.c_nodename)) {
404                         ok_to_participate = 1;
405                 }
406                 
407                 Buf = NewStrBufPlain(CM_KEY(msg, eNodeName));
408                 if (CtdlIsValidNode(NULL,
409                                     NULL,
410                                     Buf,
411                                     sc->working_ignetcfg,
412                                     sc->the_netmap) == 0)
413                 {
414                         ok_to_participate = 1;
415                 }
416         }
417         if (ok_to_participate)
418         {
419                 /* Replace the Internet email address of the
420                  * actual author with the email address of the
421                  * room itself, so the remote listserv doesn't
422                  * reject us.
423                  */
424                 CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
425
426                 valid = validate_recipients(ChrPtr(sc->Users[participate]) , NULL, 0);
427
428                 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
429                 CtdlSubmitMsg(msg, valid, "", 0);
430                 free_recipients(valid);
431         }
432         FreeStrBuf(&Buf);
433         CM_Free(msg);
434 }
435
436 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
437 {
438         StrBuf *Recipient;
439         StrBuf *RemoteRoom;
440         const char *Pos = NULL;
441         struct CtdlMessage *msg = NULL;
442         struct CitContext *CCC = CC;
443         struct ser_ret sermsg;
444         char buf[SIZ];
445         char filename[PATH_MAX];
446         FILE *fp;
447         StrBuf *Buf = NULL;
448         int i;
449         int bang = 0;
450         int send = 1;
451
452         if (sc->Users[ignet_push_share] == NULL)
453                 return;
454
455         /*
456          * Process IGnet push shares
457          */
458         msg = CM_Duplicate(omsg);
459
460         /* Prepend our node name to the Path field whenever
461          * sending a message to another IGnet node
462          */
463         Netmap_AddMe(msg, HKEY("username"));
464
465         /*
466          * Determine if this message is set to be deleted
467          * after sending out on the network
468          */
469         if (!CM_IsEmpty(msg, eSpecialField)) {
470                 if (!strcasecmp(msg->cm_fields[eSpecialField], "CANCEL")) {
471                         *delete_after_send = 1;
472                 }
473         }
474
475         /* Now send it to every node */
476         Recipient = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share]));
477         RemoteRoom = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share]));
478         while ((Pos != StrBufNOTNULL) &&
479                StrBufExtract_NextToken(Recipient, sc->Users[ignet_push_share], &Pos, ','))
480         {
481                 StrBufExtract_NextToken(RemoteRoom, sc->Users[ignet_push_share], &Pos, ',');
482                 send = 1;
483                 NewStrBufDupAppendFlush(&Buf, Recipient, NULL, 1);
484                         
485                 /* Check for valid node name */
486                 if (CtdlIsValidNode(NULL,
487                                     NULL,
488                                     Buf,
489                                     sc->working_ignetcfg,
490                                     sc->the_netmap) != 0)
491                 {
492                         QN_syslog(LOG_ERR,
493                                   "Invalid node <%s>\n",
494                                   ChrPtr(Recipient));
495                         
496                         send = 0;
497                 }
498                 
499                 /* Check for split horizon */
500                 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields[eMessagePath]);
501                 bang = num_tokens(msg->cm_fields[eMessagePath], '!');
502                 if (bang > 1) {
503                         for (i=0; i<(bang-1); ++i) {
504                                 extract_token(buf,
505                                               msg->cm_fields[eMessagePath],
506                                               i, '!',
507                                               sizeof buf);
508
509                                 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
510                                           buf, ChrPtr(Recipient)) ;
511                                 if (!strcasecmp(buf, ChrPtr(Recipient))) {
512                                         send = 0;
513                                         break;
514                                 }
515                         }
516                         
517                         QN_syslog(LOG_INFO,
518                                   " %sSending to %s\n",
519                                   (send)?"":"Not ",
520                                   ChrPtr(Recipient));
521                 }
522                 
523                 /* Send the message */
524                 if (send == 1)
525                 {
526                         /*
527                          * Force the message to appear in the correct
528                          * room on the far end by setting the C field
529                          * correctly
530                          */
531                         if (StrLength(RemoteRoom) > 0) {
532                                 CM_SetField(msg, eRemoteRoom, SKEY(RemoteRoom));
533                         }
534                         else {
535                                 CM_SetField(msg, eRemoteRoom, CCC->room.QRname, strlen(CCC->room.QRname));
536                         }
537                         
538                         /* serialize it for transmission */
539                         CtdlSerializeMessage(&sermsg, msg);
540                         if (sermsg.len > 0) {
541                                 
542                                 /* write it to a spool file */
543                                 snprintf(filename,
544                                          sizeof(filename),
545                                          "%s/%s@%lx%x",
546                                          ctdl_netout_dir,
547                                          ChrPtr(Recipient),
548                                          time(NULL),
549                                          rand()
550                                         );
551                                         
552                                 QN_syslog(LOG_DEBUG,
553                                           "Appending to %s\n",
554                                           filename);
555                                 
556                                 fp = fopen(filename, "ab");
557                                 if (fp != NULL) {
558                                         fwrite(sermsg.ser,
559                                                sermsg.len, 1, fp);
560                                         fclose(fp);
561                                 }
562                                 else {
563                                         QN_syslog(LOG_ERR,
564                                                   "%s: %s\n",
565                                                   filename,
566                                                   strerror(errno));
567                                 }
568
569                                 /* free the serialized version */
570                                 free(sermsg.ser);
571                         }
572                 }
573         }
574         FreeStrBuf(&Buf);
575         FreeStrBuf(&Recipient);
576         FreeStrBuf(&RemoteRoom);
577         CM_Free(msg);
578 }
579
580
581 /*
582  * Spools out one message from the list.
583  */
584 void network_spool_msg(long msgnum,
585                        void *userdata)
586 {
587         struct CitContext *CCC = CC;
588         struct CtdlMessage *msg = NULL;
589         long delete_after_send = 0;     /* Set to 1 to delete after spooling */
590         SpoolControl *sc;
591
592         sc = (SpoolControl *)userdata;
593
594         msg = CtdlFetchMessage(msgnum, 1);
595
596         if (msg == NULL)
597         {
598                 QN_syslog(LOG_ERR,
599                           "failed to load Message <%ld> from disk\n",
600                           msgnum);
601                 return;
602         }
603         network_process_list(sc, msg, &delete_after_send);
604         network_process_digest(sc, msg, &delete_after_send);
605         network_process_participate(sc, msg, &delete_after_send);
606         network_process_ignetpush(sc, msg, &delete_after_send);
607         
608         CM_Free(msg);
609
610         /* update lastsent */
611         sc->lastsent = msgnum;
612
613         /* Delete this message if delete-after-send is set */
614         if (delete_after_send) {
615                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
616         }
617 }