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