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