4 * This module will eventually replace netproc and some of its utilities. In
5 * the meantime, it serves as a mailing list manager.
7 * Copyright (C) 2000-2001 by Art Cancro and others.
8 * This code is released under the terms of the GNU General Public License.
20 #include <sys/types.h>
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
27 # include <sys/time.h>
38 #include "sysdep_decls.h"
39 #include "citserver.h"
42 #include "dynloader.h"
49 #include "internet_addressing.h"
50 #include "serv_network.h"
54 * When we do network processing, it's accomplished in two passes; one to
55 * gather a list of rooms and one to actually do them. It's ok that rplist
56 * is global; this process *only* runs as part of the housekeeping loop and
57 * therefore only one will run at a time.
60 struct RoomProcList *next;
61 char name[ROOMNAMELEN];
64 struct RoomProcList *rplist = NULL;
68 * We build a map of the Citadel network during network runs.
77 struct NetMap *the_netmap = NULL;
82 * Read the network map from its configuration file into memory.
84 void read_network_map(void) {
85 char *serialized_map = NULL;
90 serialized_map = CtdlGetSysConfig(IGNETMAP);
91 if (serialized_map == NULL) return; /* if null, no entries */
93 /* Use the string tokenizer to grab one line at a time */
94 for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
95 extract_token(buf, serialized_map, i, '\n');
96 nmptr = (struct NetMap *) mallok(sizeof(struct NetMap));
97 extract(nmptr->nodename, buf, 0);
98 nmptr->lastcontact = extract_long(buf, 1);
99 extract(nmptr->nexthop, buf, 2);
100 nmptr->next = the_netmap;
104 phree(serialized_map);
109 * Write the network map from memory back to the configuration file.
111 void write_network_map(void) {
112 char *serialized_map = NULL;
113 struct NetMap *nmptr;
115 serialized_map = strdoop("");
117 if (the_netmap != NULL) {
118 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
119 serialized_map = reallok(serialized_map,
120 (strlen(serialized_map)+SIZ) );
121 if (strlen(nmptr->nodename) > 0) {
122 sprintf(&serialized_map[strlen(serialized_map)],
131 CtdlPutSysConfig(IGNETMAP, serialized_map);
132 phree(serialized_map);
134 /* Now free the list */
135 while (the_netmap != NULL) {
136 nmptr = the_netmap->next;
145 * Check the network map and determine whether the supplied node name is
146 * valid. If it is not a neighbor node, supply the name of a neighbor node
147 * which is the next hop. If it *is* a neighbor node, we also fill in the
150 int is_valid_node(char *nexthop, char *secret, char *node) {
151 char *ignetcfg = NULL;
156 struct NetMap *nmptr;
163 * First try the neighbor nodes
165 ignetcfg = CtdlGetSysConfig(IGNETCFG);
166 if (ignetcfg == NULL) {
167 if (nexthop != NULL) {
174 if (nexthop != NULL) {
178 /* Use the string tokenizer to grab one line at a time */
179 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
180 extract_token(linebuf, ignetcfg, i, '\n');
181 extract(buf, linebuf, 0);
182 if (!strcasecmp(buf, node)) {
183 if (nexthop != NULL) {
186 if (secret != NULL) {
187 extract(secret, linebuf, 1);
195 return(retval); /* yup, it's a direct neighbor */
199 * If we get to this point we have to see if we know the next hop
201 if (the_netmap != NULL) {
202 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
203 if (!strcasecmp(nmptr->nodename, node)) {
204 if (nexthop != NULL) {
205 strcpy(nexthop, nmptr->nexthop);
213 * If we get to this point, the supplied node name is bogus.
215 lprintf(5, "Invalid node name <%s>\n", node);
223 void cmd_gnet(char *argbuf) {
228 if (CtdlAccessCheck(ac_room_aide)) return;
229 assoc_file_name(filename, &CC->quickroom, "netconfigs");
230 cprintf("%d Network settings for room #%ld <%s>\n",
232 CC->quickroom.QRnumber, CC->quickroom.QRname);
234 fp = fopen(filename, "r");
236 while (fgets(buf, sizeof buf, fp) != NULL) {
237 buf[strlen(buf)-1] = 0;
238 cprintf("%s\n", buf);
247 void cmd_snet(char *argbuf) {
248 char tempfilename[SIZ];
253 if (CtdlAccessCheck(ac_room_aide)) return;
254 safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
255 assoc_file_name(filename, &CC->quickroom, "netconfigs");
257 fp = fopen(tempfilename, "w");
259 cprintf("%d Cannot open %s: %s\n",
260 ERROR+INTERNAL_ERROR,
265 cprintf("%d %s\n", SEND_LISTING, tempfilename);
266 while (client_gets(buf), strcmp(buf, "000")) {
267 fprintf(fp, "%s\n", buf);
271 /* Now copy the temp file to its permanent location
272 * (We use /bin/mv instead of link() because they may be on
273 * different filesystems)
276 snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
283 * Spools out one message from the list.
285 void network_spool_msg(long msgnum, void *userdata) {
286 struct SpoolControl *sc;
287 struct namelist *nptr;
291 char *newpath = NULL;
292 size_t instr_len = SIZ;
293 struct CtdlMessage *msg = NULL;
294 struct CtdlMessage *imsg;
295 struct ser_ret sermsg;
301 int delete_after_send = 0; /* Set to 1 to delete after spooling */
303 sc = (struct SpoolControl *)userdata;
306 * Process mailing list recipients
308 if (sc->listrecps != NULL) {
310 /* First, copy it to the spoolout room */
311 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
312 if (err != 0) return;
315 * Figure out how big a buffer we need to allocate
317 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
318 instr_len = instr_len + strlen(nptr->name);
324 lprintf(9, "Generating delivery instructions\n");
325 instr = mallok(instr_len);
327 lprintf(1, "Cannot allocate %d bytes for instr...\n",
332 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
333 "bounceto|postmaster@%s\n" ,
334 SPOOLMIME, msgnum, time(NULL), config.c_fqdn );
336 /* Generate delivery instructions for each recipient */
337 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
338 sprintf(&instr[strlen(instr)], "remote|%s|0||\n",
343 * Generate a message from the instructions
345 imsg = mallok(sizeof(struct CtdlMessage));
346 memset(imsg, 0, sizeof(struct CtdlMessage));
347 imsg->cm_magic = CTDLMESSAGE_MAGIC;
348 imsg->cm_anon_type = MES_NORMAL;
349 imsg->cm_format_type = FMT_RFC822;
350 imsg->cm_fields['A'] = strdoop("Citadel");
351 imsg->cm_fields['M'] = instr;
353 /* Save delivery instructions in spoolout room */
354 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
355 CtdlFreeMessage(imsg);
359 * Process IGnet push shares
361 if (sc->ignet_push_shares != NULL) {
363 msg = CtdlFetchMessage(msgnum);
366 /* Prepend our node name to the Path field whenever
367 * sending a message to another IGnet node
369 if (msg->cm_fields['P'] == NULL) {
370 msg->cm_fields['P'] = strdoop("username");
372 newpath = mallok(strlen(msg->cm_fields['P']) +
373 strlen(config.c_nodename) + 2);
374 sprintf(newpath, "%s!%s", config.c_nodename,
375 msg->cm_fields['P']);
376 phree(msg->cm_fields['P']);
377 msg->cm_fields['P'] = newpath;
380 * Force the message to appear in the correct room
381 * on the far end by setting the C field correctly
383 if (msg->cm_fields['C'] != NULL) {
384 phree(msg->cm_fields['C']);
386 msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
389 * Determine if this message is set to be deleted
390 * after sending out on the network
392 if (msg->cm_fields['S'] != NULL) {
393 if (!strcasecmp(msg->cm_fields['S'],
395 delete_after_send = 1;
400 * Now serialize it for transmission
402 serialize_message(&sermsg, msg);
404 /* Now send it to every node */
405 for (nptr = sc->ignet_push_shares; nptr != NULL;
410 /* Check for valid node name */
411 if (is_valid_node(NULL,NULL,nptr->name) != 0) {
412 lprintf(3, "Invalid node <%s>\n",
417 /* Check for split horizon */
418 lprintf(9, "Path is %s\n", msg->cm_fields['P']);
419 bang = num_tokens(msg->cm_fields['P'], '!');
420 if (bang > 1) for (i=0; i<(bang-1); ++i) {
421 extract_token(buf, msg->cm_fields['P'],
423 if (!strcasecmp(buf, nptr->name)) {
428 /* Send the message */
431 "./network/spoolout/%s",
433 fp = fopen(filename, "ab");
442 CtdlFreeMessage(msg);
446 /* update lastsent */
447 sc->lastsent = msgnum;
449 /* Delete this message if delete-after-send is set */
450 if (delete_after_send) {
451 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
460 * Batch up and send all outbound traffic from the current room
462 void network_spoolout_room(char *room_to_spool) {
467 struct SpoolControl sc;
468 /* struct namelist *digestrecps = NULL; */
469 struct namelist *nptr;
471 lprintf(7, "Spooling <%s>\n", room_to_spool);
472 if (getroom(&CC->quickroom, room_to_spool) != 0) {
473 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
477 memset(&sc, 0, sizeof(struct SpoolControl));
478 assoc_file_name(filename, &CC->quickroom, "netconfigs");
480 fp = fopen(filename, "r");
482 lprintf(7, "Outbound batch processing skipped for <%s>\n",
483 CC->quickroom.QRname);
487 lprintf(5, "Outbound batch processing started for <%s>\n",
488 CC->quickroom.QRname);
490 while (fgets(buf, sizeof buf, fp) != NULL) {
491 buf[strlen(buf)-1] = 0;
493 extract(instr, buf, 0);
494 if (!strcasecmp(instr, "lastsent")) {
495 sc.lastsent = extract_long(buf, 1);
497 else if (!strcasecmp(instr, "listrecp")) {
498 nptr = (struct namelist *)
499 mallok(sizeof(struct namelist));
500 nptr->next = sc.listrecps;
501 extract(nptr->name, buf, 1);
504 else if (!strcasecmp(instr, "ignet_push_share")) {
505 nptr = (struct namelist *)
506 mallok(sizeof(struct namelist));
507 nptr->next = sc.ignet_push_shares;
508 extract(nptr->name, buf, 1);
509 sc.ignet_push_shares = nptr;
517 /* Do something useful */
518 CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
519 network_spool_msg, &sc);
522 /* Now rewrite the config file */
523 fp = fopen(filename, "w");
525 lprintf(1, "ERROR: cannot open %s: %s\n",
526 filename, strerror(errno));
529 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
531 /* Write out the listrecps while freeing from memory at the
532 * same time. Am I clever or what? :)
534 while (sc.listrecps != NULL) {
535 fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
536 nptr = sc.listrecps->next;
540 while (sc.ignet_push_shares != NULL) {
541 fprintf(fp, "ignet_push_share|%s\n",
542 sc.ignet_push_shares->name);
543 nptr = sc.ignet_push_shares->next;
544 phree(sc.ignet_push_shares);
545 sc.ignet_push_shares = nptr;
551 lprintf(5, "Outbound batch processing finished for <%s>\n",
552 CC->quickroom.QRname);
557 * Batch up and send all outbound traffic from the current room
559 void network_queue_room(struct quickroom *qrbuf, void *data) {
560 struct RoomProcList *ptr;
562 ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
563 if (ptr == NULL) return;
565 safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
572 * Learn topology from path fields
574 void network_learn_topology(char *node, char *path) {
576 struct NetMap *nmptr;
580 if (num_tokens(path, '!') < 3) return;
581 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
582 if (!strcasecmp(nmptr->nodename, node)) {
583 extract_token(nmptr->nexthop, path, 0, '!');
584 nmptr->lastcontact = time(NULL);
589 /* If we got here then it's not in the map, so add it. */
590 nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
591 strcpy(nmptr->nodename, node);
592 nmptr->lastcontact = time(NULL);
593 extract_token(nmptr->nexthop, path, 0, '!');
594 nmptr->next = the_netmap;
601 * Process a buffer containing a single message from a single file
602 * from the inbound queue
604 void network_process_buffer(char *buffer, long size) {
605 struct CtdlMessage *msg;
610 struct usersupp tempUS;
612 char target_room[ROOMNAMELEN];
613 struct ser_ret sermsg;
614 char *oldpath = NULL;
618 /* Set default target room to trash */
619 strcpy(target_room, TWITROOM);
621 /* Load the message into memory */
622 msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
623 memset(msg, 0, sizeof(struct CtdlMessage));
624 msg->cm_magic = CTDLMESSAGE_MAGIC;
625 msg->cm_anon_type = buffer[1];
626 msg->cm_format_type = buffer[2];
628 for (pos = 3; pos < size; ++pos) {
630 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
631 pos = pos + strlen(&buffer[(int)pos]);
634 /* Check for message routing */
635 if (msg->cm_fields['D'] != NULL) {
636 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
638 /* route the message */
639 if (is_valid_node(NULL, NULL,
640 msg->cm_fields['D']) == 0) {
642 /* prepend our node to the path */
643 if (msg->cm_fields['P'] != NULL) {
644 oldpath = msg->cm_fields['P'];
645 msg->cm_fields['P'] = NULL;
648 oldpath = strdoop("unknown_user");
650 msg->cm_fields['P'] =
651 mallok(strlen(oldpath) + SIZ);
652 sprintf(msg->cm_fields['P'], "%s!%s",
653 config.c_nodename, oldpath);
656 /* serialize the message */
657 serialize_message(&sermsg, msg);
661 "./network/spoolout/%s",
662 msg->cm_fields['D']);
663 fp = fopen(filename, "ab");
670 CtdlFreeMessage(msg);
674 else { /* invalid destination node name */
676 /* FIXME bounce the msg */
682 /* FIXME check to see if we already have this message */
684 /* Learn network topology from the path */
685 if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
686 network_learn_topology(msg->cm_fields['N'],
687 msg->cm_fields['P']);
690 /* Does it have a recipient? If so, validate it... */
691 if (msg->cm_fields['R'] != NULL) {
693 safestrncpy(recp, msg->cm_fields['R'], sizeof(recp));
695 e = alias(recp); /* alias and mail type */
696 if ((recp[0] == 0) || (e == MES_ERROR)) {
698 /* FIXME bounce the msg */
701 else if (e == MES_LOCAL) {
702 a = getuser(&tempUS, recp);
704 /* FIXME bounce the msg */
707 MailboxName(target_room, &tempUS, MAILROOM);
712 else if (msg->cm_fields['C'] != NULL) {
713 safestrncpy(target_room,
718 else if (msg->cm_fields['O'] != NULL) {
719 safestrncpy(target_room,
724 /* save the message into a room */
725 msg->cm_flags = CM_SKIP_HOOKS;
726 CtdlSaveMsg(msg, "", target_room, 0);
727 CtdlFreeMessage(msg);
732 * Process a single message from a single file from the inbound queue
734 void network_process_message(FILE *fp, long msgstart, long msgend) {
739 hold_pos = ftell(fp);
740 size = msgend - msgstart + 1;
741 buffer = mallok(size);
742 if (buffer != NULL) {
743 fseek(fp, msgstart, SEEK_SET);
744 fread(buffer, size, 1, fp);
745 network_process_buffer(buffer, size);
749 fseek(fp, hold_pos, SEEK_SET);
754 * Process a single file from the inbound queue
756 void network_process_file(char *filename) {
758 long msgstart = (-1L);
763 lprintf(7, "network: processing <%s>\n", filename);
765 fp = fopen(filename, "rb");
767 lprintf(5, "Error opening %s: %s\n",
768 filename, strerror(errno));
772 /* Look for messages in the data stream and break them out */
773 while (ch = getc(fp), ch >= 0) {
776 if (msgstart >= 0L) {
778 network_process_message(fp, msgstart, msgend);
787 if (msgstart >= 0L) {
788 network_process_message(fp, msgstart, msgend);
797 * Process anything in the inbound queue
799 void network_do_spoolin(void) {
804 dp = opendir("./network/spoolin");
805 if (dp == NULL) return;
807 while (d = readdir(dp), d != NULL) {
808 sprintf(filename, "./network/spoolin/%s", d->d_name);
809 network_process_file(filename);
820 * Run through the rooms doing various types of network stuff.
822 void network_do_queue(void) {
823 static int doing_queue = 0;
824 static time_t last_run = 0L;
825 struct RoomProcList *ptr;
828 * Run no more frequently than once every n seconds
830 if ( (time(NULL) - last_run) < NETWORK_QUEUE_FREQUENCY ) return;
833 * This is a simple concurrency check to make sure only one queue run
834 * is done at a time. We could do this with a mutex, but since we
835 * don't really require extremely fine granularity here, we'll do it
836 * with a static variable instead.
838 if (doing_queue) return;
840 last_run = time(NULL);
845 * Go ahead and run the queue
847 lprintf(7, "network: loading outbound queue\n");
848 ForEachRoom(network_queue_room, NULL);
850 lprintf(7, "network: running outbound queue\n");
851 while (rplist != NULL) {
852 network_spoolout_room(rplist->name);
854 rplist = rplist->next;
858 lprintf(7, "network: processing inbound queue\n");
859 network_do_spoolin();
863 lprintf(7, "network: queue run completed\n");
870 * cmd_netp() - authenticate to the server as another Citadel node polling
871 * for network traffic
873 void cmd_netp(char *cmdbuf)
881 extract(node, cmdbuf, 0);
882 extract(pass, cmdbuf, 1);
884 if (is_valid_node(nexthop, secret, node) != 0) {
885 cprintf("%d authentication failed\n", ERROR);
889 if (strcasecmp(pass, secret)) {
890 cprintf("%d authentication failed\n", ERROR);
894 safestrncpy(CC->net_node, node, sizeof CC->net_node);
895 cprintf("%d authenticated as network node '%s'\n", OK,
904 char *Dynamic_Module_Init(void)
906 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
907 CtdlRegisterProtoHook(cmd_snet, "SNET", "Get network config");
908 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
909 CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);