2 * This module handles shared rooms, inter-Citadel mail, and outbound
3 * mailing list processing.
5 * Copyright (c) 2000-2012 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"
76 #include "citadel_dirs.h"
79 #include "ctdl_module.h"
83 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName);
85 void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg)
89 struct CitContext *CCC = CC;
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;
98 *recps = NewStrBufPlain(NULL, recps_len);
100 if (*recps == NULL) {
102 "Cannot allocate %ld bytes for recps...\n",
108 for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
109 if (nptr != OneRNCfg->NetConfigs[Which]) {
110 StrBufAppendBufPlain(*recps, HKEY(","), 0);
112 StrBufAppendBuf(*recps, nptr->Value[0], 0);
117 * Deliver digest messages
119 void network_deliver_digest(SpoolControl *sc)
123 struct CtdlMessage *msg = NULL;
125 StrBuf *recps = NULL;
127 struct recptypes *valid;
130 if (sc->num_msgs_spooled < 1) {
131 fclose(sc->digestfp);
136 msg = malloc(sizeof(struct CtdlMessage));
137 memset(msg, 0, sizeof(struct CtdlMessage));
138 msg->cm_magic = CTDLMESSAGE_MAGIC;
139 msg->cm_format_type = FMT_RFC822;
140 msg->cm_anon_type = MES_NORMAL;
142 sprintf(buf, "%ld", time(NULL));
143 msg->cm_fields['T'] = strdup(buf);
144 msg->cm_fields['A'] = strdup(CC->room.QRname);
145 snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
146 msg->cm_fields['U'] = strdup(buf);
147 sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
148 for (i=0; buf[i]; ++i) {
149 if (isspace(buf[i])) buf[i]='_';
150 buf[i] = tolower(buf[i]);
152 msg->cm_fields['F'] = strdup(buf);
153 msg->cm_fields['R'] = strdup(buf);
155 /* Set the 'List-ID' header */
156 msg->cm_fields['L'] = malloc(1024);
157 snprintf(msg->cm_fields['L'], 1024,
158 "%s <%ld.list-id.%s>",
165 * Go fetch the contents of the digest
167 fseek(sc->digestfp, 0L, SEEK_END);
168 msglen = ftell(sc->digestfp);
170 msg->cm_fields['M'] = malloc(msglen + 1);
171 fseek(sc->digestfp, 0L, SEEK_SET);
172 fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
173 msg->cm_fields['M'][msglen] = '\0';
175 fclose(sc->digestfp);
178 /* Now generate the delivery instructions */
179 aggregate_recipients(&recps, digestrecp, sc->RNCfg);
181 /* Where do we want bounces and other noise to be heard?
182 *Surely not the list members! */
183 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
185 /* Now submit the message */
186 precps = SmashStrBuf(&recps);
187 valid = validate_recipients(precps, NULL, 0);
190 valid->bounce_to = strdup(bounce_to);
191 valid->envelope_from = strdup(bounce_to);
192 CtdlSubmitMsg(msg, valid, NULL, 0);
194 CtdlFreeMessage(msg);
195 free_recipients(valid);
199 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
202 struct CtdlMessage *msg = NULL;
205 * Process digest recipients
207 if ((sc->RNCfg->NetConfigs[digestrecp] == NULL) ||
208 (sc->digestfp == NULL))
211 msg = CtdlDuplicateMessage(omsg);
213 fprintf(sc->digestfp,
214 " -----------------------------------"
215 "------------------------------------"
217 fprintf(sc->digestfp, "From: ");
218 if (msg->cm_fields['A'] != NULL) {
219 fprintf(sc->digestfp,
221 msg->cm_fields['A']);
223 if (msg->cm_fields['F'] != NULL) {
224 fprintf(sc->digestfp,
226 msg->cm_fields['F']);
228 else if (msg->cm_fields['N'] != NULL) {
229 fprintf(sc->digestfp,
231 msg->cm_fields['N']);
233 fprintf(sc->digestfp, "\n");
234 if (msg->cm_fields['U'] != NULL) {
235 fprintf(sc->digestfp,
237 msg->cm_fields['U']);
240 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
242 safestrncpy(CC->preferred_formats,
244 sizeof CC->preferred_formats);
246 CtdlOutputPreLoadedMsg(msg,
251 StrBufTrim(CC->redirect_buffer);
252 fwrite(HKEY("\n"), 1, sc->digestfp);
253 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
254 fwrite(HKEY("\n"), 1, sc->digestfp);
256 FreeStrBuf(&CC->redirect_buffer);
258 sc->num_msgs_spooled += 1;
259 CtdlFreeMessage(msg);
264 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
268 StrBuf *Subject, *FlatSubject;
269 struct CtdlMessage *msg = NULL;
273 * Process mailing list recipients
275 if (sc->RNCfg->NetConfigs[listrecp] == NULL)
278 /* create our own copy of the message.
279 * We're going to need to modify it
280 * in order to insert the [list name] in it, etc.
283 msg = CtdlDuplicateMessage(omsg);
285 if (msg->cm_fields['K'] != NULL)
286 free(msg->cm_fields['K']);
287 if (msg->cm_fields['V'] == NULL){
288 /* local message, no enVelope */
291 StrBufAppendBufPlain(Buf,
294 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
295 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
297 msg->cm_fields['K'] = SmashStrBuf(&Buf);
300 msg->cm_fields['K'] =
301 strdup (msg->cm_fields['V']);
303 /* Set the 'List-ID' header */
304 if (msg->cm_fields['L'] != NULL) {
305 free(msg->cm_fields['L']);
307 msg->cm_fields['L'] = malloc(1024);
308 snprintf(msg->cm_fields['L'], 1024,
309 "%s <%ld.list-id.%s>",
315 /* Prepend "[List name]" to the subject */
316 if (msg->cm_fields['U'] == NULL) {
317 Subject = NewStrBufPlain(HKEY("(no subject)"));
320 Subject = NewStrBufPlain(
321 msg->cm_fields['U'], -1);
323 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
324 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
326 rlen = strlen(CC->room.QRname);
327 pCh = strstr(ChrPtr(FlatSubject), CC->room.QRname);
329 (*(pCh + rlen) != ']') ||
330 (pCh == ChrPtr(FlatSubject)) ||
335 StrBufPlain(Subject, HKEY("["));
336 StrBufAppendBufPlain(Subject,
339 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
340 StrBufAppendBuf(Subject, FlatSubject, 0);
341 /* so we can free the right one swap them */
343 Subject = FlatSubject;
345 StrBufRFC2047encode(&Subject, FlatSubject);
348 if (msg->cm_fields['U'] != NULL)
349 free (msg->cm_fields['U']);
350 msg->cm_fields['U'] = SmashStrBuf(&Subject);
352 FreeStrBuf(&FlatSubject);
354 /* else we won't modify the buffer, since the
355 * roomname is already here.
358 /* if there is no other recipient, Set the recipient
359 * of the list message to the email address of the
362 if ((msg->cm_fields['R'] == NULL) ||
363 IsEmptyStr(msg->cm_fields['R']))
365 if (msg->cm_fields['R'] != NULL)
366 free(msg->cm_fields['R']);
368 msg->cm_fields['R'] = malloc(256);
369 snprintf(msg->cm_fields['R'], 256,
370 "room_%s@%s", CC->room.QRname,
372 for (i=0; msg->cm_fields['R'][i]; ++i) {
373 if (isspace(msg->cm_fields['R'][i])) {
374 msg->cm_fields['R'][i] = '_';
379 /* Handle delivery */
380 network_deliver_list(msg, sc, CC->room.QRname);
381 CtdlFreeMessage(msg);
385 * Deliver list messages to everyone on the list ... efficiently
387 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
389 StrBuf *recps = NULL;
391 struct recptypes *valid;
394 /* Don't do this if there were no recipients! */
395 if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
397 /* Now generate the delivery instructions */
400 * Figure out how big a buffer we need to allocate
402 aggregate_recipients(&recps, listrecp, sc->RNCfg);
404 /* Where do we want bounces and other noise to be heard?
405 * Surely not the list members! */
406 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
408 /* Now submit the message */
409 precps = SmashStrBuf(&recps);
410 valid = validate_recipients(precps, NULL, 0);
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);
419 /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
423 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
425 struct CtdlMessage *msg = NULL;
427 int ok_to_participate = 0;
429 RoomNetCfgLine *nptr;
430 struct recptypes *valid;
433 * Process client-side list participations for this room
435 if (sc->RNCfg->NetConfigs[participate] == NULL)
438 msg = CtdlDuplicateMessage(omsg);
440 /* Only send messages which originated on our own
441 * Citadel network, otherwise we'll end up sending the
442 * remote mailing list's messages back to it, which
445 ok_to_participate = 0;
446 if (msg->cm_fields['N'] != NULL) {
447 if (!strcasecmp(msg->cm_fields['N'],
448 config.c_nodename)) {
449 ok_to_participate = 1;
452 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
453 if (CtdlIsValidNode(NULL,
456 sc->working_ignetcfg,
457 sc->the_netmap) == 0)
459 ok_to_participate = 1;
462 if (ok_to_participate) {
463 if (msg->cm_fields['F'] != NULL) {
464 free(msg->cm_fields['F']);
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
471 * FIXME I want to be able to pick any address
473 snprintf(msg->cm_fields['F'], SIZ,
474 "room_%s@%s", CC->room.QRname,
476 for (i=0; msg->cm_fields['F'][i]; ++i) {
477 if (isspace(msg->cm_fields['F'][i])) {
478 msg->cm_fields['F'][i] = '_';
483 * Figure out how big a buffer we need to alloc
485 for (nptr = sc->RNCfg->NetConfigs[participate];
489 if (msg->cm_fields['R'] != NULL) {
490 free(msg->cm_fields['R']);
492 msg->cm_fields['R'] =
493 strdup(ChrPtr(nptr->Value[0]));
495 valid = validate_recipients(msg->cm_fields['R'],
498 CtdlSubmitMsg(msg, valid, "", 0);
499 free_recipients(valid);
503 CtdlFreeMessage(msg);
506 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
508 struct CtdlMessage *msg = NULL;
509 struct CitContext *CCC = CC;
510 struct ser_ret sermsg;
512 char filename[PATH_MAX];
515 char *newpath = NULL;
516 RoomNetCfgLine* mptr;
522 if (sc->RNCfg->NetConfigs[ignet_push_share] == NULL)
525 * Process IGnet push shares
527 msg = CtdlDuplicateMessage(omsg);
529 /* Prepend our node name to the Path field whenever
530 * sending a message to another IGnet node
532 if (msg->cm_fields['P'] == NULL)
534 msg->cm_fields['P'] = strdup("username");
536 newpath_len = strlen(msg->cm_fields['P']) +
537 strlen(config.c_nodename) + 2;
538 newpath = malloc(newpath_len);
539 snprintf(newpath, newpath_len, "%s!%s",
540 config.c_nodename, msg->cm_fields['P']);
541 free(msg->cm_fields['P']);
542 msg->cm_fields['P'] = newpath;
545 * Determine if this message is set to be deleted
546 * after sending out on the network
548 if (msg->cm_fields['S'] != NULL) {
549 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
550 *delete_after_send = 1;
554 /* Now send it to every node */
555 for (mptr = sc->RNCfg->NetConfigs[ignet_push_share];
560 NewStrBufDupAppendFlush(&Buf, mptr->Value[0], NULL, 1);
562 /* Check for valid node name */
563 if (CtdlIsValidNode(NULL,
566 sc->working_ignetcfg,
567 sc->the_netmap) != 0)
570 "Invalid node <%s>\n",
571 ChrPtr(mptr->Value[0]));
576 /* Check for split horizon */
577 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
578 bang = num_tokens(msg->cm_fields['P'], '!');
580 for (i=0; i<(bang-1); ++i) {
586 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
587 buf, ChrPtr(mptr->Value[0])) ;
588 if (!strcasecmp(buf, ChrPtr(mptr->Value[0]))) {
597 ChrPtr(mptr->Value[0]));
600 /* Send the message */
604 * Force the message to appear in the correct
605 * room on the far end by setting the C field
608 if (msg->cm_fields['C'] != NULL) {
609 free(msg->cm_fields['C']);
611 if (StrLength(mptr->Value[0]) > 0) {
612 msg->cm_fields['C'] =
613 strdup(ChrPtr(mptr->Value[0]));
616 msg->cm_fields['C'] =
617 strdup(CC->room.QRname);
620 /* serialize it for transmission */
621 serialize_message(&sermsg, msg);
622 if (sermsg.len > 0) {
624 /* write it to a spool file */
629 ChrPtr(mptr->Value[0]),
638 fp = fopen(filename, "ab");
651 /* free the serialized version */
657 CtdlFreeMessage(msg);
662 * Spools out one message from the list.
664 void network_spool_msg(long msgnum,
667 struct CtdlMessage *msg = NULL;
668 long delete_after_send = 0; /* Set to 1 to delete after spooling */
671 sc = (SpoolControl *)userdata;
673 msg = CtdlFetchMessage(msgnum, 1);
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);
680 CtdlFreeMessage(msg);
682 /* update lastsent */
683 sc->lastsent = msgnum;
685 /* Delete this message if delete-after-send is set */
686 if (delete_after_send) {
687 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");