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