6ee61cfd57317c3aec1d45864541af63d07d2289
[citadel.git] / citadel / modules / network / serv_netmail.c
1 /*
2  * This module handles network mail and mailing list processing.
3  *
4  * Copyright (c) 2000-2018 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License, version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
15  * This is a fairly high-level type of critical section.  It ensures that no
16  * two threads work on the netconfigs files at the same time.  Since we do
17  * so many things inside these, here are the rules:
18  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
19  *  2. Do *not* perform any I/O with the client during these sections.
20  *
21  */
22
23 /*
24  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
25  * requests that have not been confirmed will be deleted.
26  */
27 #define EXP     259200  /* three days */
28
29 #include "sysdep.h"
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <stdio.h>
33 #include <fcntl.h>
34 #include <ctype.h>
35 #include <signal.h>
36 #include <pwd.h>
37 #include <errno.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <dirent.h>
41 #if TIME_WITH_SYS_TIME
42 # include <sys/time.h>
43 # include <time.h>
44 #else
45 # if HAVE_SYS_TIME_H
46 #  include <sys/time.h>
47 # else
48 #  include <time.h>
49 # endif
50 #endif
51 #ifdef HAVE_SYSCALL_H
52 # include <syscall.h>
53 #else
54 # if HAVE_SYS_SYSCALL_H
55 #  include <sys/syscall.h>
56 # endif
57 #endif
58
59 #include <sys/wait.h>
60 #include <string.h>
61 #include <limits.h>
62 #include <libcitadel.h>
63 #include "citadel.h"
64 #include "server.h"
65 #include "citserver.h"
66 #include "support.h"
67 #include "config.h"
68 #include "user_ops.h"
69 #include "database.h"
70 #include "msgbase.h"
71 #include "internet_addressing.h"
72 #include "serv_network.h"
73 #include "clientsocket.h"
74 #include "citadel_dirs.h"
75 #include "threads.h"
76 #include "context.h"
77 #include "ctdl_module.h"
78 #include "netspool.h"
79 #include "netmail.h"
80
81 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName);
82
83 void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg, long nSegments)
84 {
85         int i;
86         size_t recps_len = 0;
87         RoomNetCfgLine *nptr;
88
89         *recps = NULL;
90         /*
91          * Figure out how big a buffer we need to allocate
92          */
93         for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
94                 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
95         }
96
97         /* Nothing todo... */
98         if (recps_len == 0)
99                 return;
100
101         *recps = NewStrBufPlain(NULL, recps_len);
102
103         if (*recps == NULL) {
104                 syslog(LOG_ERR, "netmail: cannot allocate %ld bytes for recps", (long)recps_len);
105                 abort();
106         }
107
108         /* Each recipient */
109         for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
110                 if (nptr != OneRNCfg->NetConfigs[Which]) {
111                         for (i = 0; i < nSegments; i++)
112                                 StrBufAppendBufPlain(*recps, HKEY(","), i);
113                 }
114                 StrBufAppendBuf(*recps, nptr->Value[0], 0);
115                 if (Which == ignet_push_share)
116                 {
117                         StrBufAppendBufPlain(*recps, HKEY(","), 0);
118                         StrBufAppendBuf(*recps, nptr->Value[1], 0);
119
120                 }
121         }
122 }
123
124 static void ListCalculateSubject(struct CtdlMessage *msg)
125 {
126         struct CitContext *CCC = CC;
127         StrBuf *Subject, *FlatSubject;
128         int rlen;
129         char *pCh;
130
131         if (CM_IsEmpty(msg, eMsgSubject)) {
132                 Subject = NewStrBufPlain(HKEY("(no subject)"));
133         }
134         else {
135                 Subject = NewStrBufPlain(CM_KEY(msg, eMsgSubject));
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         CM_SetAsFieldSB(msg, eMsgSubject, &Subject);
163
164         FreeStrBuf(&FlatSubject);
165 }
166
167 /*
168  * Deliver digest messages
169  */
170 void network_deliver_digest(SpoolControl *sc)
171 {
172         struct CitContext *CCC = CC;
173         long len;
174         char buf[SIZ];
175         char *pbuf;
176         struct CtdlMessage *msg = NULL;
177         long msglen;
178         recptypes *valid;
179         char bounce_to[256];
180
181         if (sc->Users[digestrecp] == NULL)
182                 return;
183
184         msg = malloc(sizeof(struct CtdlMessage));
185         memset(msg, 0, sizeof(struct CtdlMessage));
186         msg->cm_magic = CTDLMESSAGE_MAGIC;
187         msg->cm_format_type = FMT_RFC822;
188         msg->cm_anon_type = MES_NORMAL;
189
190         CM_SetFieldLONG(msg, eTimestamp, time(NULL));
191         CM_SetField(msg, eAuthor, CCC->room.QRname, strlen(CCC->room.QRname));
192         len = snprintf(buf, sizeof buf, "[%s]", CCC->room.QRname);
193         CM_SetField(msg, eMsgSubject, buf, len);
194
195         CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
196         CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
197
198         /* Set the 'List-ID' header */
199         CM_SetField(msg, eListID, SKEY(sc->ListID));
200
201         /*
202          * Go fetch the contents of the digest
203          */
204         fseek(sc->digestfp, 0L, SEEK_END);
205         msglen = ftell(sc->digestfp);
206
207         pbuf = malloc(msglen + 1);
208         fseek(sc->digestfp, 0L, SEEK_SET);
209         fread(pbuf, (size_t)msglen, 1, sc->digestfp);
210         pbuf[msglen] = '\0';
211         CM_SetAsField(msg, eMesageText, &pbuf, msglen);
212
213         /* Now generate the delivery instructions */
214
215         /* Where do we want bounces and other noise to be heard?
216          * Surely not the list members! */
217         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
218
219         /* Now submit the message */
220         valid = validate_recipients(ChrPtr(sc->Users[digestrecp]), NULL, 0);
221         if (valid != NULL) {
222                 valid->bounce_to = strdup(bounce_to);
223                 valid->envelope_from = strdup(bounce_to);
224                 CtdlSubmitMsg(msg, valid, NULL, 0);
225         }
226         CM_Free(msg);
227         free_recipients(valid);
228 }
229
230
231 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
232 {
233
234         struct CtdlMessage *msg = NULL;
235
236         if (sc->Users[digestrecp] == NULL)
237                 return;
238
239         /* If there are digest recipients, we have to build a digest */
240         if (sc->digestfp == NULL) {
241                 
242                 sc->digestfp = create_digest_file(&sc->room, 1);
243
244                 if (sc->digestfp == NULL)
245                         return;
246
247                 sc->haveDigest = ftell(sc->digestfp) > 0;
248                 if (!sc->haveDigest) {
249                         fprintf(sc->digestfp, "Content-type: text/plain\n\n");
250                 }
251                 sc->haveDigest = 1;
252         }
253
254         msg = CM_Duplicate(omsg);
255         if (msg != NULL) {
256                 sc->haveDigest = 1;
257                 fprintf(sc->digestfp,
258                         " -----------------------------------"
259                         "------------------------------------"
260                         "-------\n");
261                 fprintf(sc->digestfp, "From: ");
262                 if (!CM_IsEmpty(msg, eAuthor)) {
263                         fprintf(sc->digestfp,
264                                 "%s ",
265                                 msg->cm_fields[eAuthor]);
266                 }
267                 if (!CM_IsEmpty(msg, erFc822Addr)) {
268                         fprintf(sc->digestfp,
269                                 "<%s> ",
270                                 msg->cm_fields[erFc822Addr]);
271                 }
272                 else if (!CM_IsEmpty(msg, eNodeName)) {
273                         fprintf(sc->digestfp,
274                                 "@%s ",
275                                 msg->cm_fields[eNodeName]);
276                 }
277                 fprintf(sc->digestfp, "\n");
278                 if (!CM_IsEmpty(msg, eMsgSubject)) {
279                         fprintf(sc->digestfp,
280                                 "Subject: %s\n",
281                                 msg->cm_fields[eMsgSubject]);
282                 }
283
284                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
285
286                 safestrncpy(CC->preferred_formats,
287                             "text/plain",
288                             sizeof CC->preferred_formats);
289
290                 CtdlOutputPreLoadedMsg(msg,
291                                        MT_CITADEL,
292                                        HEADERS_NONE,
293                                        0, 0, 0);
294
295                 StrBufTrim(CC->redirect_buffer);
296                 fwrite(HKEY("\n"), 1, sc->digestfp);
297                 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
298                 fwrite(HKEY("\n"), 1, sc->digestfp);
299
300                 FreeStrBuf(&CC->redirect_buffer);
301
302                 sc->num_msgs_spooled += 1;
303                 CM_Free(msg);
304         }
305 }
306
307
308 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
309 {
310         struct CtdlMessage *msg = NULL;
311
312         /*
313          * Process mailing list recipients
314          */
315         if (sc->Users[listrecp] == NULL)
316                 return;
317
318         /* create our own copy of the message.
319          *  We're going to need to modify it
320          * in order to insert the [list name] in it, etc.
321          */
322
323         msg = CM_Duplicate(omsg);
324
325
326         CM_SetField(msg, eReplyTo, SKEY(sc->Users[roommailalias]));
327
328         /* if there is no other recipient, Set the recipient
329          * of the list message to the email address of the
330          * room itself.
331          */
332         if (CM_IsEmpty(msg, eRecipient))
333         {
334                 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
335         }
336
337         /* Set the 'List-ID' header */
338         CM_SetField(msg, eListID, SKEY(sc->ListID));
339
340
341         /* Prepend "[List name]" to the subject */
342         ListCalculateSubject(msg);
343
344         /* Handle delivery */
345         network_deliver_list(msg, sc, CC->room.QRname);
346         CM_Free(msg);
347 }
348
349 /*
350  * Deliver list messages to everyone on the list ... efficiently
351  */
352 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
353 {
354         recptypes *valid;
355         char bounce_to[256];
356
357         /* Don't do this if there were no recipients! */
358         if (sc->Users[listrecp] == NULL)
359                 return;
360
361         /* Now generate the delivery instructions */
362
363         /* Where do we want bounces and other noise to be heard?
364          *  Surely not the list members! */
365         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
366
367         /* Now submit the message */
368         valid = validate_recipients(ChrPtr(sc->Users[listrecp]), NULL, 0);
369         if (valid != NULL) {
370                 valid->bounce_to = strdup(bounce_to);
371                 valid->envelope_from = strdup(bounce_to);
372                 valid->sending_room = strdup(RoomName);
373                 CtdlSubmitMsg(msg, valid, NULL, 0);
374                 free_recipients(valid);
375         }
376         /* Do not call CM_Free(msg) here; the caller will free it. */
377 }
378
379
380 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
381 {
382         struct CtdlMessage *msg = NULL;
383         int ok_to_participate = 0;
384         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 = CM_Duplicate(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 (!CM_IsEmpty(msg, eNodeName)) {
401                 if (!strcasecmp(msg->cm_fields[eNodeName], CtdlGetConfigStr("c_nodename")))
402                 {
403                         ok_to_participate = 1;
404                 }
405         }
406         if (ok_to_participate)
407         {
408                 /* Replace the Internet email address of the
409                  * actual author with the email address of the
410                  * room itself, so the remote listserv doesn't
411                  * reject us.
412                  */
413                 CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
414
415                 valid = validate_recipients(ChrPtr(sc->Users[participate]) , NULL, 0);
416
417                 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
418                 CtdlSubmitMsg(msg, valid, "", 0);
419                 free_recipients(valid);
420         }
421         CM_Free(msg);
422 }
423
424
425 /*
426  * Spools out one message from the list.
427  */
428 void network_spool_msg(long msgnum, void *userdata)
429 {
430         struct CtdlMessage *msg = NULL;
431         long delete_after_send = 0;     /* Set to 1 to delete after spooling */
432         SpoolControl *sc;
433
434         sc = (SpoolControl *)userdata;
435         msg = CtdlFetchMessage(msgnum, 1, 1);
436
437         if (msg == NULL)
438         {
439                 syslog(LOG_ERR, "netmail: failed to load Message <%ld> from disk", msgnum);
440                 return;
441         }
442         network_process_list(sc, msg, &delete_after_send);
443         network_process_digest(sc, msg, &delete_after_send);
444         network_process_participate(sc, msg, &delete_after_send);
445         
446         CM_Free(msg);
447
448         /* update lastsent */
449         sc->lastsent = msgnum;
450
451         /* Delete this message if delete-after-send is set */
452         if (delete_after_send) {
453                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
454         }
455 }