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