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"
80 #include "netconfig.h"
86 * Deliver digest messages
88 void network_deliver_digest(SpoolControl *sc) {
89 struct CitContext *CCC = CC;
92 struct CtdlMessage *msg = NULL;
96 size_t recps_len = SIZ;
97 struct recptypes *valid;
101 if (sc->num_msgs_spooled < 1) {
102 fclose(sc->digestfp);
107 msg = malloc(sizeof(struct CtdlMessage));
108 memset(msg, 0, sizeof(struct CtdlMessage));
109 msg->cm_magic = CTDLMESSAGE_MAGIC;
110 msg->cm_format_type = FMT_RFC822;
111 msg->cm_anon_type = MES_NORMAL;
113 sprintf(buf, "%ld", time(NULL));
114 msg->cm_fields['T'] = strdup(buf);
115 msg->cm_fields['A'] = strdup(CC->room.QRname);
116 snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
117 msg->cm_fields['U'] = strdup(buf);
118 sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
119 for (i=0; buf[i]; ++i) {
120 if (isspace(buf[i])) buf[i]='_';
121 buf[i] = tolower(buf[i]);
123 msg->cm_fields['F'] = strdup(buf);
124 msg->cm_fields['R'] = strdup(buf);
126 /* Set the 'List-ID' header */
127 msg->cm_fields['L'] = malloc(1024);
128 snprintf(msg->cm_fields['L'], 1024,
129 "%s <%ld.list-id.%s>",
136 * Go fetch the contents of the digest
138 fseek(sc->digestfp, 0L, SEEK_END);
139 msglen = ftell(sc->digestfp);
141 msg->cm_fields['M'] = malloc(msglen + 1);
142 fseek(sc->digestfp, 0L, SEEK_SET);
143 fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
144 msg->cm_fields['M'][msglen] = '\0';
146 fclose(sc->digestfp);
149 /* Now generate the delivery instructions */
152 * Figure out how big a buffer we need to allocate
154 for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
155 recps_len = recps_len + StrLength(nptr->Value) + 2;
158 recps = NewStrBufPlain(NULL, recps_len);
162 "Cannot allocate %ld bytes for recps...\n",
168 for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
169 if (nptr != sc->RNCfg->NetConfigs[digestrecp]) {
170 StrBufAppendBufPlain(recps, HKEY(","), 0);
172 StrBufAppendBuf(recps, nptr->Value, 0);
175 /* Where do we want bounces and other noise to be heard?
176 *Surely not the list members! */
177 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
179 /* Now submit the message */
180 precps = SmashStrBuf(&recps);
181 valid = validate_recipients(precps, NULL, 0);
184 valid->bounce_to = strdup(bounce_to);
185 valid->envelope_from = strdup(bounce_to);
186 CtdlSubmitMsg(msg, valid, NULL, 0);
188 CtdlFreeMessage(msg);
189 free_recipients(valid);
194 * Deliver list messages to everyone on the list ... efficiently
196 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
198 struct CitContext *CCC = CC;
199 StrBuf *recps = NULL;
201 size_t recps_len = SIZ;
202 struct recptypes *valid;
203 RoomNetCfgLine *nptr;
206 /* Don't do this if there were no recipients! */
207 if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
209 /* Now generate the delivery instructions */
212 * Figure out how big a buffer we need to allocate
214 for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
215 recps_len = recps_len + StrLength(nptr->Value) + 2;
218 recps = NewStrBufPlain(NULL, recps_len);
222 "Cannot allocate %ld bytes for recps...\n",
228 for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
229 if (nptr != sc->RNCfg->NetConfigs[listrecp]) {
230 StrBufAppendBufPlain(recps, HKEY(","), 0);
232 StrBufAppendBuf(recps, nptr->Value, 0);
235 /* Where do we want bounces and other noise to be heard?
236 * Surely not the list members! */
237 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
239 /* Now submit the message */
240 precps = SmashStrBuf(&recps);
241 valid = validate_recipients(precps, NULL, 0);
244 valid->bounce_to = strdup(bounce_to);
245 valid->envelope_from = strdup(bounce_to);
246 valid->sending_room = strdup(RoomName);
247 CtdlSubmitMsg(msg, valid, NULL, 0);
248 free_recipients(valid);
250 /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
255 * Spools out one message from the list.
257 void network_spool_msg(long msgnum,
260 struct CitContext *CCC = CC;
264 char *newpath = NULL;
265 struct CtdlMessage *msg = NULL;
266 RoomNetCfgLine *nptr;
268 struct ser_ret sermsg;
270 char filename[PATH_MAX];
274 int delete_after_send = 0; /* Set to 1 to delete after spooling */
275 int ok_to_participate = 0;
276 struct recptypes *valid;
278 sc = (SpoolControl *)userdata;
281 * Process mailing list recipients
283 if (sc->RNCfg->NetConfigs[listrecp] != NULL) {
284 /* Fetch the message. We're going to need to modify it
285 * in order to insert the [list name] in it, etc.
287 msg = CtdlFetchMessage(msgnum, 1);
291 StrBuf *Subject, *FlatSubject;
293 if (msg->cm_fields['K'] != NULL)
294 free(msg->cm_fields['K']);
295 if (msg->cm_fields['V'] == NULL){
296 /* local message, no enVelope */
299 StrBufAppendBufPlain(Buf,
302 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
303 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
305 msg->cm_fields['K'] = SmashStrBuf(&Buf);
308 msg->cm_fields['K'] =
309 strdup (msg->cm_fields['V']);
311 /* Set the 'List-ID' header */
312 if (msg->cm_fields['L'] != NULL) {
313 free(msg->cm_fields['L']);
315 msg->cm_fields['L'] = malloc(1024);
316 snprintf(msg->cm_fields['L'], 1024,
317 "%s <%ld.list-id.%s>",
323 /* Prepend "[List name]" to the subject */
324 if (msg->cm_fields['U'] == NULL) {
325 Subject = NewStrBufPlain(HKEY("(no subject)"));
328 Subject = NewStrBufPlain(
329 msg->cm_fields['U'], -1);
331 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
332 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
334 rlen = strlen(CC->room.QRname);
335 pCh = strstr(ChrPtr(FlatSubject), CC->room.QRname);
337 (*(pCh + rlen) != ']') ||
338 (pCh == ChrPtr(FlatSubject)) ||
343 StrBufPlain(Subject, HKEY("["));
344 StrBufAppendBufPlain(Subject,
347 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
348 StrBufAppendBuf(Subject, FlatSubject, 0);
349 /* so we can free the right one swap them */
351 Subject = FlatSubject;
353 StrBufRFC2047encode(&Subject, FlatSubject);
356 if (msg->cm_fields['U'] != NULL)
357 free (msg->cm_fields['U']);
358 msg->cm_fields['U'] = SmashStrBuf(&Subject);
360 FreeStrBuf(&FlatSubject);
362 /* else we won't modify the buffer, since the
363 * roomname is already here.
366 /* if there is no other recipient, Set the recipient
367 * of the list message to the email address of the
370 if ((msg->cm_fields['R'] == NULL) ||
371 IsEmptyStr(msg->cm_fields['R']))
373 if (msg->cm_fields['R'] != NULL)
374 free(msg->cm_fields['R']);
376 msg->cm_fields['R'] = malloc(256);
377 snprintf(msg->cm_fields['R'], 256,
378 "room_%s@%s", CC->room.QRname,
380 for (i=0; msg->cm_fields['R'][i]; ++i) {
381 if (isspace(msg->cm_fields['R'][i])) {
382 msg->cm_fields['R'][i] = '_';
387 /* Handle delivery */
388 network_deliver_list(msg, sc, CC->room.QRname);
389 CtdlFreeMessage(msg);
394 * Process digest recipients
396 if ((sc->RNCfg->NetConfigs[digestrecp] != NULL) && (sc->digestfp != NULL)) {
397 msg = CtdlFetchMessage(msgnum, 1);
399 fprintf(sc->digestfp,
400 " -----------------------------------"
401 "------------------------------------"
403 fprintf(sc->digestfp, "From: ");
404 if (msg->cm_fields['A'] != NULL) {
405 fprintf(sc->digestfp,
407 msg->cm_fields['A']);
409 if (msg->cm_fields['F'] != NULL) {
410 fprintf(sc->digestfp,
412 msg->cm_fields['F']);
414 else if (msg->cm_fields['N'] != NULL) {
415 fprintf(sc->digestfp,
417 msg->cm_fields['N']);
419 fprintf(sc->digestfp, "\n");
420 if (msg->cm_fields['U'] != NULL) {
421 fprintf(sc->digestfp,
423 msg->cm_fields['U']);
426 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
428 safestrncpy(CC->preferred_formats,
430 sizeof CC->preferred_formats);
432 CtdlOutputPreLoadedMsg(msg,
437 StrBufTrim(CC->redirect_buffer);
438 fwrite(HKEY("\n"), 1, sc->digestfp);
439 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
440 fwrite(HKEY("\n"), 1, sc->digestfp);
442 FreeStrBuf(&CC->redirect_buffer);
444 sc->num_msgs_spooled += 1;
445 CtdlFreeMessage(msg);
450 * Process client-side list participations for this room
452 if (sc->RNCfg->NetConfigs[participate] != NULL) {
453 msg = CtdlFetchMessage(msgnum, 1);
456 /* Only send messages which originated on our own
457 * Citadel network, otherwise we'll end up sending the
458 * remote mailing list's messages back to it, which
461 ok_to_participate = 0;
462 if (msg->cm_fields['N'] != NULL) {
463 if (!strcasecmp(msg->cm_fields['N'],
464 config.c_nodename)) {
465 ok_to_participate = 1;
468 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
469 if (CtdlIsValidNode(NULL,
472 sc->working_ignetcfg,
473 sc->the_netmap) == 0)
475 ok_to_participate = 1;
478 if (ok_to_participate) {
479 if (msg->cm_fields['F'] != NULL) {
480 free(msg->cm_fields['F']);
482 msg->cm_fields['F'] = malloc(SIZ);
483 /* Replace the Internet email address of the
484 * actual author with the email address of the
485 * room itself, so the remote listserv doesn't
487 * FIXME I want to be able to pick any address
489 snprintf(msg->cm_fields['F'], SIZ,
490 "room_%s@%s", CC->room.QRname,
492 for (i=0; msg->cm_fields['F'][i]; ++i) {
493 if (isspace(msg->cm_fields['F'][i])) {
494 msg->cm_fields['F'][i] = '_';
499 * Figure out how big a buffer we need to alloc
501 for (nptr = sc->RNCfg->NetConfigs[participate];
505 if (msg->cm_fields['R'] != NULL) {
506 free(msg->cm_fields['R']);
508 msg->cm_fields['R'] =
509 strdup(ChrPtr(nptr->Value));
511 valid = validate_recipients(msg->cm_fields['R'],
514 CtdlSubmitMsg(msg, valid, "", 0);
515 free_recipients(valid);
518 CtdlFreeMessage(msg);
523 * Process IGnet push shares
525 msg = CtdlFetchMessage(msgnum, 1);
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) {
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 if (sc->RNCfg->NetConfigs[ignet_push_share] != NULL)
555 for (mptr = (MapList*)sc->RNCfg->NetConfigs[ignet_push_share]; mptr != NULL;
559 NewStrBufDupAppendFlush(&Buf, mptr->remote_nodename, 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->remote_nodename));
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->remote_nodename)) ;
587 if (!strcasecmp(buf, ChrPtr(mptr->remote_nodename))) {
596 ChrPtr(mptr->remote_nodename));
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->remote_roomname) > 0) {
611 msg->cm_fields['C'] =
612 strdup(ChrPtr(mptr->remote_roomname));
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->remote_nodename),
637 fp = fopen(filename, "ab");
650 /* free the serialized version */
656 CtdlFreeMessage(msg);
659 /* update lastsent */
660 sc->RNCfg->lastsent = msgnum;
662 /* Delete this message if delete-after-send is set */
663 if (delete_after_send) {
664 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");