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);
86 * Deliver digest messages
88 void network_deliver_digest(SpoolControl *sc)
90 struct CitContext *CCC = CC;
93 struct CtdlMessage *msg = NULL;
97 size_t recps_len = SIZ;
98 struct recptypes *valid;
102 if (sc->num_msgs_spooled < 1) {
103 fclose(sc->digestfp);
108 msg = malloc(sizeof(struct CtdlMessage));
109 memset(msg, 0, sizeof(struct CtdlMessage));
110 msg->cm_magic = CTDLMESSAGE_MAGIC;
111 msg->cm_format_type = FMT_RFC822;
112 msg->cm_anon_type = MES_NORMAL;
114 sprintf(buf, "%ld", time(NULL));
115 msg->cm_fields['T'] = strdup(buf);
116 msg->cm_fields['A'] = strdup(CC->room.QRname);
117 snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
118 msg->cm_fields['U'] = strdup(buf);
119 sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
120 for (i=0; buf[i]; ++i) {
121 if (isspace(buf[i])) buf[i]='_';
122 buf[i] = tolower(buf[i]);
124 msg->cm_fields['F'] = strdup(buf);
125 msg->cm_fields['R'] = strdup(buf);
127 /* Set the 'List-ID' header */
128 msg->cm_fields['L'] = malloc(1024);
129 snprintf(msg->cm_fields['L'], 1024,
130 "%s <%ld.list-id.%s>",
137 * Go fetch the contents of the digest
139 fseek(sc->digestfp, 0L, SEEK_END);
140 msglen = ftell(sc->digestfp);
142 msg->cm_fields['M'] = malloc(msglen + 1);
143 fseek(sc->digestfp, 0L, SEEK_SET);
144 fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
145 msg->cm_fields['M'][msglen] = '\0';
147 fclose(sc->digestfp);
150 /* Now generate the delivery instructions */
153 * Figure out how big a buffer we need to allocate
155 for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
156 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
159 recps = NewStrBufPlain(NULL, recps_len);
163 "Cannot allocate %ld bytes for recps...\n",
169 for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
170 if (nptr != sc->RNCfg->NetConfigs[digestrecp]) {
171 StrBufAppendBufPlain(recps, HKEY(","), 0);
173 StrBufAppendBuf(recps, nptr->Value[0], 0);
176 /* Where do we want bounces and other noise to be heard?
177 *Surely not the list members! */
178 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
180 /* Now submit the message */
181 precps = SmashStrBuf(&recps);
182 valid = validate_recipients(precps, NULL, 0);
185 valid->bounce_to = strdup(bounce_to);
186 valid->envelope_from = strdup(bounce_to);
187 CtdlSubmitMsg(msg, valid, NULL, 0);
189 CtdlFreeMessage(msg);
190 free_recipients(valid);
194 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
197 struct CtdlMessage *msg = NULL;
200 * Process digest recipients
202 if ((sc->RNCfg->NetConfigs[digestrecp] == NULL) ||
203 (sc->digestfp == NULL))
206 msg = CtdlDuplicateMessage(omsg);
208 fprintf(sc->digestfp,
209 " -----------------------------------"
210 "------------------------------------"
212 fprintf(sc->digestfp, "From: ");
213 if (msg->cm_fields['A'] != NULL) {
214 fprintf(sc->digestfp,
216 msg->cm_fields['A']);
218 if (msg->cm_fields['F'] != NULL) {
219 fprintf(sc->digestfp,
221 msg->cm_fields['F']);
223 else if (msg->cm_fields['N'] != NULL) {
224 fprintf(sc->digestfp,
226 msg->cm_fields['N']);
228 fprintf(sc->digestfp, "\n");
229 if (msg->cm_fields['U'] != NULL) {
230 fprintf(sc->digestfp,
232 msg->cm_fields['U']);
235 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
237 safestrncpy(CC->preferred_formats,
239 sizeof CC->preferred_formats);
241 CtdlOutputPreLoadedMsg(msg,
246 StrBufTrim(CC->redirect_buffer);
247 fwrite(HKEY("\n"), 1, sc->digestfp);
248 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
249 fwrite(HKEY("\n"), 1, sc->digestfp);
251 FreeStrBuf(&CC->redirect_buffer);
253 sc->num_msgs_spooled += 1;
254 CtdlFreeMessage(msg);
259 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
263 StrBuf *Subject, *FlatSubject;
264 struct CtdlMessage *msg = NULL;
268 * Process mailing list recipients
270 if (sc->RNCfg->NetConfigs[listrecp] == NULL)
273 /* create our own copy of the message.
274 * We're going to need to modify it
275 * in order to insert the [list name] in it, etc.
278 msg = CtdlDuplicateMessage(omsg);
280 if (msg->cm_fields['K'] != NULL)
281 free(msg->cm_fields['K']);
282 if (msg->cm_fields['V'] == NULL){
283 /* local message, no enVelope */
286 StrBufAppendBufPlain(Buf,
289 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
290 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
292 msg->cm_fields['K'] = SmashStrBuf(&Buf);
295 msg->cm_fields['K'] =
296 strdup (msg->cm_fields['V']);
298 /* Set the 'List-ID' header */
299 if (msg->cm_fields['L'] != NULL) {
300 free(msg->cm_fields['L']);
302 msg->cm_fields['L'] = malloc(1024);
303 snprintf(msg->cm_fields['L'], 1024,
304 "%s <%ld.list-id.%s>",
310 /* Prepend "[List name]" to the subject */
311 if (msg->cm_fields['U'] == NULL) {
312 Subject = NewStrBufPlain(HKEY("(no subject)"));
315 Subject = NewStrBufPlain(
316 msg->cm_fields['U'], -1);
318 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
319 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
321 rlen = strlen(CC->room.QRname);
322 pCh = strstr(ChrPtr(FlatSubject), CC->room.QRname);
324 (*(pCh + rlen) != ']') ||
325 (pCh == ChrPtr(FlatSubject)) ||
330 StrBufPlain(Subject, HKEY("["));
331 StrBufAppendBufPlain(Subject,
334 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
335 StrBufAppendBuf(Subject, FlatSubject, 0);
336 /* so we can free the right one swap them */
338 Subject = FlatSubject;
340 StrBufRFC2047encode(&Subject, FlatSubject);
343 if (msg->cm_fields['U'] != NULL)
344 free (msg->cm_fields['U']);
345 msg->cm_fields['U'] = SmashStrBuf(&Subject);
347 FreeStrBuf(&FlatSubject);
349 /* else we won't modify the buffer, since the
350 * roomname is already here.
353 /* if there is no other recipient, Set the recipient
354 * of the list message to the email address of the
357 if ((msg->cm_fields['R'] == NULL) ||
358 IsEmptyStr(msg->cm_fields['R']))
360 if (msg->cm_fields['R'] != NULL)
361 free(msg->cm_fields['R']);
363 msg->cm_fields['R'] = malloc(256);
364 snprintf(msg->cm_fields['R'], 256,
365 "room_%s@%s", CC->room.QRname,
367 for (i=0; msg->cm_fields['R'][i]; ++i) {
368 if (isspace(msg->cm_fields['R'][i])) {
369 msg->cm_fields['R'][i] = '_';
374 /* Handle delivery */
375 network_deliver_list(msg, sc, CC->room.QRname);
376 CtdlFreeMessage(msg);
380 * Deliver list messages to everyone on the list ... efficiently
382 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
384 struct CitContext *CCC = CC;
385 StrBuf *recps = NULL;
387 size_t recps_len = SIZ;
388 struct recptypes *valid;
389 RoomNetCfgLine *nptr;
392 /* Don't do this if there were no recipients! */
393 if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
395 /* Now generate the delivery instructions */
398 * Figure out how big a buffer we need to allocate
400 for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
401 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
404 recps = NewStrBufPlain(NULL, recps_len);
408 "Cannot allocate %ld bytes for recps...\n",
414 for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
415 if (nptr != sc->RNCfg->NetConfigs[listrecp]) {
416 StrBufAppendBufPlain(recps, HKEY(","), 0);
418 StrBufAppendBuf(recps, nptr->Value[0], 0);
421 /* Where do we want bounces and other noise to be heard?
422 * Surely not the list members! */
423 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
425 /* Now submit the message */
426 precps = SmashStrBuf(&recps);
427 valid = validate_recipients(precps, NULL, 0);
430 valid->bounce_to = strdup(bounce_to);
431 valid->envelope_from = strdup(bounce_to);
432 valid->sending_room = strdup(RoomName);
433 CtdlSubmitMsg(msg, valid, NULL, 0);
434 free_recipients(valid);
436 /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
440 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
442 struct CtdlMessage *msg = NULL;
444 int ok_to_participate = 0;
446 RoomNetCfgLine *nptr;
447 struct recptypes *valid;
450 * Process client-side list participations for this room
452 if (sc->RNCfg->NetConfigs[participate] == NULL)
455 msg = CtdlDuplicateMessage(omsg);
457 /* Only send messages which originated on our own
458 * Citadel network, otherwise we'll end up sending the
459 * remote mailing list's messages back to it, which
462 ok_to_participate = 0;
463 if (msg->cm_fields['N'] != NULL) {
464 if (!strcasecmp(msg->cm_fields['N'],
465 config.c_nodename)) {
466 ok_to_participate = 1;
469 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
470 if (CtdlIsValidNode(NULL,
473 sc->working_ignetcfg,
474 sc->the_netmap) == 0)
476 ok_to_participate = 1;
479 if (ok_to_participate) {
480 if (msg->cm_fields['F'] != NULL) {
481 free(msg->cm_fields['F']);
483 msg->cm_fields['F'] = malloc(SIZ);
484 /* Replace the Internet email address of the
485 * actual author with the email address of the
486 * room itself, so the remote listserv doesn't
488 * FIXME I want to be able to pick any address
490 snprintf(msg->cm_fields['F'], SIZ,
491 "room_%s@%s", CC->room.QRname,
493 for (i=0; msg->cm_fields['F'][i]; ++i) {
494 if (isspace(msg->cm_fields['F'][i])) {
495 msg->cm_fields['F'][i] = '_';
500 * Figure out how big a buffer we need to alloc
502 for (nptr = sc->RNCfg->NetConfigs[participate];
506 if (msg->cm_fields['R'] != NULL) {
507 free(msg->cm_fields['R']);
509 msg->cm_fields['R'] =
510 strdup(ChrPtr(nptr->Value[0]));
512 valid = validate_recipients(msg->cm_fields['R'],
515 CtdlSubmitMsg(msg, valid, "", 0);
516 free_recipients(valid);
520 CtdlFreeMessage(msg);
523 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
525 struct CtdlMessage *msg = NULL;
526 struct CitContext *CCC = CC;
527 struct ser_ret sermsg;
529 char filename[PATH_MAX];
532 char *newpath = NULL;
533 RoomNetCfgLine* mptr;
539 if (sc->RNCfg->NetConfigs[ignet_push_share] == NULL)
542 * Process IGnet push shares
544 msg = CtdlDuplicateMessage(omsg);
546 /* Prepend our node name to the Path field whenever
547 * sending a message to another IGnet node
549 if (msg->cm_fields['P'] == NULL)
551 msg->cm_fields['P'] = strdup("username");
553 newpath_len = strlen(msg->cm_fields['P']) +
554 strlen(config.c_nodename) + 2;
555 newpath = malloc(newpath_len);
556 snprintf(newpath, newpath_len, "%s!%s",
557 config.c_nodename, msg->cm_fields['P']);
558 free(msg->cm_fields['P']);
559 msg->cm_fields['P'] = newpath;
562 * Determine if this message is set to be deleted
563 * after sending out on the network
565 if (msg->cm_fields['S'] != NULL) {
566 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
567 *delete_after_send = 1;
571 /* Now send it to every node */
572 for (mptr = sc->RNCfg->NetConfigs[ignet_push_share];
577 NewStrBufDupAppendFlush(&Buf, mptr->Value[0], NULL, 1);
579 /* Check for valid node name */
580 if (CtdlIsValidNode(NULL,
583 sc->working_ignetcfg,
584 sc->the_netmap) != 0)
587 "Invalid node <%s>\n",
588 ChrPtr(mptr->Value[0]));
593 /* Check for split horizon */
594 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
595 bang = num_tokens(msg->cm_fields['P'], '!');
597 for (i=0; i<(bang-1); ++i) {
603 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
604 buf, ChrPtr(mptr->Value[0])) ;
605 if (!strcasecmp(buf, ChrPtr(mptr->Value[0]))) {
614 ChrPtr(mptr->Value[0]));
617 /* Send the message */
621 * Force the message to appear in the correct
622 * room on the far end by setting the C field
625 if (msg->cm_fields['C'] != NULL) {
626 free(msg->cm_fields['C']);
628 if (StrLength(mptr->Value[0]) > 0) {
629 msg->cm_fields['C'] =
630 strdup(ChrPtr(mptr->Value[0]));
633 msg->cm_fields['C'] =
634 strdup(CC->room.QRname);
637 /* serialize it for transmission */
638 serialize_message(&sermsg, msg);
639 if (sermsg.len > 0) {
641 /* write it to a spool file */
646 ChrPtr(mptr->Value[0]),
655 fp = fopen(filename, "ab");
668 /* free the serialized version */
674 CtdlFreeMessage(msg);
679 * Spools out one message from the list.
681 void network_spool_msg(long msgnum,
684 struct CtdlMessage *msg = NULL;
685 long delete_after_send = 0; /* Set to 1 to delete after spooling */
688 sc = (SpoolControl *)userdata;
690 msg = CtdlFetchMessage(msgnum, 1);
692 network_process_list(sc, msg, &delete_after_send);
693 network_process_digest(sc, msg, &delete_after_send);
694 network_process_participate(sc, msg, &delete_after_send);
695 network_process_ignetpush(sc, msg, &delete_after_send);
697 CtdlFreeMessage(msg);
699 /* update lastsent */
700 sc->lastsent = msgnum;
702 /* Delete this message if delete-after-send is set */
703 if (delete_after_send) {
704 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");