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 as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * ** NOTE ** A word on the S_NETCONFIGS semaphore:
22 * This is a fairly high-level type of critical section. It ensures that no
23 * two threads work on the netconfigs files at the same time. Since we do
24 * so many things inside these, here are the rules:
25 * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
26 * 2. Do *not* perform any I/O with the client during these sections.
31 * Duration of time (in seconds) after which pending list subscribe/unsubscribe
32 * requests that have not been confirmed will be deleted.
34 #define EXP 259200 /* three days */
46 #include <sys/types.h>
48 #if TIME_WITH_SYS_TIME
49 # include <sys/time.h>
53 # include <sys/time.h>
61 # if HAVE_SYS_SYSCALL_H
62 # include <sys/syscall.h>
69 #include <libcitadel.h>
72 #include "citserver.h"
78 #include "internet_addressing.h"
79 #include "serv_network.h"
80 #include "clientsocket.h"
82 #include "citadel_dirs.h"
90 #include "netconfig.h"
93 #include "ctdl_module.h"
97 * Deliver digest messages
99 void network_deliver_digest(SpoolControl *sc) {
100 struct CitContext *CCC = CC;
103 struct CtdlMessage *msg = NULL;
106 size_t recps_len = SIZ;
107 struct recptypes *valid;
111 if (sc->num_msgs_spooled < 1) {
112 fclose(sc->digestfp);
117 msg = malloc(sizeof(struct CtdlMessage));
118 memset(msg, 0, sizeof(struct CtdlMessage));
119 msg->cm_magic = CTDLMESSAGE_MAGIC;
120 msg->cm_format_type = FMT_RFC822;
121 msg->cm_anon_type = MES_NORMAL;
123 sprintf(buf, "%ld", time(NULL));
124 msg->cm_fields['T'] = strdup(buf);
125 msg->cm_fields['A'] = strdup(CC->room.QRname);
126 snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
127 msg->cm_fields['U'] = strdup(buf);
128 sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
129 for (i=0; buf[i]; ++i) {
130 if (isspace(buf[i])) buf[i]='_';
131 buf[i] = tolower(buf[i]);
133 msg->cm_fields['F'] = strdup(buf);
134 msg->cm_fields['R'] = strdup(buf);
136 /* Set the 'List-ID' header */
137 msg->cm_fields['L'] = malloc(1024);
138 snprintf(msg->cm_fields['L'], 1024,
139 "%s <%ld.list-id.%s>",
146 * Go fetch the contents of the digest
148 fseek(sc->digestfp, 0L, SEEK_END);
149 msglen = ftell(sc->digestfp);
151 msg->cm_fields['M'] = malloc(msglen + 1);
152 fseek(sc->digestfp, 0L, SEEK_SET);
153 fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
154 msg->cm_fields['M'][msglen] = '\0';
156 fclose(sc->digestfp);
159 /* Now generate the delivery instructions */
162 * Figure out how big a buffer we need to allocate
164 for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
165 recps_len = recps_len + strlen(nptr->name) + 2;
168 recps = malloc(recps_len);
172 "Cannot allocate %ld bytes for recps...\n",
180 for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
181 if (nptr != sc->digestrecps) {
184 strcat(recps, nptr->name);
187 /* Where do we want bounces and other noise to be heard?
188 *Surely not the list members! */
189 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
191 /* Now submit the message */
192 valid = validate_recipients(recps, 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);
205 * Deliver list messages to everyone on the list ... efficiently
207 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
209 struct CitContext *CCC = CC;
211 size_t recps_len = SIZ;
212 struct recptypes *valid;
216 /* Don't do this if there were no recipients! */
217 if (sc->listrecps == NULL) return;
219 /* Now generate the delivery instructions */
222 * Figure out how big a buffer we need to allocate
224 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
225 recps_len = recps_len + strlen(nptr->name) + 2;
228 recps = malloc(recps_len);
232 "Cannot allocate %ld bytes for recps...\n",
240 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
241 if (nptr != sc->listrecps) {
244 strcat(recps, nptr->name);
247 /* Where do we want bounces and other noise to be heard?
248 * Surely not the list members! */
249 snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
251 /* Now submit the message */
252 valid = validate_recipients(recps, NULL, 0);
255 valid->bounce_to = strdup(bounce_to);
256 valid->envelope_from = strdup(bounce_to);
257 valid->sending_room = strdup(RoomName);
258 CtdlSubmitMsg(msg, valid, NULL, 0);
259 free_recipients(valid);
261 /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
266 * Spools out one message from the list.
268 void network_spool_msg(long msgnum,
271 struct CitContext *CCC = CC;
275 char *newpath = NULL;
276 struct CtdlMessage *msg = NULL;
279 struct ser_ret sermsg;
281 char filename[PATH_MAX];
285 int delete_after_send = 0; /* Set to 1 to delete after spooling */
286 int ok_to_participate = 0;
287 struct recptypes *valid;
289 sc = (SpoolControl *)userdata;
292 * Process mailing list recipients
294 if (sc->listrecps != NULL) {
295 /* Fetch the message. We're going to need to modify it
296 * in order to insert the [list name] in it, etc.
298 msg = CtdlFetchMessage(msgnum, 1);
302 StrBuf *Subject, *FlatSubject;
304 if (msg->cm_fields['K'] != NULL)
305 free(msg->cm_fields['K']);
306 if (msg->cm_fields['V'] == NULL){
307 /* local message, no enVelope */
310 StrBufAppendBufPlain(Buf,
313 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
314 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
316 msg->cm_fields['K'] = SmashStrBuf(&Buf);
319 msg->cm_fields['K'] =
320 strdup (msg->cm_fields['V']);
322 /* Set the 'List-ID' header */
323 if (msg->cm_fields['L'] != NULL) {
324 free(msg->cm_fields['L']);
326 msg->cm_fields['L'] = malloc(1024);
327 snprintf(msg->cm_fields['L'], 1024,
328 "%s <%ld.list-id.%s>",
334 /* Prepend "[List name]" to the subject */
335 if (msg->cm_fields['U'] == NULL) {
336 Subject = NewStrBufPlain(HKEY("(no subject)"));
339 Subject = NewStrBufPlain(
340 msg->cm_fields['U'], -1);
342 FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
343 StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
345 rlen = strlen(CC->room.QRname);
346 pCh = strstr(ChrPtr(FlatSubject), CC->room.QRname);
348 (*(pCh + rlen) != ']') ||
349 (pCh == ChrPtr(FlatSubject)) ||
354 StrBufPlain(Subject, HKEY("["));
355 StrBufAppendBufPlain(Subject,
358 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
359 StrBufAppendBuf(Subject, FlatSubject, 0);
360 /* so we can free the right one swap them */
362 Subject = FlatSubject;
364 StrBufRFC2047encode(&Subject, FlatSubject);
367 if (msg->cm_fields['U'] != NULL)
368 free (msg->cm_fields['U']);
369 msg->cm_fields['U'] = SmashStrBuf(&Subject);
371 FreeStrBuf(&FlatSubject);
373 /* else we won't modify the buffer, since the
374 * roomname is already here.
377 /* if there is no other recipient, Set the recipient
378 * of the list message to the email address of the
381 if ((msg->cm_fields['R'] == NULL) ||
382 IsEmptyStr(msg->cm_fields['R']))
384 if (msg->cm_fields['R'] != NULL)
385 free(msg->cm_fields['R']);
387 msg->cm_fields['R'] = malloc(256);
388 snprintf(msg->cm_fields['R'], 256,
389 "room_%s@%s", CC->room.QRname,
391 for (i=0; msg->cm_fields['R'][i]; ++i) {
392 if (isspace(msg->cm_fields['R'][i])) {
393 msg->cm_fields['R'][i] = '_';
398 /* Handle delivery */
399 network_deliver_list(msg, sc, CC->room.QRname);
400 CtdlFreeMessage(msg);
405 * Process digest recipients
407 if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
408 msg = CtdlFetchMessage(msgnum, 1);
410 fprintf(sc->digestfp,
411 " -----------------------------------"
412 "------------------------------------"
414 fprintf(sc->digestfp, "From: ");
415 if (msg->cm_fields['A'] != NULL) {
416 fprintf(sc->digestfp,
418 msg->cm_fields['A']);
420 if (msg->cm_fields['F'] != NULL) {
421 fprintf(sc->digestfp,
423 msg->cm_fields['F']);
425 else if (msg->cm_fields['N'] != NULL) {
426 fprintf(sc->digestfp,
428 msg->cm_fields['N']);
430 fprintf(sc->digestfp, "\n");
431 if (msg->cm_fields['U'] != NULL) {
432 fprintf(sc->digestfp,
434 msg->cm_fields['U']);
437 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
439 safestrncpy(CC->preferred_formats,
441 sizeof CC->preferred_formats);
443 CtdlOutputPreLoadedMsg(msg,
448 StrBufTrim(CC->redirect_buffer);
449 fwrite(HKEY("\n"), 1, sc->digestfp);
450 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
451 fwrite(HKEY("\n"), 1, sc->digestfp);
453 FreeStrBuf(&CC->redirect_buffer);
455 sc->num_msgs_spooled += 1;
456 CtdlFreeMessage(msg);
461 * Process client-side list participations for this room
463 if (sc->participates != NULL) {
464 msg = CtdlFetchMessage(msgnum, 1);
467 /* Only send messages which originated on our own
468 * Citadel network, otherwise we'll end up sending the
469 * remote mailing list's messages back to it, which
472 ok_to_participate = 0;
473 if (msg->cm_fields['N'] != NULL) {
474 if (!strcasecmp(msg->cm_fields['N'],
475 config.c_nodename)) {
476 ok_to_participate = 1;
479 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
480 if (is_valid_node(NULL,
483 sc->working_ignetcfg,
484 sc->the_netmap) == 0)
486 ok_to_participate = 1;
489 if (ok_to_participate) {
490 if (msg->cm_fields['F'] != NULL) {
491 free(msg->cm_fields['F']);
493 msg->cm_fields['F'] = malloc(SIZ);
494 /* Replace the Internet email address of the
495 * actual author with the email address of the
496 * room itself, so the remote listserv doesn't
498 * FIXME I want to be able to pick any address
500 snprintf(msg->cm_fields['F'], SIZ,
501 "room_%s@%s", CC->room.QRname,
503 for (i=0; msg->cm_fields['F'][i]; ++i) {
504 if (isspace(msg->cm_fields['F'][i])) {
505 msg->cm_fields['F'][i] = '_';
510 * Figure out how big a buffer we need to alloc
512 for (nptr = sc->participates;
516 if (msg->cm_fields['R'] != NULL) {
517 free(msg->cm_fields['R']);
519 msg->cm_fields['R'] =
522 valid = validate_recipients(nptr->name,
525 CtdlSubmitMsg(msg, valid, "", 0);
526 free_recipients(valid);
529 CtdlFreeMessage(msg);
534 * Process IGnet push shares
536 msg = CtdlFetchMessage(msgnum, 1);
540 /* Prepend our node name to the Path field whenever
541 * sending a message to another IGnet node
543 if (msg->cm_fields['P'] == NULL) {
544 msg->cm_fields['P'] = strdup("username");
546 newpath_len = strlen(msg->cm_fields['P']) +
547 strlen(config.c_nodename) + 2;
548 newpath = malloc(newpath_len);
549 snprintf(newpath, newpath_len, "%s!%s",
550 config.c_nodename, msg->cm_fields['P']);
551 free(msg->cm_fields['P']);
552 msg->cm_fields['P'] = newpath;
555 * Determine if this message is set to be deleted
556 * after sending out on the network
558 if (msg->cm_fields['S'] != NULL) {
559 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
560 delete_after_send = 1;
564 /* Now send it to every node */
565 if (sc->ignet_push_shares != NULL)
566 for (mptr = sc->ignet_push_shares; mptr != NULL;
571 Buf = NewStrBufPlain(mptr->remote_nodename, -1);
573 StrBufPlain(Buf, mptr->remote_nodename, -1);
574 /* Check for valid node name */
575 if (is_valid_node(NULL,
578 sc->working_ignetcfg,
579 sc->the_netmap) != 0)
582 "Invalid node <%s>\n",
583 mptr->remote_nodename);
588 /* Check for split horizon */
589 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
590 bang = num_tokens(msg->cm_fields['P'], '!');
592 for (i=0; i<(bang-1); ++i) {
598 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
599 buf, mptr->remote_nodename) ;
600 if (!strcasecmp(buf, mptr->remote_nodename)) {
609 mptr->remote_nodename);
612 /* Send the message */
616 * Force the message to appear in the correct
617 * room on the far end by setting the C field
620 if (msg->cm_fields['C'] != NULL) {
621 free(msg->cm_fields['C']);
623 if (!IsEmptyStr(mptr->remote_roomname)) {
624 msg->cm_fields['C'] =
625 strdup(mptr->remote_roomname);
628 msg->cm_fields['C'] =
629 strdup(CC->room.QRname);
632 /* serialize it for transmission */
633 serialize_message(&sermsg, msg);
634 if (sermsg.len > 0) {
636 /* write it to a spool file */
641 mptr->remote_nodename,
650 fp = fopen(filename, "ab");
663 /* free the serialized version */
669 CtdlFreeMessage(msg);
672 /* update lastsent */
673 sc->lastsent = msgnum;
675 /* Delete this message if delete-after-send is set */
676 if (delete_after_send) {
677 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");