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