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