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;
93 * Figure out how big a buffer we need to allocate
95 for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
96 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
103 *recps = NewStrBufPlain(NULL, recps_len);
105 if (*recps == NULL) {
107 "Cannot allocate %ld bytes for recps...\n",
113 for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) {
114 if (nptr != OneRNCfg->NetConfigs[Which]) {
115 StrBufAppendBufPlain(*recps, HKEY(","), 0);
117 StrBufAppendBuf(*recps, nptr->Value[0], 0);
122 * Deliver digest messages
124 void network_deliver_digest(SpoolControl *sc)
128 struct CtdlMessage *msg = NULL;
130 StrBuf *recps = NULL;
132 struct recptypes *valid;
135 if (sc->num_msgs_spooled < 1) {
136 fclose(sc->digestfp);
141 msg = malloc(sizeof(struct CtdlMessage));
142 memset(msg, 0, sizeof(struct CtdlMessage));
143 msg->cm_magic = CTDLMESSAGE_MAGIC;
144 msg->cm_format_type = FMT_RFC822;
145 msg->cm_anon_type = MES_NORMAL;
147 sprintf(buf, "%ld", time(NULL));
148 msg->cm_fields['T'] = strdup(buf);
149 msg->cm_fields['A'] = strdup(CC->room.QRname);
150 snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
151 msg->cm_fields['U'] = strdup(buf);
152 sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
153 for (i=0; buf[i]; ++i) {
154 if (isspace(buf[i])) buf[i]='_';
155 buf[i] = tolower(buf[i]);
157 msg->cm_fields['F'] = strdup(buf);
158 msg->cm_fields['R'] = strdup(buf);
160 /* Set the 'List-ID' header */
161 msg->cm_fields['L'] = malloc(1024);
162 snprintf(msg->cm_fields['L'], 1024,
163 "%s <%ld.list-id.%s>",
170 * Go fetch the contents of the digest
172 fseek(sc->digestfp, 0L, SEEK_END);
173 msglen = ftell(sc->digestfp);
175 msg->cm_fields['M'] = malloc(msglen + 1);
176 fseek(sc->digestfp, 0L, SEEK_SET);
177 fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
178 msg->cm_fields['M'][msglen] = '\0';
180 fclose(sc->digestfp);
183 /* Now generate the delivery instructions */
184 aggregate_recipients(&recps, digestrecp, sc->RNCfg);
186 /* Where do we want bounces and other noise to be heard?
187 *Surely not the list members! */
188 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
190 /* Now submit the message */
191 precps = SmashStrBuf(&recps);
192 valid = validate_recipients(precps, NULL, 0);
195 valid->bounce_to = strdup(bounce_to);
196 valid->envelope_from = strdup(bounce_to);
197 CtdlSubmitMsg(msg, valid, NULL, 0);
199 CtdlFreeMessage(msg);
200 free_recipients(valid);
204 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
207 struct CtdlMessage *msg = NULL;
210 * Process digest recipients
212 if ((sc->RNCfg->NetConfigs[digestrecp] == NULL) ||
213 (sc->digestfp == NULL))
216 msg = CtdlDuplicateMessage(omsg);
218 fprintf(sc->digestfp,
219 " -----------------------------------"
220 "------------------------------------"
222 fprintf(sc->digestfp, "From: ");
223 if (msg->cm_fields['A'] != NULL) {
224 fprintf(sc->digestfp,
226 msg->cm_fields['A']);
228 if (msg->cm_fields['F'] != NULL) {
229 fprintf(sc->digestfp,
231 msg->cm_fields['F']);
233 else if (msg->cm_fields['N'] != NULL) {
234 fprintf(sc->digestfp,
236 msg->cm_fields['N']);
238 fprintf(sc->digestfp, "\n");
239 if (msg->cm_fields['U'] != NULL) {
240 fprintf(sc->digestfp,
242 msg->cm_fields['U']);
245 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
247 safestrncpy(CC->preferred_formats,
249 sizeof CC->preferred_formats);
251 CtdlOutputPreLoadedMsg(msg,
256 StrBufTrim(CC->redirect_buffer);
257 fwrite(HKEY("\n"), 1, sc->digestfp);
258 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
259 fwrite(HKEY("\n"), 1, sc->digestfp);
261 FreeStrBuf(&CC->redirect_buffer);
263 sc->num_msgs_spooled += 1;
264 CtdlFreeMessage(msg);
269 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
273 StrBuf *Subject, *FlatSubject;
274 struct CtdlMessage *msg = NULL;
278 * Process mailing list recipients
280 if (sc->RNCfg->NetConfigs[listrecp] == NULL)
283 /* create our own copy of the message.
284 * We're going to need to modify it
285 * in order to insert the [list name] in it, etc.
288 msg = CtdlDuplicateMessage(omsg);
290 if (msg->cm_fields['K'] != NULL)
291 free(msg->cm_fields['K']);
292 if (msg->cm_fields['V'] == NULL){
293 /* local message, no enVelope */
296 StrBufAppendBufPlain(Buf,
299 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
300 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
302 msg->cm_fields['K'] = SmashStrBuf(&Buf);
305 msg->cm_fields['K'] =
306 strdup (msg->cm_fields['V']);
308 /* Set the 'List-ID' header */
309 if (msg->cm_fields['L'] != NULL) {
310 free(msg->cm_fields['L']);
312 msg->cm_fields['L'] = malloc(1024);
313 snprintf(msg->cm_fields['L'], 1024,
314 "%s <%ld.list-id.%s>",
320 /* Prepend "[List name]" to the subject */
321 if (msg->cm_fields['U'] == NULL) {
322 Subject = NewStrBufPlain(HKEY("(no subject)"));
325 Subject = NewStrBufPlain(
326 msg->cm_fields['U'], -1);
328 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
329 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
331 rlen = strlen(CC->room.QRname);
332 pCh = strstr(ChrPtr(FlatSubject), CC->room.QRname);
334 (*(pCh + rlen) != ']') ||
335 (pCh == ChrPtr(FlatSubject)) ||
340 StrBufPlain(Subject, HKEY("["));
341 StrBufAppendBufPlain(Subject,
344 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
345 StrBufAppendBuf(Subject, FlatSubject, 0);
346 /* so we can free the right one swap them */
348 Subject = FlatSubject;
350 StrBufRFC2047encode(&Subject, FlatSubject);
353 if (msg->cm_fields['U'] != NULL)
354 free (msg->cm_fields['U']);
355 msg->cm_fields['U'] = SmashStrBuf(&Subject);
357 FreeStrBuf(&FlatSubject);
359 /* else we won't modify the buffer, since the
360 * roomname is already here.
363 /* if there is no other recipient, Set the recipient
364 * of the list message to the email address of the
367 if ((msg->cm_fields['R'] == NULL) ||
368 IsEmptyStr(msg->cm_fields['R']))
370 if (msg->cm_fields['R'] != NULL)
371 free(msg->cm_fields['R']);
373 msg->cm_fields['R'] = malloc(256);
374 snprintf(msg->cm_fields['R'], 256,
375 "room_%s@%s", CC->room.QRname,
377 for (i=0; msg->cm_fields['R'][i]; ++i) {
378 if (isspace(msg->cm_fields['R'][i])) {
379 msg->cm_fields['R'][i] = '_';
384 /* Handle delivery */
385 network_deliver_list(msg, sc, CC->room.QRname);
386 CtdlFreeMessage(msg);
390 * Deliver list messages to everyone on the list ... efficiently
392 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
394 StrBuf *recps = NULL;
396 struct recptypes *valid;
399 /* Don't do this if there were no recipients! */
400 if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
402 /* Now generate the delivery instructions */
405 * Figure out how big a buffer we need to allocate
407 aggregate_recipients(&recps, listrecp, sc->RNCfg);
409 /* Where do we want bounces and other noise to be heard?
410 * Surely not the list members! */
411 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
413 /* Now submit the message */
414 precps = SmashStrBuf(&recps);
415 valid = validate_recipients(precps, NULL, 0);
418 valid->bounce_to = strdup(bounce_to);
419 valid->envelope_from = strdup(bounce_to);
420 valid->sending_room = strdup(RoomName);
421 CtdlSubmitMsg(msg, valid, NULL, 0);
422 free_recipients(valid);
424 /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
428 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
430 struct CtdlMessage *msg = NULL;
432 int ok_to_participate = 0;
434 struct recptypes *valid;
437 * Process client-side list participations for this room
439 if (sc->RNCfg->NetConfigs[participate] == NULL)
442 msg = CtdlDuplicateMessage(omsg);
444 /* Only send messages which originated on our own
445 * Citadel network, otherwise we'll end up sending the
446 * remote mailing list's messages back to it, which
449 ok_to_participate = 0;
450 if (msg->cm_fields['N'] != NULL) {
451 if (!strcasecmp(msg->cm_fields['N'],
452 config.c_nodename)) {
453 ok_to_participate = 1;
456 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
457 if (CtdlIsValidNode(NULL,
460 sc->working_ignetcfg,
461 sc->the_netmap) == 0)
463 ok_to_participate = 1;
466 if (ok_to_participate)
468 StrBuf *recps = NULL;
471 if (msg->cm_fields['F'] != NULL) {
472 free(msg->cm_fields['F']);
474 msg->cm_fields['F'] = malloc(SIZ);
475 /* Replace the Internet email address of the
476 * actual author with the email address of the
477 * room itself, so the remote listserv doesn't
479 * FIXME I want to be able to pick any address
481 snprintf(msg->cm_fields['F'], SIZ,
482 "room_%s@%s", CC->room.QRname,
484 for (i=0; msg->cm_fields['F'][i]; ++i) {
485 if (isspace(msg->cm_fields['F'][i])) {
486 msg->cm_fields['F'][i] = '_';
490 aggregate_recipients(&recps, participate, sc->RNCfg);
491 precps = SmashStrBuf(&recps);
492 valid = validate_recipients(precps, NULL, 0);
494 if (msg->cm_fields['R'] != NULL) {
495 free(msg->cm_fields['R']);
496 }/* TODO: check whether 'R' is set appropriate later. */
498 CtdlSubmitMsg(msg, valid, "", 0);
499 free_recipients(valid);
502 CtdlFreeMessage(msg);
505 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
507 struct CtdlMessage *msg = NULL;
508 struct CitContext *CCC = CC;
509 struct ser_ret sermsg;
511 char filename[PATH_MAX];
514 char *newpath = NULL;
515 RoomNetCfgLine* mptr;
521 if (sc->RNCfg->NetConfigs[ignet_push_share] == NULL)
524 * Process IGnet push shares
526 msg = CtdlDuplicateMessage(omsg);
528 /* Prepend our node name to the Path field whenever
529 * sending a message to another IGnet node
531 if (msg->cm_fields['P'] == NULL)
533 msg->cm_fields['P'] = strdup("username");
535 newpath_len = strlen(msg->cm_fields['P']) +
536 strlen(config.c_nodename) + 2;
537 newpath = malloc(newpath_len);
538 snprintf(newpath, newpath_len, "%s!%s",
539 config.c_nodename, msg->cm_fields['P']);
540 free(msg->cm_fields['P']);
541 msg->cm_fields['P'] = newpath;
544 * Determine if this message is set to be deleted
545 * after sending out on the network
547 if (msg->cm_fields['S'] != NULL) {
548 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
549 *delete_after_send = 1;
553 /* Now send it to every node */
554 for (mptr = sc->RNCfg->NetConfigs[ignet_push_share];
559 NewStrBufDupAppendFlush(&Buf, mptr->Value[0], NULL, 1);
561 /* Check for valid node name */
562 if (CtdlIsValidNode(NULL,
565 sc->working_ignetcfg,
566 sc->the_netmap) != 0)
569 "Invalid node <%s>\n",
570 ChrPtr(mptr->Value[0]));
575 /* Check for split horizon */
576 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
577 bang = num_tokens(msg->cm_fields['P'], '!');
579 for (i=0; i<(bang-1); ++i) {
585 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
586 buf, ChrPtr(mptr->Value[0])) ;
587 if (!strcasecmp(buf, ChrPtr(mptr->Value[0]))) {
596 ChrPtr(mptr->Value[0]));
599 /* Send the message */
603 * Force the message to appear in the correct
604 * room on the far end by setting the C field
607 if (msg->cm_fields['C'] != NULL) {
608 free(msg->cm_fields['C']);
610 if (StrLength(mptr->Value[0]) > 0) {
611 msg->cm_fields['C'] =
612 strdup(ChrPtr(mptr->Value[0]));
615 msg->cm_fields['C'] =
616 strdup(CC->room.QRname);
619 /* serialize it for transmission */
620 serialize_message(&sermsg, msg);
621 if (sermsg.len > 0) {
623 /* write it to a spool file */
628 ChrPtr(mptr->Value[0]),
637 fp = fopen(filename, "ab");
650 /* free the serialized version */
656 CtdlFreeMessage(msg);
661 * Spools out one message from the list.
663 void network_spool_msg(long msgnum,
666 struct CtdlMessage *msg = NULL;
667 long delete_after_send = 0; /* Set to 1 to delete after spooling */
670 sc = (SpoolControl *)userdata;
672 msg = CtdlFetchMessage(msgnum, 1);
674 network_process_list(sc, msg, &delete_after_send);
675 network_process_digest(sc, msg, &delete_after_send);
676 network_process_participate(sc, msg, &delete_after_send);
677 network_process_ignetpush(sc, msg, &delete_after_send);
679 CtdlFreeMessage(msg);
681 /* update lastsent */
682 sc->lastsent = msgnum;
684 /* Delete this message if delete-after-send is set */
685 if (delete_after_send) {
686 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");