2 * This module handles shared rooms, inter-Citadel mail, and outbound
3 * mailing list processing.
5 * Copyright (c) 2000-2016 by the citadel.org team
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.
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.
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.
25 * Duration of time (in seconds) after which pending list subscribe/unsubscribe
26 * requests that have not been confirmed will be deleted.
28 #define EXP 259200 /* three days */
40 #include <sys/types.h>
42 #if TIME_WITH_SYS_TIME
43 # include <sys/time.h>
47 # include <sys/time.h>
55 # if HAVE_SYS_SYSCALL_H
56 # include <sys/syscall.h>
63 #include <libcitadel.h>
66 #include "citserver.h"
72 #include "internet_addressing.h"
73 #include "serv_network.h"
74 #include "clientsocket.h"
75 #include "citadel_dirs.h"
78 #include "ctdl_module.h"
82 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName);
84 void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg, long nSegments)
92 * Figure out how big a buffer we need to allocate
94 for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
95 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
102 *recps = NewStrBufPlain(NULL, recps_len);
104 if (*recps == NULL) {
105 syslog(LOG_ERR, "netmail: cannot allocate %ld bytes for recps", (long)recps_len);
110 for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
111 if (nptr != OneRNCfg->NetConfigs[Which]) {
112 for (i = 0; i < nSegments; i++)
113 StrBufAppendBufPlain(*recps, HKEY(","), i);
115 StrBufAppendBuf(*recps, nptr->Value[0], 0);
116 if (Which == ignet_push_share)
118 StrBufAppendBufPlain(*recps, HKEY(","), 0);
119 StrBufAppendBuf(*recps, nptr->Value[1], 0);
125 static void ListCalculateSubject(struct CtdlMessage *msg)
127 struct CitContext *CCC = CC;
128 StrBuf *Subject, *FlatSubject;
132 if (CM_IsEmpty(msg, eMsgSubject)) {
133 Subject = NewStrBufPlain(HKEY("(no subject)"));
136 Subject = NewStrBufPlain(CM_KEY(msg, eMsgSubject));
138 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
139 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
141 rlen = strlen(CCC->room.QRname);
142 pCh = strstr(ChrPtr(FlatSubject), CCC->room.QRname);
144 (*(pCh + rlen) != ']') ||
145 (pCh == ChrPtr(FlatSubject)) ||
150 StrBufPlain(Subject, HKEY("["));
151 StrBufAppendBufPlain(Subject,
154 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
155 StrBufAppendBuf(Subject, FlatSubject, 0);
156 /* so we can free the right one swap them */
158 Subject = FlatSubject;
160 StrBufRFC2047encode(&Subject, FlatSubject);
163 CM_SetAsFieldSB(msg, eMsgSubject, &Subject);
165 FreeStrBuf(&FlatSubject);
169 * Deliver digest messages
171 void network_deliver_digest(SpoolControl *sc)
173 struct CitContext *CCC = CC;
177 struct CtdlMessage *msg = NULL;
182 if (sc->Users[digestrecp] == NULL)
185 msg = malloc(sizeof(struct CtdlMessage));
186 memset(msg, 0, sizeof(struct CtdlMessage));
187 msg->cm_magic = CTDLMESSAGE_MAGIC;
188 msg->cm_format_type = FMT_RFC822;
189 msg->cm_anon_type = MES_NORMAL;
191 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
192 CM_SetField(msg, eAuthor, CCC->room.QRname, strlen(CCC->room.QRname));
193 len = snprintf(buf, sizeof buf, "[%s]", CCC->room.QRname);
194 CM_SetField(msg, eMsgSubject, buf, len);
196 CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
197 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
199 /* Set the 'List-ID' header */
200 CM_SetField(msg, eListID, SKEY(sc->ListID));
203 * Go fetch the contents of the digest
205 fseek(sc->digestfp, 0L, SEEK_END);
206 msglen = ftell(sc->digestfp);
208 pbuf = malloc(msglen + 1);
209 fseek(sc->digestfp, 0L, SEEK_SET);
210 fread(pbuf, (size_t)msglen, 1, sc->digestfp);
212 CM_SetAsField(msg, eMesageText, &pbuf, msglen);
214 /* Now generate the delivery instructions */
216 /* Where do we want bounces and other noise to be heard?
217 * Surely not the list members! */
218 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
220 /* Now submit the message */
221 valid = validate_recipients(ChrPtr(sc->Users[digestrecp]), NULL, 0);
223 valid->bounce_to = strdup(bounce_to);
224 valid->envelope_from = strdup(bounce_to);
225 CtdlSubmitMsg(msg, valid, NULL, 0);
228 free_recipients(valid);
232 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
235 struct CtdlMessage *msg = NULL;
237 if (sc->Users[digestrecp] == NULL)
240 /* If there are digest recipients, we have to build a digest */
241 if (sc->digestfp == NULL) {
243 sc->digestfp = create_digest_file(&sc->room, 1);
245 if (sc->digestfp == NULL)
248 sc->haveDigest = ftell(sc->digestfp) > 0;
249 if (!sc->haveDigest) {
250 fprintf(sc->digestfp, "Content-type: text/plain\n\n");
255 msg = CM_Duplicate(omsg);
258 fprintf(sc->digestfp,
259 " -----------------------------------"
260 "------------------------------------"
262 fprintf(sc->digestfp, "From: ");
263 if (!CM_IsEmpty(msg, eAuthor)) {
264 fprintf(sc->digestfp,
266 msg->cm_fields[eAuthor]);
268 if (!CM_IsEmpty(msg, erFc822Addr)) {
269 fprintf(sc->digestfp,
271 msg->cm_fields[erFc822Addr]);
273 else if (!CM_IsEmpty(msg, eNodeName)) {
274 fprintf(sc->digestfp,
276 msg->cm_fields[eNodeName]);
278 fprintf(sc->digestfp, "\n");
279 if (!CM_IsEmpty(msg, eMsgSubject)) {
280 fprintf(sc->digestfp,
282 msg->cm_fields[eMsgSubject]);
285 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
287 safestrncpy(CC->preferred_formats,
289 sizeof CC->preferred_formats);
291 CtdlOutputPreLoadedMsg(msg,
296 StrBufTrim(CC->redirect_buffer);
297 fwrite(HKEY("\n"), 1, sc->digestfp);
298 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
299 fwrite(HKEY("\n"), 1, sc->digestfp);
301 FreeStrBuf(&CC->redirect_buffer);
303 sc->num_msgs_spooled += 1;
309 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
311 struct CtdlMessage *msg = NULL;
314 * Process mailing list recipients
316 if (sc->Users[listrecp] == NULL)
319 /* create our own copy of the message.
320 * We're going to need to modify it
321 * in order to insert the [list name] in it, etc.
324 msg = CM_Duplicate(omsg);
327 CM_SetField(msg, eReplyTo, SKEY(sc->Users[roommailalias]));
329 /* if there is no other recipient, Set the recipient
330 * of the list message to the email address of the
333 if (CM_IsEmpty(msg, eRecipient))
335 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
338 /* Set the 'List-ID' header */
339 CM_SetField(msg, eListID, SKEY(sc->ListID));
342 /* Prepend "[List name]" to the subject */
343 ListCalculateSubject(msg);
345 /* Handle delivery */
346 network_deliver_list(msg, sc, CC->room.QRname);
351 * Deliver list messages to everyone on the list ... efficiently
353 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
358 /* Don't do this if there were no recipients! */
359 if (sc->Users[listrecp] == NULL)
362 /* Now generate the delivery instructions */
364 /* Where do we want bounces and other noise to be heard?
365 * Surely not the list members! */
366 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
368 /* Now submit the message */
369 valid = validate_recipients(ChrPtr(sc->Users[listrecp]), NULL, 0);
371 valid->bounce_to = strdup(bounce_to);
372 valid->envelope_from = strdup(bounce_to);
373 valid->sending_room = strdup(RoomName);
374 CtdlSubmitMsg(msg, valid, NULL, 0);
375 free_recipients(valid);
377 /* Do not call CM_Free(msg) here; the caller will free it. */
381 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
383 struct CtdlMessage *msg = NULL;
384 int ok_to_participate = 0;
389 * Process client-side list participations for this room
391 if (sc->Users[participate] == NULL)
394 msg = CM_Duplicate(omsg);
396 /* Only send messages which originated on our own
397 * Citadel network, otherwise we'll end up sending the
398 * remote mailing list's messages back to it, which
401 ok_to_participate = 0;
402 if (!CM_IsEmpty(msg, eNodeName)) {
403 if (!strcasecmp(msg->cm_fields[eNodeName], CtdlGetConfigStr("c_nodename")))
405 ok_to_participate = 1;
408 Buf = NewStrBufPlain(CM_KEY(msg, eNodeName));
409 if (CtdlIsValidNode(NULL,
412 sc->working_ignetcfg,
413 sc->the_netmap) == 0)
415 ok_to_participate = 1;
418 if (ok_to_participate)
420 /* Replace the Internet email address of the
421 * actual author with the email address of the
422 * room itself, so the remote listserv doesn't
425 CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias]));
427 valid = validate_recipients(ChrPtr(sc->Users[participate]) , NULL, 0);
429 CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias]));
430 CtdlSubmitMsg(msg, valid, "", 0);
431 free_recipients(valid);
437 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
441 const char *Pos = NULL;
442 struct CtdlMessage *msg = NULL;
443 struct CitContext *CCC = CC;
444 struct ser_ret sermsg;
446 char filename[PATH_MAX];
453 if (sc->Users[ignet_push_share] == NULL)
457 * Process IGnet push shares
459 msg = CM_Duplicate(omsg);
461 /* Prepend our node name to the Path field whenever
462 * sending a message to another IGnet node
464 Netmap_AddMe(msg, HKEY("username"));
467 * Determine if this message is set to be deleted
468 * after sending out on the network
470 if (!CM_IsEmpty(msg, eSpecialField)) {
471 if (!strcasecmp(msg->cm_fields[eSpecialField], "CANCEL")) {
472 *delete_after_send = 1;
476 /* Now send it to every node */
477 Recipient = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share]));
478 RemoteRoom = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share]));
479 while ((Pos != StrBufNOTNULL) &&
480 StrBufExtract_NextToken(Recipient, sc->Users[ignet_push_share], &Pos, ','))
482 StrBufExtract_NextToken(RemoteRoom, sc->Users[ignet_push_share], &Pos, ',');
484 NewStrBufDupAppendFlush(&Buf, Recipient, NULL, 1);
486 /* Check for valid node name */
487 if (CtdlIsValidNode(NULL,
490 sc->working_ignetcfg,
491 sc->the_netmap) != 0)
493 syslog(LOG_ERR, "netmail: invalid node <%s>", ChrPtr(Recipient));
497 /* Check for split horizon */
498 syslog(LOG_DEBUG, "netmail: path is %s", msg->cm_fields[eMessagePath]);
499 bang = num_tokens(msg->cm_fields[eMessagePath], '!');
501 for (i=0; i<(bang-1); ++i) {
503 msg->cm_fields[eMessagePath],
507 syslog(LOG_DEBUG, "netmail: compare <%s> to <%s>", buf, ChrPtr(Recipient)) ;
508 if (!strcasecmp(buf, ChrPtr(Recipient))) {
514 syslog(LOG_INFO, "netmail: %ssending to %s", (send)?"":"not ", ChrPtr(Recipient));
517 /* Send the message */
521 * Force the message to appear in the correct
522 * room on the far end by setting the C field
525 if (StrLength(RemoteRoom) > 0) {
526 CM_SetField(msg, eRemoteRoom, SKEY(RemoteRoom));
529 CM_SetField(msg, eRemoteRoom, CCC->room.QRname, strlen(CCC->room.QRname));
532 /* serialize it for transmission */
533 CtdlSerializeMessage(&sermsg, msg);
534 if (sermsg.len > 0) {
536 /* write it to a spool file */
546 syslog(LOG_DEBUG, "netmail: appending to %s", filename);
548 fp = fopen(filename, "ab");
550 fwrite(sermsg.ser, sermsg.len, 1, fp);
554 syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno));
557 /* free the serialized version */
563 FreeStrBuf(&Recipient);
564 FreeStrBuf(&RemoteRoom);
570 * Spools out one message from the list.
572 void network_spool_msg(long msgnum, void *userdata)
574 struct CtdlMessage *msg = NULL;
575 long delete_after_send = 0; /* Set to 1 to delete after spooling */
578 sc = (SpoolControl *)userdata;
579 msg = CtdlFetchMessage(msgnum, 1, 1);
583 syslog(LOG_ERR, "netmail: failed to load Message <%ld> from disk", msgnum);
586 network_process_list(sc, msg, &delete_after_send);
587 network_process_digest(sc, msg, &delete_after_send);
588 network_process_participate(sc, msg, &delete_after_send);
589 network_process_ignetpush(sc, msg, &delete_after_send);
593 /* update lastsent */
594 sc->lastsent = msgnum;
596 /* Delete this message if delete-after-send is set */
597 if (delete_after_send) {
598 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");