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"
85 * Deliver digest messages
87 void network_deliver_digest(SpoolControl *sc) {
88 struct CitContext *CCC = CC;
91 struct CtdlMessage *msg = NULL;
95 size_t recps_len = SIZ;
96 struct recptypes *valid;
100 if (sc->num_msgs_spooled < 1) {
101 fclose(sc->digestfp);
106 msg = malloc(sizeof(struct CtdlMessage));
107 memset(msg, 0, sizeof(struct CtdlMessage));
108 msg->cm_magic = CTDLMESSAGE_MAGIC;
109 msg->cm_format_type = FMT_RFC822;
110 msg->cm_anon_type = MES_NORMAL;
112 sprintf(buf, "%ld", time(NULL));
113 msg->cm_fields['T'] = strdup(buf);
114 msg->cm_fields['A'] = strdup(CC->room.QRname);
115 snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
116 msg->cm_fields['U'] = strdup(buf);
117 sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
118 for (i=0; buf[i]; ++i) {
119 if (isspace(buf[i])) buf[i]='_';
120 buf[i] = tolower(buf[i]);
122 msg->cm_fields['F'] = strdup(buf);
123 msg->cm_fields['R'] = strdup(buf);
125 /* Set the 'List-ID' header */
126 msg->cm_fields['L'] = malloc(1024);
127 snprintf(msg->cm_fields['L'], 1024,
128 "%s <%ld.list-id.%s>",
135 * Go fetch the contents of the digest
137 fseek(sc->digestfp, 0L, SEEK_END);
138 msglen = ftell(sc->digestfp);
140 msg->cm_fields['M'] = malloc(msglen + 1);
141 fseek(sc->digestfp, 0L, SEEK_SET);
142 fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
143 msg->cm_fields['M'][msglen] = '\0';
145 fclose(sc->digestfp);
148 /* Now generate the delivery instructions */
151 * Figure out how big a buffer we need to allocate
153 for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
154 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
157 recps = NewStrBufPlain(NULL, recps_len);
161 "Cannot allocate %ld bytes for recps...\n",
167 for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
168 if (nptr != sc->RNCfg->NetConfigs[digestrecp]) {
169 StrBufAppendBufPlain(recps, HKEY(","), 0);
171 StrBufAppendBuf(recps, nptr->Value[0], 0);
174 /* Where do we want bounces and other noise to be heard?
175 *Surely not the list members! */
176 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
178 /* Now submit the message */
179 precps = SmashStrBuf(&recps);
180 valid = validate_recipients(precps, NULL, 0);
183 valid->bounce_to = strdup(bounce_to);
184 valid->envelope_from = strdup(bounce_to);
185 CtdlSubmitMsg(msg, valid, NULL, 0);
187 CtdlFreeMessage(msg);
188 free_recipients(valid);
193 * Deliver list messages to everyone on the list ... efficiently
195 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
197 struct CitContext *CCC = CC;
198 StrBuf *recps = NULL;
200 size_t recps_len = SIZ;
201 struct recptypes *valid;
202 RoomNetCfgLine *nptr;
205 /* Don't do this if there were no recipients! */
206 if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
208 /* Now generate the delivery instructions */
211 * Figure out how big a buffer we need to allocate
213 for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
214 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
217 recps = NewStrBufPlain(NULL, recps_len);
221 "Cannot allocate %ld bytes for recps...\n",
227 for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
228 if (nptr != sc->RNCfg->NetConfigs[listrecp]) {
229 StrBufAppendBufPlain(recps, HKEY(","), 0);
231 StrBufAppendBuf(recps, nptr->Value[0], 0);
234 /* Where do we want bounces and other noise to be heard?
235 * Surely not the list members! */
236 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
238 /* Now submit the message */
239 precps = SmashStrBuf(&recps);
240 valid = validate_recipients(precps, NULL, 0);
243 valid->bounce_to = strdup(bounce_to);
244 valid->envelope_from = strdup(bounce_to);
245 valid->sending_room = strdup(RoomName);
246 CtdlSubmitMsg(msg, valid, NULL, 0);
247 free_recipients(valid);
249 /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
254 * Spools out one message from the list.
256 void network_spool_msg(long msgnum,
259 RoomNetCfgLine* mptr;
260 struct CitContext *CCC = CC;
264 char *newpath = NULL;
265 struct CtdlMessage *msg = NULL;
266 RoomNetCfgLine *nptr;
267 struct ser_ret sermsg;
269 char filename[PATH_MAX];
273 int delete_after_send = 0; /* Set to 1 to delete after spooling */
274 int ok_to_participate = 0;
275 struct recptypes *valid;
277 sc = (SpoolControl *)userdata;
280 * Process mailing list recipients
282 if (sc->RNCfg->NetConfigs[listrecp] != NULL) {
283 /* Fetch the message. We're going to need to modify it
284 * in order to insert the [list name] in it, etc.
286 msg = CtdlFetchMessage(msgnum, 1);
290 StrBuf *Subject, *FlatSubject;
292 if (msg->cm_fields['K'] != NULL)
293 free(msg->cm_fields['K']);
294 if (msg->cm_fields['V'] == NULL){
295 /* local message, no enVelope */
298 StrBufAppendBufPlain(Buf,
301 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
302 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
304 msg->cm_fields['K'] = SmashStrBuf(&Buf);
307 msg->cm_fields['K'] =
308 strdup (msg->cm_fields['V']);
310 /* Set the 'List-ID' header */
311 if (msg->cm_fields['L'] != NULL) {
312 free(msg->cm_fields['L']);
314 msg->cm_fields['L'] = malloc(1024);
315 snprintf(msg->cm_fields['L'], 1024,
316 "%s <%ld.list-id.%s>",
322 /* Prepend "[List name]" to the subject */
323 if (msg->cm_fields['U'] == NULL) {
324 Subject = NewStrBufPlain(HKEY("(no subject)"));
327 Subject = NewStrBufPlain(
328 msg->cm_fields['U'], -1);
330 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
331 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
333 rlen = strlen(CC->room.QRname);
334 pCh = strstr(ChrPtr(FlatSubject), CC->room.QRname);
336 (*(pCh + rlen) != ']') ||
337 (pCh == ChrPtr(FlatSubject)) ||
342 StrBufPlain(Subject, HKEY("["));
343 StrBufAppendBufPlain(Subject,
346 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
347 StrBufAppendBuf(Subject, FlatSubject, 0);
348 /* so we can free the right one swap them */
350 Subject = FlatSubject;
352 StrBufRFC2047encode(&Subject, FlatSubject);
355 if (msg->cm_fields['U'] != NULL)
356 free (msg->cm_fields['U']);
357 msg->cm_fields['U'] = SmashStrBuf(&Subject);
359 FreeStrBuf(&FlatSubject);
361 /* else we won't modify the buffer, since the
362 * roomname is already here.
365 /* if there is no other recipient, Set the recipient
366 * of the list message to the email address of the
369 if ((msg->cm_fields['R'] == NULL) ||
370 IsEmptyStr(msg->cm_fields['R']))
372 if (msg->cm_fields['R'] != NULL)
373 free(msg->cm_fields['R']);
375 msg->cm_fields['R'] = malloc(256);
376 snprintf(msg->cm_fields['R'], 256,
377 "room_%s@%s", CC->room.QRname,
379 for (i=0; msg->cm_fields['R'][i]; ++i) {
380 if (isspace(msg->cm_fields['R'][i])) {
381 msg->cm_fields['R'][i] = '_';
386 /* Handle delivery */
387 network_deliver_list(msg, sc, CC->room.QRname);
388 CtdlFreeMessage(msg);
393 * Process digest recipients
395 if ((sc->RNCfg->NetConfigs[digestrecp] != NULL) && (sc->digestfp != NULL)) {
396 msg = CtdlFetchMessage(msgnum, 1);
398 fprintf(sc->digestfp,
399 " -----------------------------------"
400 "------------------------------------"
402 fprintf(sc->digestfp, "From: ");
403 if (msg->cm_fields['A'] != NULL) {
404 fprintf(sc->digestfp,
406 msg->cm_fields['A']);
408 if (msg->cm_fields['F'] != NULL) {
409 fprintf(sc->digestfp,
411 msg->cm_fields['F']);
413 else if (msg->cm_fields['N'] != NULL) {
414 fprintf(sc->digestfp,
416 msg->cm_fields['N']);
418 fprintf(sc->digestfp, "\n");
419 if (msg->cm_fields['U'] != NULL) {
420 fprintf(sc->digestfp,
422 msg->cm_fields['U']);
425 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
427 safestrncpy(CC->preferred_formats,
429 sizeof CC->preferred_formats);
431 CtdlOutputPreLoadedMsg(msg,
436 StrBufTrim(CC->redirect_buffer);
437 fwrite(HKEY("\n"), 1, sc->digestfp);
438 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
439 fwrite(HKEY("\n"), 1, sc->digestfp);
441 FreeStrBuf(&CC->redirect_buffer);
443 sc->num_msgs_spooled += 1;
444 CtdlFreeMessage(msg);
449 * Process client-side list participations for this room
451 if (sc->RNCfg->NetConfigs[participate] != NULL) {
452 msg = CtdlFetchMessage(msgnum, 1);
455 /* Only send messages which originated on our own
456 * Citadel network, otherwise we'll end up sending the
457 * remote mailing list's messages back to it, which
460 ok_to_participate = 0;
461 if (msg->cm_fields['N'] != NULL) {
462 if (!strcasecmp(msg->cm_fields['N'],
463 config.c_nodename)) {
464 ok_to_participate = 1;
467 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
468 if (CtdlIsValidNode(NULL,
471 sc->working_ignetcfg,
472 sc->the_netmap) == 0)
474 ok_to_participate = 1;
477 if (ok_to_participate) {
478 if (msg->cm_fields['F'] != NULL) {
479 free(msg->cm_fields['F']);
481 msg->cm_fields['F'] = malloc(SIZ);
482 /* Replace the Internet email address of the
483 * actual author with the email address of the
484 * room itself, so the remote listserv doesn't
486 * FIXME I want to be able to pick any address
488 snprintf(msg->cm_fields['F'], SIZ,
489 "room_%s@%s", CC->room.QRname,
491 for (i=0; msg->cm_fields['F'][i]; ++i) {
492 if (isspace(msg->cm_fields['F'][i])) {
493 msg->cm_fields['F'][i] = '_';
498 * Figure out how big a buffer we need to alloc
500 for (nptr = sc->RNCfg->NetConfigs[participate];
504 if (msg->cm_fields['R'] != NULL) {
505 free(msg->cm_fields['R']);
507 msg->cm_fields['R'] =
508 strdup(ChrPtr(nptr->Value[0]));
510 valid = validate_recipients(msg->cm_fields['R'],
513 CtdlSubmitMsg(msg, valid, "", 0);
514 free_recipients(valid);
517 CtdlFreeMessage(msg);
521 if (sc->RNCfg->NetConfigs[ignet_push_share] != NULL)
524 * Process IGnet push shares
526 msg = CtdlFetchMessage(msgnum, 1);
530 /* Prepend our node name to the Path field whenever
531 * sending a message to another IGnet node
533 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 if (sc->RNCfg->NetConfigs[ignet_push_share] != NULL)
556 for (mptr = sc->RNCfg->NetConfigs[ignet_push_share]; mptr != NULL;
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);
660 /* update lastsent */
661 ///sc->lastsent = msgnum; ////// TODO
663 /* Delete this message if delete-after-send is set */
664 if (delete_after_send) {
665 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");