Removed some leftover dependencies on message fields which no longer exist
[citadel.git] / citadel / modules / network / serv_netmail.c
1 /*
2  * This module handles network mail and mailing list processing.
3  *
4  * Copyright (c) 2000-2020 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                 fprintf(sc->digestfp, "\n");
273                 if (!CM_IsEmpty(msg, eMsgSubject)) {
274                         fprintf(sc->digestfp,
275                                 "Subject: %s\n",
276                                 msg->cm_fields[eMsgSubject]);
277                 }
278
279                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
280
281                 safestrncpy(CC->preferred_formats,
282                             "text/plain",
283                             sizeof CC->preferred_formats);
284
285                 CtdlOutputPreLoadedMsg(msg,
286                                        MT_CITADEL,
287                                        HEADERS_NONE,
288                                        0, 0, 0);
289
290                 StrBufTrim(CC->redirect_buffer);
291                 fwrite(HKEY("\n"), 1, sc->digestfp);
292                 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
293                 fwrite(HKEY("\n"), 1, sc->digestfp);
294
295                 FreeStrBuf(&CC->redirect_buffer);
296
297                 sc->num_msgs_spooled += 1;
298                 CM_Free(msg);
299         }
300 }
301
302
303 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
304 {
305         struct CtdlMessage *msg = NULL;
306
307         /*
308          * Process mailing list recipients
309          */
310         if (sc->Users[listrecp] == NULL)
311                 return;
312
313         /* create our own copy of the message.
314          *  We're going to need to modify it
315          * in order to insert the [list name] in it, etc.
316          */
317
318         msg = CM_Duplicate(omsg);
319
320
321         CM_SetField(msg, eReplyTo, SKEY(sc->Users[roommailalias]));
322
323         /* if there is no other recipient, Set the recipient
324          * of the list message to the email address of the
325          * room itself.
326          */
327         if (CM_IsEmpty(msg, eRecipient))
328         {
329                 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
330         }
331
332         /* Set the 'List-ID' header */
333         CM_SetField(msg, eListID, SKEY(sc->ListID));
334
335
336         /* Prepend "[List name]" to the subject */
337         ListCalculateSubject(msg);
338
339         /* Handle delivery */
340         network_deliver_list(msg, sc, CC->room.QRname);
341         CM_Free(msg);
342 }
343
344 /*
345  * Deliver list messages to everyone on the list ... efficiently
346  */
347 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
348 {
349         recptypes *valid;
350         char bounce_to[256];
351
352         /* Don't do this if there were no recipients! */
353         if (sc->Users[listrecp] == NULL)
354                 return;
355
356         /* Now generate the delivery instructions */
357
358         /* Where do we want bounces and other noise to be heard?
359          *  Surely not the list members! */
360         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
361
362         /* Now submit the message */
363         valid = validate_recipients(ChrPtr(sc->Users[listrecp]), NULL, 0);
364         if (valid != NULL) {
365                 valid->bounce_to = strdup(bounce_to);
366                 valid->envelope_from = strdup(bounce_to);
367                 valid->sending_room = strdup(RoomName);
368                 CtdlSubmitMsg(msg, valid, NULL, 0);
369                 free_recipients(valid);
370         }
371         /* Do not call CM_Free(msg) here; the caller will free it. */
372 }
373
374
375 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
376 {
377         struct CtdlMessage *msg = NULL;
378         int ok_to_participate = 0;
379         recptypes *valid;
380
381         /*
382          * Process client-side list participations for this room
383          */
384         if (sc->Users[participate] == NULL)
385                 return;
386
387         msg = CM_Duplicate(omsg);
388
389         /* Only send messages which originated on our own
390          * Citadel network, otherwise we'll end up sending the
391          * remote mailing list's messages back to it, which
392          * is rude...
393          */
394         ok_to_participate = 0;
395
396         // FIXME -- After we removed CitaNet/IGnet support , we now need a new heuristic to determine
397         // whether a message originated locally.  This means the "participate" mode no longer works.
398         // We'll definitely need to refactor this when we do other federated stuff later.
399
400         if (ok_to_participate)
401         {
402                 /* Replace the Internet email address of the
403                  * actual author with the email address of the
404                  * room itself, so the remote listserv doesn't
405                  * reject us.
406                  */
407                 CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
408
409                 valid = validate_recipients(ChrPtr(sc->Users[participate]) , NULL, 0);
410
411                 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
412                 CtdlSubmitMsg(msg, valid, "", 0);
413                 free_recipients(valid);
414         }
415         CM_Free(msg);
416 }
417
418
419 /*
420  * Spools out one message from the list.
421  */
422 void network_spool_msg(long msgnum, void *userdata)
423 {
424         struct CtdlMessage *msg = NULL;
425         long delete_after_send = 0;     /* Set to 1 to delete after spooling */
426         SpoolControl *sc;
427
428         sc = (SpoolControl *)userdata;
429         msg = CtdlFetchMessage(msgnum, 1, 1);
430
431         if (msg == NULL)
432         {
433                 syslog(LOG_ERR, "netmail: failed to load Message <%ld> from disk", msgnum);
434                 return;
435         }
436         network_process_list(sc, msg, &delete_after_send);
437         network_process_digest(sc, msg, &delete_after_send);
438         network_process_participate(sc, msg, &delete_after_send);
439         
440         CM_Free(msg);
441
442         /* update lastsent */
443         sc->lastsent = msgnum;
444
445         /* Delete this message if delete-after-send is set */
446         if (delete_after_send) {
447                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
448         }
449 }