NETWORKER: add from addresses and new way of setting list headers.
[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         if (msg->cm_fields['K'] != NULL)
324                 free(msg->cm_fields['K']);
325         if (msg->cm_fields['V'] == NULL){
326                 /* local message, no enVelope */
327                 StrBuf *Buf;
328                 Buf = NewStrBuf();
329                 StrBufAppendBufPlain(Buf,
330                                      msg->cm_fields['O']
331                                      , -1, 0);
332                 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
333                 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
334
335                 msg->cm_fields['K'] = SmashStrBuf(&Buf);
336         }
337         else {
338                 msg->cm_fields['K'] =
339                         strdup (msg->cm_fields['V']);
340         }
341
342         CtdlMsgSetCM_Fields(msg, 'F', SKEY(sc->Users[roommailalias]));
343
344         /* if there is no other recipient, Set the recipient
345          * of the list message to the email address of the
346          * room itself.
347          */
348         if ((msg->cm_fields['R'] == NULL) ||
349             IsEmptyStr(msg->cm_fields['R']))
350         {
351                 CtdlMsgSetCM_Fields(msg, 'R', SKEY(sc->Users[roommailalias]));
352         }
353
354         /* Set the 'List-ID' header */
355         CtdlMsgSetCM_Fields(msg, 'L', SKEY(sc->ListID));
356
357
358         /* Prepend "[List name]" to the subject */
359         ListCalculateSubject(msg);
360
361         /* Handle delivery */
362         network_deliver_list(msg, sc, CC->room.QRname);
363         CtdlFreeMessage(msg);
364 }
365
366 /*
367  * Deliver list messages to everyone on the list ... efficiently
368  */
369 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
370 {
371         struct recptypes *valid;
372         char bounce_to[256];
373
374         /* Don't do this if there were no recipients! */
375         if (sc->Users[listrecp] == NULL)
376                 return;
377
378         /* Now generate the delivery instructions */
379
380         /* Where do we want bounces and other noise to be heard?
381          *  Surely not the list members! */
382         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
383
384         /* Now submit the message */
385         valid = validate_recipients(ChrPtr(sc->Users[listrecp]), NULL, 0);
386         if (valid != NULL) {
387                 valid->bounce_to = strdup(bounce_to);
388                 valid->envelope_from = strdup(bounce_to);
389                 valid->sending_room = strdup(RoomName);
390                 CtdlSubmitMsg(msg, valid, NULL, 0);
391                 free_recipients(valid);
392         }
393         /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
394 }
395
396
397 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
398 {
399         struct CtdlMessage *msg = NULL;
400         int ok_to_participate = 0;
401         StrBuf *Buf = NULL;
402         struct recptypes *valid;
403
404         /*
405          * Process client-side list participations for this room
406          */
407         if (sc->Users[participate] == NULL)
408                 return;
409
410         msg = CtdlDuplicateMessage(omsg);
411
412         /* Only send messages which originated on our own
413          * Citadel network, otherwise we'll end up sending the
414          * remote mailing list's messages back to it, which
415          * is rude...
416          */
417         ok_to_participate = 0;
418         if (msg->cm_fields['N'] != NULL) {
419                 if (!strcasecmp(msg->cm_fields['N'],
420                                 config.c_nodename)) {
421                         ok_to_participate = 1;
422                 }
423                 
424                 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
425                 if (CtdlIsValidNode(NULL,
426                                     NULL,
427                                     Buf,
428                                     sc->working_ignetcfg,
429                                     sc->the_netmap) == 0)
430                 {
431                         ok_to_participate = 1;
432                 }
433         }
434         if (ok_to_participate)
435         {
436                 /* Replace the Internet email address of the
437                  * actual author with the email address of the
438                  * room itself, so the remote listserv doesn't
439                  * reject us.
440                  */
441                 CtdlMsgSetCM_Fields(msg, 'F', SKEY(sc->Users[roommailalias]));
442
443                 valid = validate_recipients(ChrPtr(sc->Users[participate]) , NULL, 0);
444
445                 CtdlMsgSetCM_Fields(msg, 'R', SKEY(sc->Users[roommailalias]));
446                 CtdlSubmitMsg(msg, valid, "", 0);
447                 free_recipients(valid);
448         }
449         FreeStrBuf(&Buf);
450         CtdlFreeMessage(msg);
451 }
452
453 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
454 {
455         StrBuf *Recipient;
456         StrBuf *RemoteRoom;
457         const char *Pos = NULL;
458         struct CtdlMessage *msg = NULL;
459         struct CitContext *CCC = CC;
460         struct ser_ret sermsg;
461         char buf[SIZ];
462         char filename[PATH_MAX];
463         FILE *fp;
464         size_t newpath_len;
465         char *newpath = NULL;
466         StrBuf *Buf = NULL;
467         int i;
468         int bang = 0;
469         int send = 1;
470
471         if (sc->Users[ignet_push_share] == NULL)
472                 return;
473
474         /*
475          * Process IGnet push shares
476          */
477         msg = CtdlDuplicateMessage(omsg);
478
479         /* Prepend our node name to the Path field whenever
480          * sending a message to another IGnet node
481          */
482         if (msg->cm_fields['P'] == NULL)
483         {
484                 msg->cm_fields['P'] = strdup("username");
485         }
486         newpath_len = strlen(msg->cm_fields['P']) +
487                 strlen(config.c_nodename) + 2;
488         newpath = malloc(newpath_len);
489         snprintf(newpath, newpath_len, "%s!%s",
490                  config.c_nodename, msg->cm_fields['P']);
491         free(msg->cm_fields['P']);
492         msg->cm_fields['P'] = newpath;
493         
494         /*
495          * Determine if this message is set to be deleted
496          * after sending out on the network
497          */
498         if (msg->cm_fields['S'] != NULL) {
499                 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
500                         *delete_after_send = 1;
501                 }
502         }
503
504         /* Now send it to every node */
505         Recipient = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share]));
506         RemoteRoom = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share]));
507         while ((Pos != StrBufNOTNULL) &&
508                StrBufExtract_NextToken(Recipient, sc->Users[ignet_push_share], &Pos, ','))
509         {
510                 StrBufExtract_NextToken(RemoteRoom, sc->Users[ignet_push_share], &Pos, ',');
511                 send = 1;
512                 NewStrBufDupAppendFlush(&Buf, Recipient, NULL, 1);
513                         
514                 /* Check for valid node name */
515                 if (CtdlIsValidNode(NULL,
516                                     NULL,
517                                     Buf,
518                                     sc->working_ignetcfg,
519                                     sc->the_netmap) != 0)
520                 {
521                         QN_syslog(LOG_ERR,
522                                   "Invalid node <%s>\n",
523                                   ChrPtr(Recipient));
524                         
525                         send = 0;
526                 }
527                 
528                 /* Check for split horizon */
529                 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
530                 bang = num_tokens(msg->cm_fields['P'], '!');
531                 if (bang > 1) {
532                         for (i=0; i<(bang-1); ++i) {
533                                 extract_token(buf,
534                                               msg->cm_fields['P'],
535                                               i, '!',
536                                               sizeof buf);
537
538                                 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
539                                           buf, ChrPtr(Recipient)) ;
540                                 if (!strcasecmp(buf, ChrPtr(Recipient))) {
541                                         send = 0;
542                                         break;
543                                 }
544                         }
545                         
546                         QN_syslog(LOG_INFO,
547                                   "%sSending to %s\n",
548                                   (send)?"":"Not ",
549                                   ChrPtr(Recipient));
550                 }
551                 
552                 /* Send the message */
553                 if (send == 1)
554                 {
555                         /*
556                          * Force the message to appear in the correct
557                          * room on the far end by setting the C field
558                          * correctly
559                          */
560                         if (msg->cm_fields['C'] != NULL) {
561                                 free(msg->cm_fields['C']);
562                         }
563                         if (StrLength(RemoteRoom) > 0) {
564                                 msg->cm_fields['C'] =
565                                         strdup(ChrPtr(RemoteRoom));
566                         }
567                         else {
568                                 msg->cm_fields['C'] =
569                                         strdup(CC->room.QRname);
570                         }
571                         
572                         /* serialize it for transmission */
573                         serialize_message(&sermsg, msg);
574                         if (sermsg.len > 0) {
575                                 
576                                 /* write it to a spool file */
577                                 snprintf(filename,
578                                          sizeof(filename),
579                                          "%s/%s@%lx%x",
580                                          ctdl_netout_dir,
581                                          ChrPtr(Recipient),
582                                          time(NULL),
583                                          rand()
584                                         );
585                                         
586                                 QN_syslog(LOG_DEBUG,
587                                           "Appending to %s\n",
588                                           filename);
589                                 
590                                 fp = fopen(filename, "ab");
591                                 if (fp != NULL) {
592                                         fwrite(sermsg.ser,
593                                                sermsg.len, 1, fp);
594                                         fclose(fp);
595                                 }
596                                 else {
597                                         QN_syslog(LOG_ERR,
598                                                   "%s: %s\n",
599                                                   filename,
600                                                   strerror(errno));
601                                 }
602
603                                 /* free the serialized version */
604                                 free(sermsg.ser);
605                         }
606                 }
607         }
608         FreeStrBuf(&Buf);
609         FreeStrBuf(&Recipient);
610         FreeStrBuf(&RemoteRoom);
611         CtdlFreeMessage(msg);
612 }
613
614
615 /*
616  * Spools out one message from the list.
617  */
618 void network_spool_msg(long msgnum,
619                        void *userdata)
620 {
621         struct CtdlMessage *msg = NULL;
622         long delete_after_send = 0;     /* Set to 1 to delete after spooling */
623         SpoolControl *sc;
624
625         sc = (SpoolControl *)userdata;
626
627         msg = CtdlFetchMessage(msgnum, 1);
628
629         network_process_list(sc, msg, &delete_after_send);
630         network_process_digest(sc, msg, &delete_after_send);
631         network_process_participate(sc, msg, &delete_after_send);
632         network_process_ignetpush(sc, msg, &delete_after_send);
633         
634         CtdlFreeMessage(msg);
635
636         /* update lastsent */
637         sc->lastsent = msgnum;
638
639         /* Delete this message if delete-after-send is set */
640         if (delete_after_send) {
641                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
642         }
643 }