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