X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fserv_network.c;h=a769aab3e3c20a4cba7c88abb75881f82f322836;hb=8eac29db12e027867644fcac287a6b4c34e067c7;hp=ae15d83246afbd9678e7dc46c08797ecc1ee0b68;hpb=d92a32fa28d95b62170007d7fb32e2db7f3bff23;p=citadel.git diff --git a/citadel/serv_network.c b/citadel/serv_network.c index ae15d8324..a769aab3e 100644 --- a/citadel/serv_network.c +++ b/citadel/serv_network.c @@ -4,24 +4,34 @@ * This module handles shared rooms, inter-Citadel mail, and outbound * mailing list processing. * - * Copyright (C) 2000-2002 by Art Cancro and others. + * Copyright (C) 2000-2005 by Art Cancro and others. * This code is released under the terms of the GNU General Public License. * + * ** NOTE ** A word on the S_NETCONFIGS semaphore: + * This is a fairly high-level type of critical section. It ensures that no + * two threads work on the netconfigs files at the same time. Since we do + * so many things inside these, here are the rules: + * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others. + * 2. Do *not* perform any I/O with the client during these sections. + * */ /* - * FIXME - * Don't allow polls during network processing + * Duration of time (in seconds) after which pending list subscribe/unsubscribe + * requests that have not been confirmed will be deleted. */ +#define EXP 259200 /* three days */ #include "sysdep.h" #include #include #include #include +#include #include #include #include +#include #include #include #if TIME_WITH_SYS_TIME @@ -44,7 +54,7 @@ #include "citserver.h" #include "support.h" #include "config.h" -#include "dynloader.h" +#include "serv_extensions.h" #include "room_ops.h" #include "user_ops.h" #include "policy.h" @@ -55,11 +65,14 @@ #include "serv_network.h" #include "clientsocket.h" #include "file_ops.h" +#include "citadel_dirs.h" #ifndef HAVE_SNPRINTF #include "snprintf.h" #endif +/* Nonzero while we are doing network processing */ +static int doing_queue = 0; /* * When we do network processing, it's accomplished in two passes; one to @@ -73,6 +86,31 @@ struct RoomProcList *rplist = NULL; * We build a map of network nodes during processing. */ struct NetMap *the_netmap = NULL; +int netmap_changed = 0; +char *working_ignetcfg = NULL; + +/* + * Load or refresh the Citadel network (IGnet) configuration for this node. + */ +void load_working_ignetcfg(void) { + char *cfg; + char *oldcfg; + + cfg = CtdlGetSysConfig(IGNETCFG); + if (cfg == NULL) { + cfg = strdup(""); + } + + oldcfg = working_ignetcfg; + working_ignetcfg = cfg; + if (oldcfg != NULL) { + free(oldcfg); + } +} + + + + /* * Keep track of what messages to reject @@ -89,13 +127,13 @@ struct FilterList *load_filter_list(void) { /* Use the string tokenizer to grab one line at a time */ for (i=0; ifl_user, buf, 0); + extract_token(buf, serialized_list, i, '\n', sizeof buf); + nptr = (struct FilterList *) malloc(sizeof(struct FilterList)); + extract_token(nptr->fl_user, buf, 0, '|', sizeof nptr->fl_user); striplt(nptr->fl_user); - extract(nptr->fl_room, buf, 1); + extract_token(nptr->fl_room, buf, 1, '|', sizeof nptr->fl_room); striplt(nptr->fl_room); - extract(nptr->fl_node, buf, 2); + extract_token(nptr->fl_node, buf, 2, '|', sizeof nptr->fl_node); striplt(nptr->fl_node); /* Cowardly refuse to add an any/any/any entry that would @@ -103,7 +141,7 @@ struct FilterList *load_filter_list(void) { */ if (strlen(nptr->fl_user) + strlen(nptr->fl_room) + strlen(nptr->fl_node) == 0) { - phree(nptr); + free(nptr); } else { nptr->next = newlist; @@ -111,7 +149,7 @@ struct FilterList *load_filter_list(void) { } } - phree(serialized_list); + free(serialized_list); return newlist; } @@ -119,7 +157,7 @@ struct FilterList *load_filter_list(void) { void free_filter_list(struct FilterList *fl) { if (fl == NULL) return; free_filter_list(fl->next); - phree(fl); + free(fl); } @@ -188,16 +226,17 @@ void read_network_map(void) { /* Use the string tokenizer to grab one line at a time */ for (i=0; inodename, buf, 0); + extract_token(buf, serialized_map, i, '\n', sizeof buf); + nmptr = (struct NetMap *) malloc(sizeof(struct NetMap)); + extract_token(nmptr->nodename, buf, 0, '|', sizeof nmptr->nodename); nmptr->lastcontact = extract_long(buf, 1); - extract(nmptr->nexthop, buf, 2); + extract_token(nmptr->nexthop, buf, 2, '|', sizeof nmptr->nexthop); nmptr->next = the_netmap; the_netmap = nmptr; } - phree(serialized_map); + free(serialized_map); + netmap_changed = 0; } @@ -208,32 +247,36 @@ void write_network_map(void) { char *serialized_map = NULL; struct NetMap *nmptr; - serialized_map = strdoop(""); - if (the_netmap != NULL) { - for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) { - serialized_map = reallok(serialized_map, - (strlen(serialized_map)+SIZ) ); - if (strlen(nmptr->nodename) > 0) { - snprintf(&serialized_map[strlen(serialized_map)], - SIZ, - "%s|%ld|%s\n", - nmptr->nodename, - (long)nmptr->lastcontact, - nmptr->nexthop); + if (netmap_changed) { + serialized_map = strdup(""); + + if (the_netmap != NULL) { + for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) { + serialized_map = realloc(serialized_map, + (strlen(serialized_map)+SIZ) ); + if (strlen(nmptr->nodename) > 0) { + snprintf(&serialized_map[strlen(serialized_map)], + SIZ, + "%s|%ld|%s\n", + nmptr->nodename, + (long)nmptr->lastcontact, + nmptr->nexthop); + } } } - } - CtdlPutSysConfig(IGNETMAP, serialized_map); - phree(serialized_map); + CtdlPutSysConfig(IGNETMAP, serialized_map); + free(serialized_map); + } /* Now free the list */ while (the_netmap != NULL) { nmptr = the_netmap->next; - phree(the_netmap); + free(the_netmap); the_netmap = nmptr; } + netmap_changed = 0; } @@ -245,7 +288,6 @@ void write_network_map(void) { * shared secret. */ int is_valid_node(char *nexthop, char *secret, char *node) { - char *ignetcfg = NULL; int i; char linebuf[SIZ]; char buf[SIZ]; @@ -259,8 +301,8 @@ int is_valid_node(char *nexthop, char *secret, char *node) { /* * First try the neighbor nodes */ - ignetcfg = CtdlGetSysConfig(IGNETCFG); - if (ignetcfg == NULL) { + if (working_ignetcfg == NULL) { + lprintf(CTDL_ERR, "working_ignetcfg is NULL!\n"); if (nexthop != NULL) { strcpy(nexthop, ""); } @@ -273,21 +315,20 @@ int is_valid_node(char *nexthop, char *secret, char *node) { } /* Use the string tokenizer to grab one line at a time */ - for (i=0; i\n", node); + lprintf(CTDL_ERR, "Invalid node name <%s>\n", node); return(-1); } @@ -323,10 +364,10 @@ void cmd_gnet(char *argbuf) { FILE *fp; if (CtdlAccessCheck(ac_room_aide)) return; - assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs"); + assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir); cprintf("%d Network settings for room #%ld <%s>\n", LISTING_FOLLOWS, - CC->quickroom.QRnumber, CC->quickroom.QRname); + CC->room.QRnumber, CC->room.QRname); fp = fopen(filename, "r"); if (fp != NULL) { @@ -347,20 +388,22 @@ void cmd_snet(char *argbuf) { char buf[SIZ]; FILE *fp; + unbuffer_output(); + if (CtdlAccessCheck(ac_room_aide)) return; - safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename); - assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs"); + CtdlMakeTempFileName(tempfilename, sizeof tempfilename); + assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir); fp = fopen(tempfilename, "w"); if (fp == NULL) { cprintf("%d Cannot open %s: %s\n", - ERROR+INTERNAL_ERROR, + ERROR + INTERNAL_ERROR, tempfilename, strerror(errno)); } cprintf("%d %s\n", SEND_LISTING, tempfilename); - while (client_gets(buf), strcmp(buf, "000")) { + while (client_getln(buf, sizeof buf), strcmp(buf, "000")) { fprintf(fp, "%s\n", buf); } fclose(fp); @@ -371,24 +414,193 @@ void cmd_snet(char *argbuf) { */ unlink(filename); snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename); + begin_critical_section(S_NETCONFIGS); system(buf); + end_critical_section(S_NETCONFIGS); +} + + +/* + * Deliver digest messages + */ +void network_deliver_digest(struct SpoolControl *sc) { + char buf[SIZ]; + int i; + struct CtdlMessage *msg; + long msglen; + long msgnum; + char *instr = NULL; + size_t instr_len = SIZ; + struct CtdlMessage *imsg; + struct namelist *nptr; + + if (sc->num_msgs_spooled < 1) { + fclose(sc->digestfp); + sc->digestfp = NULL; + return; + } + + msg = malloc(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_format_type = FMT_RFC822; + msg->cm_anon_type = MES_NORMAL; + + sprintf(buf, "%ld", time(NULL)); + msg->cm_fields['T'] = strdup(buf); + msg->cm_fields['A'] = strdup(CC->room.QRname); + snprintf(buf, sizeof buf, "[%s]", CC->room.QRname); + msg->cm_fields['U'] = strdup(buf); + sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn); + for (i=0; icm_fields['F'] = strdup(buf); + msg->cm_fields['R'] = strdup(buf); + + /* + * Go fetch the contents of the digest + */ + fseek(sc->digestfp, 0L, SEEK_END); + msglen = ftell(sc->digestfp); + + msg->cm_fields['M'] = malloc(msglen + 1); + fseek(sc->digestfp, 0L, SEEK_SET); + fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp); + msg->cm_fields['M'][msglen] = 0; + + fclose(sc->digestfp); + sc->digestfp = NULL; + + msgnum = CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM); + CtdlFreeMessage(msg); + + /* Now generate the delivery instructions */ + + /* + * Figure out how big a buffer we need to allocate + */ + for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) { + instr_len = instr_len + strlen(nptr->name) + 2; + } + + /* + * allocate... + */ + lprintf(CTDL_DEBUG, "Generating delivery instructions\n"); + instr = malloc(instr_len); + if (instr == NULL) { + lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for instr...\n", + (long)instr_len); + abort(); + } + snprintf(instr, instr_len, + "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n" + "bounceto|postmaster@%s\n" , + SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn ); + + /* Generate delivery instructions for each recipient */ + for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) { + size_t tmp = strlen(instr); + snprintf(&instr[tmp], instr_len - tmp, + "remote|%s|0||\n", nptr->name); + } + + /* + * Generate a message from the instructions + */ + imsg = malloc(sizeof(struct CtdlMessage)); + memset(imsg, 0, sizeof(struct CtdlMessage)); + imsg->cm_magic = CTDLMESSAGE_MAGIC; + imsg->cm_anon_type = MES_NORMAL; + imsg->cm_format_type = FMT_RFC822; + imsg->cm_fields['A'] = strdup("Citadel"); + imsg->cm_fields['M'] = instr; + + /* Save delivery instructions in spoolout room */ + CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM); + CtdlFreeMessage(imsg); } +/* + * Deliver list messages to everyone on the list ... efficiently + */ +void network_deliver_list(struct CtdlMessage *msg, struct SpoolControl *sc) { + long msgnum; + char *instr = NULL; + size_t instr_len = SIZ; + struct CtdlMessage *imsg; + struct namelist *nptr; + + /* Don't do this if there were no recipients! */ + if (sc->listrecps == NULL) return; + + /* Save the message to disk... */ + msgnum = CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM); + + /* Now generate the delivery instructions */ + + /* + * Figure out how big a buffer we need to allocate + */ + for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) { + instr_len = instr_len + strlen(nptr->name) + 2; + } + + /* + * allocate... + */ + lprintf(CTDL_DEBUG, "Generating delivery instructions\n"); + instr = malloc(instr_len); + if (instr == NULL) { + lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for instr...\n", + (long)instr_len); + abort(); + } + snprintf(instr, instr_len, + "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n" + "bounceto|postmaster@%s\n" , + SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn ); + + /* Generate delivery instructions for each recipient */ + for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) { + size_t tmp = strlen(instr); + snprintf(&instr[tmp], instr_len - tmp, + "remote|%s|0||\n", nptr->name); + } + + /* + * Generate a message from the instructions + */ + imsg = malloc(sizeof(struct CtdlMessage)); + memset(imsg, 0, sizeof(struct CtdlMessage)); + imsg->cm_magic = CTDLMESSAGE_MAGIC; + imsg->cm_anon_type = MES_NORMAL; + imsg->cm_format_type = FMT_RFC822; + imsg->cm_fields['A'] = strdup("Citadel"); + imsg->cm_fields['M'] = instr; + + /* Save delivery instructions in spoolout room */ + CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM); + CtdlFreeMessage(imsg); +} + + + /* * Spools out one message from the list. */ void network_spool_msg(long msgnum, void *userdata) { struct SpoolControl *sc; - struct namelist *nptr; - int err; int i; - char *instr = NULL; char *newpath = NULL; size_t instr_len = SIZ; struct CtdlMessage *msg = NULL; - struct CtdlMessage *imsg; + struct namelist *nptr; + struct maplist *mptr; struct ser_ret sermsg; FILE *fp; char filename[SIZ]; @@ -396,151 +608,256 @@ void network_spool_msg(long msgnum, void *userdata) { int bang = 0; int send = 1; int delete_after_send = 0; /* Set to 1 to delete after spooling */ + int ok_to_participate = 0; + struct recptypes *valid; sc = (struct SpoolControl *)userdata; /* * Process mailing list recipients */ + instr_len = SIZ; if (sc->listrecps != NULL) { - - /* First, copy it to the spoolout room */ - err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0); - if (err != 0) return; - - /* - * Figure out how big a buffer we need to allocate - */ - for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) { - instr_len = instr_len + strlen(nptr->name); - } - - /* - * allocate... - */ - lprintf(9, "Generating delivery instructions\n"); - instr = mallok(instr_len); - if (instr == NULL) { - lprintf(1, "Cannot allocate %ld bytes for instr...\n", - (long)instr_len); - abort(); + /* Fetch the message. We're going to need to modify it + * in order to insert the [list name] in it, etc. + */ + msg = CtdlFetchMessage(msgnum, 1); + if (msg != NULL) { + + /* Prepend "[List name]" to the subject */ + if (msg->cm_fields['U'] == NULL) { + msg->cm_fields['U'] = strdup("(no subject)"); + } + snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, msg->cm_fields['U']); + free(msg->cm_fields['U']); + msg->cm_fields['U'] = strdup(buf); + + /* Set the recipient of the list message to the + * email address of the room itself. + * FIXME ... I want to be able to pick any address + */ + if (msg->cm_fields['R'] != NULL) { + free(msg->cm_fields['R']); + } + msg->cm_fields['R'] = malloc(256); + snprintf(msg->cm_fields['R'], 256, + "room_%s@%s", CC->room.QRname, + config.c_fqdn); + for (i=0; icm_fields['R']); ++i) { + if (isspace(msg->cm_fields['R'][i])) { + msg->cm_fields['R'][i] = '_'; + } + } + + /* Handle delivery */ + network_deliver_list(msg, sc); + CtdlFreeMessage(msg); } - snprintf(instr, instr_len, - "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n" - "bounceto|postmaster@%s\n" , - SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn ); - - /* Generate delivery instructions for each recipient */ - for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) { - size_t tmp = strlen(instr); - snprintf(&instr[tmp], instr_len - tmp, - "remote|%s|0||\n", nptr->name); + } + + /* + * Process digest recipients + */ + if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) { + msg = CtdlFetchMessage(msgnum, 1); + if (msg != NULL) { + fprintf(sc->digestfp, " -----------------------------------" + "------------------------------------" + "-------\n"); + fprintf(sc->digestfp, "From: "); + if (msg->cm_fields['A'] != NULL) { + fprintf(sc->digestfp, "%s ", msg->cm_fields['A']); + } + if (msg->cm_fields['F'] != NULL) { + fprintf(sc->digestfp, "<%s> ", msg->cm_fields['F']); + } + else if (msg->cm_fields['N'] != NULL) { + fprintf(sc->digestfp, "@%s ", msg->cm_fields['N']); + } + fprintf(sc->digestfp, "\n"); + if (msg->cm_fields['U'] != NULL) { + fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields['U']); + } + + CC->redirect_buffer = malloc(SIZ); + CC->redirect_len = 0; + CC->redirect_alloc = SIZ; + + safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats); + CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0); + + striplt(CC->redirect_buffer); + fprintf(sc->digestfp, "\n%s\n", CC->redirect_buffer); + + free(CC->redirect_buffer); + CC->redirect_buffer = NULL; + CC->redirect_len = 0; + CC->redirect_alloc = 0; + + sc->num_msgs_spooled += 1; + free(msg); } - - /* - * Generate a message from the instructions - */ - imsg = mallok(sizeof(struct CtdlMessage)); - memset(imsg, 0, sizeof(struct CtdlMessage)); - imsg->cm_magic = CTDLMESSAGE_MAGIC; - imsg->cm_anon_type = MES_NORMAL; - imsg->cm_format_type = FMT_RFC822; - imsg->cm_fields['A'] = strdoop("Citadel"); - imsg->cm_fields['M'] = instr; - - /* Save delivery instructions in spoolout room */ - CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM); - CtdlFreeMessage(imsg); } - + /* - * Process IGnet push shares + * Process client-side list participations for this room */ - if (sc->ignet_push_shares != NULL) { - - msg = CtdlFetchMessage(msgnum); + instr_len = SIZ; + if (sc->participates != NULL) { + msg = CtdlFetchMessage(msgnum, 1); if (msg != NULL) { - size_t newpath_len; - /* Prepend our node name to the Path field whenever - * sending a message to another IGnet node - */ - if (msg->cm_fields['P'] == NULL) { - msg->cm_fields['P'] = strdoop("username"); - } - newpath_len = strlen(msg->cm_fields['P']) + - strlen(config.c_nodename) + 2; - newpath = mallok(newpath_len); - snprintf(newpath, newpath_len, "%s!%s", - config.c_nodename, msg->cm_fields['P']); - phree(msg->cm_fields['P']); - msg->cm_fields['P'] = newpath; - - /* - * Force the message to appear in the correct room - * on the far end by setting the C field correctly + /* Only send messages which originated on our own Citadel + * network, otherwise we'll end up sending the remote + * mailing list's messages back to it, which is rude... */ - if (msg->cm_fields['C'] != NULL) { - phree(msg->cm_fields['C']); + ok_to_participate = 0; + if (msg->cm_fields['N'] != NULL) { + if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) { + ok_to_participate = 1; + } + if (is_valid_node(NULL, NULL, msg->cm_fields['N']) == 0) { + ok_to_participate = 1; + } } - msg->cm_fields['C'] = strdoop(CC->quickroom.QRname); + if (ok_to_participate) { + if (msg->cm_fields['F'] != NULL) { + free(msg->cm_fields['F']); + } + msg->cm_fields['F'] = malloc(SIZ); + /* Replace the Internet email address of the actual + * author with the email address of the room itself, + * so the remote listserv doesn't reject us. + * FIXME ... I want to be able to pick any address + */ + snprintf(msg->cm_fields['F'], SIZ, + "room_%s@%s", CC->room.QRname, + config.c_fqdn); + for (i=0; icm_fields['F']); ++i) { + if (isspace(msg->cm_fields['F'][i])) { + msg->cm_fields['F'][i] = '_'; + } + } - /* - * Determine if this message is set to be deleted - * after sending out on the network - */ - if (msg->cm_fields['S'] != NULL) { - if (!strcasecmp(msg->cm_fields['S'], - "CANCEL")) { - delete_after_send = 1; + /* + * Figure out how big a buffer we need to allocate + */ + for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) { + + if (msg->cm_fields['R'] == NULL) { + free(msg->cm_fields['R']); + } + msg->cm_fields['R'] = strdup(nptr->name); + + valid = validate_recipients(nptr->name); + CtdlSubmitMsg(msg, valid, ""); + free(valid); } + } + CtdlFreeMessage(msg); + } + } + + /* + * Process IGnet push shares + */ + msg = CtdlFetchMessage(msgnum, 1); + if (msg != NULL) { + size_t newpath_len; - /* - * Now serialize it for transmission - */ - serialize_message(&sermsg, msg); + /* Prepend our node name to the Path field whenever + * sending a message to another IGnet node + */ + if (msg->cm_fields['P'] == NULL) { + msg->cm_fields['P'] = strdup("username"); + } + newpath_len = strlen(msg->cm_fields['P']) + + strlen(config.c_nodename) + 2; + newpath = malloc(newpath_len); + snprintf(newpath, newpath_len, "%s!%s", + config.c_nodename, msg->cm_fields['P']); + free(msg->cm_fields['P']); + msg->cm_fields['P'] = newpath; + + /* + * Determine if this message is set to be deleted + * after sending out on the network + */ + if (msg->cm_fields['S'] != NULL) { + if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) { + delete_after_send = 1; + } + } - /* Now send it to every node */ - for (nptr = sc->ignet_push_shares; nptr != NULL; - nptr = nptr->next) { + /* Now send it to every node */ + if (sc->ignet_push_shares != NULL) + for (mptr = sc->ignet_push_shares; mptr != NULL; + mptr = mptr->next) { - send = 1; + send = 1; + + /* Check for valid node name */ + if (is_valid_node(NULL, NULL, mptr->remote_nodename) != 0) { + lprintf(CTDL_ERR, "Invalid node <%s>\n", + mptr->remote_nodename); + send = 0; + } - /* Check for valid node name */ - if (is_valid_node(NULL,NULL,nptr->name) != 0) { - lprintf(3, "Invalid node <%s>\n", - nptr->name); + /* Check for split horizon */ + lprintf(CTDL_DEBUG, "Path is %s\n", msg->cm_fields['P']); + bang = num_tokens(msg->cm_fields['P'], '!'); + if (bang > 1) for (i=0; i<(bang-1); ++i) { + extract_token(buf, msg->cm_fields['P'], + i, '!', sizeof buf); + if (!strcasecmp(buf, mptr->remote_nodename)) { send = 0; } + } - /* Check for split horizon */ - lprintf(9, "Path is %s\n", msg->cm_fields['P']); - bang = num_tokens(msg->cm_fields['P'], '!'); - if (bang > 1) for (i=0; i<(bang-1); ++i) { - extract_token(buf, msg->cm_fields['P'], - i, '!'); - if (!strcasecmp(buf, nptr->name)) { - send = 0; - } + /* Send the message */ + if (send == 1) { + + /* + * Force the message to appear in the correct room + * on the far end by setting the C field correctly + */ + if (msg->cm_fields['C'] != NULL) { + free(msg->cm_fields['C']); } + if (strlen(mptr->remote_roomname) > 0) { + msg->cm_fields['C'] = strdup(mptr->remote_roomname); + } + else { + msg->cm_fields['C'] = strdup(CC->room.QRname); + } + + /* serialize it for transmission */ + serialize_message(&sermsg, msg); + if (sermsg.len > 0) { - /* Send the message */ - if (send == 1) { - snprintf(filename, sizeof filename, - "./network/spoolout/%s", - nptr->name); + /* write it to the spool file */ + snprintf(filename, sizeof filename,"%s/%s", + ctdl_netout_dir, + mptr->remote_nodename); + lprintf(CTDL_DEBUG, "Appending to %s\n", filename); fp = fopen(filename, "ab"); if (fp != NULL) { fwrite(sermsg.ser, sermsg.len, 1, fp); fclose(fp); } + else { + lprintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno)); + } + + /* free the serialized version */ + free(sermsg.ser); } + } - phree(sermsg.ser); - CtdlFreeMessage(msg); } + CtdlFreeMessage(msg); } /* update lastsent */ @@ -548,14 +865,12 @@ void network_spool_msg(long msgnum, void *userdata) { /* Delete this message if delete-after-send is set */ if (delete_after_send) { - CtdlDeleteMessages(CC->quickroom.QRname, msgnum, ""); + CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "", 0); } } - - /* * Batch up and send all outbound traffic from the current room */ @@ -563,66 +878,146 @@ void network_spoolout_room(char *room_to_spool) { char filename[SIZ]; char buf[SIZ]; char instr[SIZ]; + char nodename[256]; + char roomname[ROOMNAMELEN]; + char nexthop[256]; FILE *fp; struct SpoolControl sc; - /* struct namelist *digestrecps = NULL; */ - struct namelist *nptr; + struct namelist *nptr = NULL; + struct maplist *mptr = NULL; + size_t miscsize = 0; + size_t linesize = 0; + int skipthisline = 0; + int i; - lprintf(7, "Spooling <%s>\n", room_to_spool); - if (getroom(&CC->quickroom, room_to_spool) != 0) { - lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool); + if (getroom(&CC->room, room_to_spool) != 0) { + lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", room_to_spool); return; } memset(&sc, 0, sizeof(struct SpoolControl)); - assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs"); + assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir); + + begin_critical_section(S_NETCONFIGS); fp = fopen(filename, "r"); if (fp == NULL) { - lprintf(7, "Outbound batch processing skipped for <%s>\n", - CC->quickroom.QRname); + end_critical_section(S_NETCONFIGS); return; } - lprintf(5, "Outbound batch processing started for <%s>\n", - CC->quickroom.QRname); + lprintf(CTDL_INFO, "Networking started for <%s>\n", CC->room.QRname); while (fgets(buf, sizeof buf, fp) != NULL) { buf[strlen(buf)-1] = 0; - extract(instr, buf, 0); + extract_token(instr, buf, 0, '|', sizeof instr); if (!strcasecmp(instr, "lastsent")) { sc.lastsent = extract_long(buf, 1); } else if (!strcasecmp(instr, "listrecp")) { nptr = (struct namelist *) - mallok(sizeof(struct namelist)); + malloc(sizeof(struct namelist)); nptr->next = sc.listrecps; - extract(nptr->name, buf, 1); + extract_token(nptr->name, buf, 1, '|', sizeof nptr->name); sc.listrecps = nptr; } - else if (!strcasecmp(instr, "ignet_push_share")) { + else if (!strcasecmp(instr, "participate")) { nptr = (struct namelist *) - mallok(sizeof(struct namelist)); - nptr->next = sc.ignet_push_shares; - extract(nptr->name, buf, 1); - sc.ignet_push_shares = nptr; + malloc(sizeof(struct namelist)); + nptr->next = sc.participates; + extract_token(nptr->name, buf, 1, '|', sizeof nptr->name); + sc.participates = nptr; + } + else if (!strcasecmp(instr, "digestrecp")) { + nptr = (struct namelist *) + malloc(sizeof(struct namelist)); + nptr->next = sc.digestrecps; + extract_token(nptr->name, buf, 1, '|', sizeof nptr->name); + sc.digestrecps = nptr; + } + else if (!strcasecmp(instr, "ignet_push_share")) { + /* by checking each node's validity, we automatically + * purge nodes which do not exist from room network + * configurations at this time. + */ + extract_token(nodename, buf, 1, '|', sizeof nodename); + extract_token(roomname, buf, 2, '|', sizeof roomname); + strcpy(nexthop, "xxx"); + if (is_valid_node(nexthop, NULL, nodename) == 0) { + if (strlen(nexthop) == 0) { + mptr = (struct maplist *) + malloc(sizeof(struct maplist)); + mptr->next = sc.ignet_push_shares; + strcpy(mptr->remote_nodename, nodename); + strcpy(mptr->remote_roomname, roomname); + sc.ignet_push_shares = mptr; + } + } + } + else { + /* Preserve 'other' lines ... *unless* they happen to + * be subscribe/unsubscribe pendings with expired + * timestamps. + */ + skipthisline = 0; + if (!strncasecmp(buf, "subpending|", 11)) { + if (time(NULL) - extract_long(buf, 4) > EXP) { + skipthisline = 1; + } + } + if (!strncasecmp(buf, "unsubpending|", 13)) { + if (time(NULL) - extract_long(buf, 3) > EXP) { + skipthisline = 1; + } + } + + if (skipthisline == 0) { + linesize = strlen(buf); + sc.misc = realloc(sc.misc, + (miscsize + linesize + 2) ); + sprintf(&sc.misc[miscsize], "%s\n", buf); + miscsize = miscsize + linesize + 1; + } } } fclose(fp); + /* If there are digest recipients, we have to build a digest */ + if (sc.digestrecps != NULL) { + sc.digestfp = tmpfile(); + fprintf(sc.digestfp, "Content-type: text/plain\n\n"); + } /* Do something useful */ - CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, + CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, NULL, network_spool_msg, &sc); + /* If we wrote a digest, deliver it and then close it */ + snprintf(buf, sizeof buf, "room_%s@%s", + CC->room.QRname, config.c_fqdn); + for (i=0; iroom.QRname, buf + ); + network_deliver_digest(&sc); /* deliver and close */ + } /* Now rewrite the config file */ fp = fopen(filename, "w"); if (fp == NULL) { - lprintf(1, "ERROR: cannot open %s: %s\n", + lprintf(CTDL_CRIT, "ERROR: cannot open %s: %s\n", filename, strerror(errno)); } else { @@ -634,32 +1029,145 @@ void network_spoolout_room(char *room_to_spool) { while (sc.listrecps != NULL) { fprintf(fp, "listrecp|%s\n", sc.listrecps->name); nptr = sc.listrecps->next; - phree(sc.listrecps); + free(sc.listrecps); sc.listrecps = nptr; } + /* Do the same for digestrecps */ + while (sc.digestrecps != NULL) { + fprintf(fp, "digestrecp|%s\n", sc.digestrecps->name); + nptr = sc.digestrecps->next; + free(sc.digestrecps); + sc.digestrecps = nptr; + } + /* Do the same for participates */ + while (sc.participates != NULL) { + fprintf(fp, "participate|%s\n", sc.participates->name); + nptr = sc.participates->next; + free(sc.participates); + sc.participates = nptr; + } while (sc.ignet_push_shares != NULL) { - fprintf(fp, "ignet_push_share|%s\n", - sc.ignet_push_shares->name); - nptr = sc.ignet_push_shares->next; - phree(sc.ignet_push_shares); - sc.ignet_push_shares = nptr; + /* by checking each node's validity, we automatically + * purge nodes which do not exist from room network + * configurations at this time. + */ + if (is_valid_node(NULL, NULL, sc.ignet_push_shares->remote_nodename) == 0) { + } + fprintf(fp, "ignet_push_share|%s", + sc.ignet_push_shares->remote_nodename); + if (strlen(sc.ignet_push_shares->remote_roomname) > 0) { + fprintf(fp, "|%s", sc.ignet_push_shares->remote_roomname); + } + fprintf(fp, "\n"); + mptr = sc.ignet_push_shares->next; + free(sc.ignet_push_shares); + sc.ignet_push_shares = mptr; + } + if (sc.misc != NULL) { + fwrite(sc.misc, strlen(sc.misc), 1, fp); } + free(sc.misc); fclose(fp); } + end_critical_section(S_NETCONFIGS); +} + + + +/* + * Send the *entire* contents of the current room to one specific network node, + * ignoring anything we know about which messages have already undergone + * network processing. This can be used to bring a new node into sync. + */ +int network_sync_to(char *target_node) { + struct SpoolControl sc; + int num_spooled = 0; + int found_node = 0; + char buf[256]; + char sc_type[256]; + char sc_node[256]; + char sc_room[256]; + char filename[256]; + FILE *fp; + + /* Grab the configuration line we're looking for */ + assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir); + begin_critical_section(S_NETCONFIGS); + fp = fopen(filename, "r"); + if (fp == NULL) { + end_critical_section(S_NETCONFIGS); + return(-1); + } + while (fgets(buf, sizeof buf, fp) != NULL) { + buf[strlen(buf)-1] = 0; + extract_token(sc_type, buf, 0, '|', sizeof sc_type); + extract_token(sc_node, buf, 1, '|', sizeof sc_node); + extract_token(sc_room, buf, 2, '|', sizeof sc_room); + if ( (!strcasecmp(sc_type, "ignet_push_share")) + && (!strcasecmp(sc_node, target_node)) ) { + found_node = 1; + + /* Concise syntax because we don't need a full linked-list */ + memset(&sc, 0, sizeof(struct SpoolControl)); + sc.ignet_push_shares = (struct maplist *) + malloc(sizeof(struct maplist)); + sc.ignet_push_shares->next = NULL; + safestrncpy(sc.ignet_push_shares->remote_nodename, + sc_node, + sizeof sc.ignet_push_shares->remote_nodename); + safestrncpy(sc.ignet_push_shares->remote_roomname, + sc_room, + sizeof sc.ignet_push_shares->remote_roomname); + } + } + fclose(fp); + end_critical_section(S_NETCONFIGS); - lprintf(5, "Outbound batch processing finished for <%s>\n", - CC->quickroom.QRname); + if (!found_node) return(-1); + + /* Send ALL messages */ + num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, + network_spool_msg, &sc); + + /* Concise cleanup because we know there's only one node in the sc */ + free(sc.ignet_push_shares); + + lprintf(CTDL_NOTICE, "Synchronized %d messages to <%s>\n", + num_spooled, target_node); + return(num_spooled); } +/* + * Implements the NSYN command + */ +void cmd_nsyn(char *argbuf) { + int num_spooled; + char target_node[256]; + + if (CtdlAccessCheck(ac_aide)) return; + + extract_token(target_node, argbuf, 0, '|', sizeof target_node); + num_spooled = network_sync_to(target_node); + if (num_spooled >= 0) { + cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled); + } + else { + cprintf("%d No such room/node share exists.\n", + ERROR + ROOM_NOT_FOUND); + } +} + + + /* * Batch up and send all outbound traffic from the current room */ -void network_queue_room(struct quickroom *qrbuf, void *data) { +void network_queue_room(struct ctdlroom *qrbuf, void *data) { struct RoomProcList *ptr; - ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList)); + ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList)); if (ptr == NULL) return; safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name); @@ -672,7 +1180,7 @@ void network_queue_room(struct quickroom *qrbuf, void *data) { * Learn topology from path fields */ void network_learn_topology(char *node, char *path) { - char nexthop[SIZ]; + char nexthop[256]; struct NetMap *nmptr; strcpy(nexthop, ""); @@ -680,19 +1188,21 @@ void network_learn_topology(char *node, char *path) { if (num_tokens(path, '!') < 3) return; for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) { if (!strcasecmp(nmptr->nodename, node)) { - extract_token(nmptr->nexthop, path, 0, '!'); + extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop); nmptr->lastcontact = time(NULL); + ++netmap_changed; return; } } /* If we got here then it's not in the map, so add it. */ - nmptr = (struct NetMap *) mallok(sizeof (struct NetMap)); + nmptr = (struct NetMap *) malloc(sizeof (struct NetMap)); strcpy(nmptr->nodename, node); nmptr->lastcontact = time(NULL); - extract_token(nmptr->nexthop, path, 0, '!'); + extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop); nmptr->next = the_netmap; the_netmap = nmptr; + ++netmap_changed; } @@ -711,7 +1221,7 @@ void network_bounce(struct CtdlMessage *msg, char *reason) { static int serialnum = 0; size_t size; - lprintf(9, "entering network_bounce()\n"); + lprintf(CTDL_DEBUG, "entering network_bounce()\n"); if (msg == NULL) return; @@ -721,47 +1231,51 @@ void network_bounce(struct CtdlMessage *msg, char *reason) { * Give it a fresh message ID */ if (msg->cm_fields['I'] != NULL) { - phree(msg->cm_fields['I']); + free(msg->cm_fields['I']); } snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s", (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn); - msg->cm_fields['I'] = strdoop(buf); + msg->cm_fields['I'] = strdup(buf); /* * FIXME ... right now we're just sending a bounce; we really want to * include the text of the bounced message. */ if (msg->cm_fields['M'] != NULL) { - phree(msg->cm_fields['M']); + free(msg->cm_fields['M']); } - msg->cm_fields['M'] = strdoop(reason); + msg->cm_fields['M'] = strdup(reason); msg->cm_format_type = 0; /* * Turn the message around */ if (msg->cm_fields['R'] == NULL) { - phree(msg->cm_fields['R']); + free(msg->cm_fields['R']); } if (msg->cm_fields['D'] == NULL) { - phree(msg->cm_fields['D']); + free(msg->cm_fields['D']); } snprintf(recipient, sizeof recipient, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']); if (msg->cm_fields['A'] == NULL) { - phree(msg->cm_fields['A']); + free(msg->cm_fields['A']); } if (msg->cm_fields['N'] == NULL) { - phree(msg->cm_fields['N']); + free(msg->cm_fields['N']); } - msg->cm_fields['A'] = strdoop(BOUNCESOURCE); - msg->cm_fields['N'] = strdoop(config.c_nodename); - + if (msg->cm_fields['U'] == NULL) { + free(msg->cm_fields['U']); + } + + msg->cm_fields['A'] = strdup(BOUNCESOURCE); + msg->cm_fields['N'] = strdup(config.c_nodename); + msg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)"); /* prepend our node to the path */ if (msg->cm_fields['P'] != NULL) { @@ -769,17 +1283,17 @@ void network_bounce(struct CtdlMessage *msg, char *reason) { msg->cm_fields['P'] = NULL; } else { - oldpath = strdoop("unknown_user"); + oldpath = strdup("unknown_user"); } size = strlen(oldpath) + SIZ; - msg->cm_fields['P'] = mallok(size); + msg->cm_fields['P'] = malloc(size); snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath); - phree(oldpath); + free(oldpath); /* Now submit the message */ valid = validate_recipients(recipient); - if (valid != NULL) if (valid->num_error > 0) { - phree(valid); + if (valid != NULL) if (valid->num_error != 0) { + free(valid); valid = NULL; } if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) { @@ -794,9 +1308,9 @@ void network_bounce(struct CtdlMessage *msg, char *reason) { CtdlSubmitMsg(msg, valid, force_room); /* Clean up */ - if (valid != NULL) phree(valid); + if (valid != NULL) free(valid); CtdlFreeMessage(msg); - lprintf(9, "leaving network_bounce()\n"); + lprintf(CTDL_DEBUG, "leaving network_bounce()\n"); } @@ -816,21 +1330,33 @@ void network_process_buffer(char *buffer, long size) { char *oldpath = NULL; char filename[SIZ]; FILE *fp; - char buf[SIZ]; + char nexthop[SIZ]; + unsigned char firstbyte; + unsigned char lastbyte; + + /* Validate just a little bit. First byte should be FF and + * last byte should be 00. + */ + memcpy(&firstbyte, &buffer[0], 1); + memcpy(&lastbyte, &buffer[size-1], 1); + if ( (firstbyte != 255) || (lastbyte != 0) ) { + lprintf(CTDL_ERR, "Corrupt message! Ignoring.\n"); + return; + } /* Set default target room to trash */ strcpy(target_room, TWITROOM); /* Load the message into memory */ - msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage)); - memset(msg, 0, sizeof(struct CtdlMessage)); - msg->cm_magic = CTDLMESSAGE_MAGIC; - msg->cm_anon_type = buffer[1]; - msg->cm_format_type = buffer[2]; + msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_anon_type = buffer[1]; + msg->cm_format_type = buffer[2]; for (pos = 3; pos < size; ++pos) { field = buffer[pos]; - msg->cm_fields[field] = strdoop(&buffer[pos+1]); + msg->cm_fields[field] = strdup(&buffer[pos+1]); pos = pos + strlen(&buffer[(int)pos]); } @@ -839,7 +1365,8 @@ void network_process_buffer(char *buffer, long size) { if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) { /* route the message */ - if (is_valid_node(NULL, NULL, + strcpy(nexthop, ""); + if (is_valid_node(nexthop, NULL, msg->cm_fields['D']) == 0) { /* prepend our node to the path */ @@ -848,28 +1375,37 @@ void network_process_buffer(char *buffer, long size) { msg->cm_fields['P'] = NULL; } else { - oldpath = strdoop("unknown_user"); + oldpath = strdup("unknown_user"); } size = strlen(oldpath) + SIZ; - msg->cm_fields['P'] = mallok(size); + msg->cm_fields['P'] = malloc(size); snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath); - phree(oldpath); + free(oldpath); /* serialize the message */ serialize_message(&sermsg, msg); /* now send it */ - snprintf(filename, sizeof filename, - "./network/spoolout/%s", - msg->cm_fields['D']); + if (strlen(nexthop) == 0) { + strcpy(nexthop, msg->cm_fields['D']); + } + snprintf(filename, + sizeof filename, + "%s/%s", + ctdl_netout_dir, + nexthop); + lprintf(CTDL_DEBUG, "Appending to %s\n", filename); fp = fopen(filename, "ab"); if (fp != NULL) { fwrite(sermsg.ser, sermsg.len, 1, fp); fclose(fp); } - phree(sermsg.ser); + else { + lprintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno)); + } + free(sermsg.ser); CtdlFreeMessage(msg); return; } @@ -887,20 +1423,12 @@ void network_process_buffer(char *buffer, long size) { } /* - * Check to see if we already have a copy of this message + * Check to see if we already have a copy of this message, and + * abort its processing if so. (We used to post a warning to Aide> + * every time this happened, but the network is now so densely + * connected that it's inevitable.) */ if (network_usetable(msg) != 0) { - snprintf(buf, sizeof buf, - "Loopzapper rejected message <%s> " - "from <%s> in <%s> @ <%s>\n", - ((msg->cm_fields['I']!=NULL)?(msg->cm_fields['I']):""), - ((msg->cm_fields['A']!=NULL)?(msg->cm_fields['A']):""), - ((msg->cm_fields['O']!=NULL)?(msg->cm_fields['O']):""), - ((msg->cm_fields['N']!=NULL)?(msg->cm_fields['N']):"") - ); - aide_message(buf); - CtdlFreeMessage(msg); - msg = NULL; return; } @@ -910,26 +1438,32 @@ void network_process_buffer(char *buffer, long size) { msg->cm_fields['P']); } - /* Does it have a recipient? If so, validate it... */ - if (msg->cm_fields['R'] != NULL) { + /* Is the sending node giving us a very persuasive suggestion about + * which room this message should be saved in? If so, go with that. + */ + if (msg->cm_fields['C'] != NULL) { + safestrncpy(target_room, + msg->cm_fields['C'], + sizeof target_room); + } + + /* Otherwise, does it have a recipient? If so, validate it... */ + else if (msg->cm_fields['R'] != NULL) { recp = validate_recipients(msg->cm_fields['R']); - if (recp != NULL) if (recp->num_error > 0) { + if (recp != NULL) if (recp->num_error != 0) { network_bounce(msg, "A message you sent could not be delivered due to an invalid address.\n" "Please check the address and try sending the message again.\n"); msg = NULL; - phree(recp); + free(recp); return; - } + } strcpy(target_room, ""); /* no target room if mail */ } - else if (msg->cm_fields['C'] != NULL) { - safestrncpy(target_room, - msg->cm_fields['C'], - sizeof target_room); - } - + /* Our last shot at finding a home for this message is to see if + * it has the O field (Originating room) set. + */ else if (msg->cm_fields['O'] != NULL) { safestrncpy(target_room, msg->cm_fields['O'], @@ -938,21 +1472,21 @@ void network_process_buffer(char *buffer, long size) { /* Strip out fields that are only relevant during transit */ if (msg->cm_fields['D'] != NULL) { - phree(msg->cm_fields['D']); + free(msg->cm_fields['D']); msg->cm_fields['D'] = NULL; } if (msg->cm_fields['C'] != NULL) { - phree(msg->cm_fields['C']); + free(msg->cm_fields['C']); msg->cm_fields['C'] = NULL; } /* save the message into a room */ if (PerformNetprocHooks(msg, target_room) == 0) { msg->cm_flags = CM_SKIP_HOOKS; - CtdlSubmitMsg(msg, recp, target_room); + CtdlSubmitMsg(msg, recp, target_room); } CtdlFreeMessage(msg); - phree(recp); + free(recp); } @@ -966,12 +1500,12 @@ void network_process_message(FILE *fp, long msgstart, long msgend) { hold_pos = ftell(fp); size = msgend - msgstart + 1; - buffer = mallok(size); + buffer = malloc(size); if (buffer != NULL) { fseek(fp, msgstart, SEEK_SET); fread(buffer, size, 1, fp); network_process_buffer(buffer, size); - phree(buffer); + free(buffer); } fseek(fp, hold_pos, SEEK_SET); @@ -988,15 +1522,16 @@ void network_process_file(char *filename) { long msgcur = 0L; int ch; - lprintf(7, "network: processing <%s>\n", filename); fp = fopen(filename, "rb"); if (fp == NULL) { - lprintf(5, "Error opening %s: %s\n", + lprintf(CTDL_CRIT, "Error opening %s: %s\n", filename, strerror(errno)); return; } + lprintf(CTDL_INFO, "network: processing <%s>\n", filename); + /* Look for messages in the data stream and break them out */ while (ch = getc(fp), ch >= 0) { @@ -1027,22 +1562,76 @@ void network_process_file(char *filename) { void network_do_spoolin(void) { DIR *dp; struct dirent *d; - char filename[SIZ]; + struct stat statbuf; + char filename[256]; + static time_t last_spoolin_mtime = 0L; + + /* + * Check the spoolin directory's modification time. If it hasn't + * been touched, we don't need to scan it. + */ + if (stat(ctdl_netin_dir, &statbuf)) return; + if (statbuf.st_mtime == last_spoolin_mtime) { + lprintf(CTDL_DEBUG, "network: nothing in inbound queue\n"); + return; + } + last_spoolin_mtime = statbuf.st_mtime; + lprintf(CTDL_DEBUG, "network: processing inbound queue\n"); - dp = opendir("./network/spoolin"); + /* + * Ok, there's something interesting in there, so scan it. + */ + dp = opendir(ctdl_netin_dir); if (dp == NULL) return; while (d = readdir(dp), d != NULL) { - snprintf(filename, sizeof filename, "./network/spoolin/%s", d->d_name); - network_process_file(filename); + if ((strcmp(d->d_name, ".")) && (strcmp(d->d_name, ".."))) { + snprintf(filename, + sizeof filename, + "%s/%s", + ctdl_netin_dir, + d->d_name); + network_process_file(filename); + } } - closedir(dp); } +/* + * Delete any files in the outbound queue that were intended + * to be sent to nodes which no longer exist. + */ +void network_purge_spoolout(void) { + DIR *dp; + struct dirent *d; + char filename[256]; + char nexthop[256]; + int i; + + dp = opendir(ctdl_netout_dir); + if (dp == NULL) return; + + while (d = readdir(dp), d != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + snprintf(filename, + sizeof filename, + "%s/%s", + ctdl_netout_dir, + d->d_name); + + strcpy(nexthop, ""); + i = is_valid_node(nexthop, NULL, d->d_name); + + if ( (i != 0) || (strlen(nexthop) > 0) ) { + unlink(filename); + } + } + closedir(dp); +} /* @@ -1057,10 +1646,10 @@ void receive_spool(int sock, char *remote_nodename) { long plen; FILE *fp; - strcpy(tempfilename, tmpnam(NULL)); + CtdlMakeTempFileName(tempfilename, sizeof tempfilename); if (sock_puts(sock, "NDOP") < 0) return; if (sock_gets(sock, buf) < 0) return; - lprintf(9, "<%s\n", buf); + lprintf(CTDL_DEBUG, "<%s\n", buf); if (buf[0] != '2') { return; } @@ -1069,7 +1658,7 @@ void receive_spool(int sock, char *remote_nodename) { bytes_received = 0L; fp = fopen(tempfilename, "w"); if (fp == NULL) { - lprintf(9, "cannot open download file locally: %s\n", + lprintf(CTDL_CRIT, "cannot open download file locally: %s\n", strerror(errno)); return; } @@ -1110,9 +1699,18 @@ void receive_spool(int sock, char *remote_nodename) { unlink(tempfilename); return; } - lprintf(9, "%s\n", buf); - snprintf(buf, sizeof buf, "mv %s ./network/spoolin/%s.%ld", - tempfilename, remote_nodename, (long) getpid()); + if (download_len > 0) + lprintf(CTDL_NOTICE, "Received %ld octets from <%s>", + download_len, remote_nodename); + lprintf(CTDL_DEBUG, "%s", buf); + /* TODO: make move inline. forking is verry expensive. */ + snprintf(buf, + sizeof buf, + "mv %s %s/%s.%ld", + tempfilename, + ctdl_netin_dir, + remote_nodename, + (long) getpid()); system(buf); } @@ -1126,28 +1724,30 @@ void transmit_spool(int sock, char *remote_nodename) char buf[SIZ]; char pbuf[4096]; long plen; - long bytes_to_write, thisblock; + long bytes_to_write, thisblock, bytes_written; int fd; char sfname[128]; if (sock_puts(sock, "NUOP") < 0) return; if (sock_gets(sock, buf) < 0) return; - lprintf(9, "<%s\n", buf); + lprintf(CTDL_DEBUG, "<%s\n", buf); if (buf[0] != '2') { return; } - snprintf(sfname, sizeof sfname, "./network/spoolout/%s", remote_nodename); + snprintf(sfname, sizeof sfname, + "%s/%s", + ctdl_netout_dir, + remote_nodename); fd = open(sfname, O_RDONLY); if (fd < 0) { - if (errno == ENOENT) { - lprintf(9, "Nothing to send.\n"); - } else { - lprintf(5, "cannot open upload file locally: %s\n", + if (errno != ENOENT) { + lprintf(CTDL_CRIT, "cannot open upload file locally: %s\n", strerror(errno)); } return; } + bytes_written = 0; while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) { bytes_to_write = plen; while (bytes_to_write > 0L) { @@ -1167,7 +1767,8 @@ void transmit_spool(int sock, char *remote_nodename) close(fd); return; } - bytes_to_write = bytes_to_write - thisblock; + bytes_to_write -= thisblock; + bytes_written += thisblock; } else { goto ABORTUPL; } @@ -1178,8 +1779,11 @@ ABORTUPL: close(fd); if (sock_puts(sock, "UCLS 1") < 0) return; if (sock_gets(sock, buf) < 0) return; - lprintf(9, "<%s\n", buf); + lprintf(CTDL_NOTICE, "Sent %ld octets to <%s>\n", + bytes_written, remote_nodename); + lprintf(CTDL_DEBUG, "<%s\n", buf); if (buf[0] == '2') { + lprintf(CTDL_DEBUG, "Removing <%s>\n", sfname); unlink(sfname); } } @@ -1195,27 +1799,27 @@ void network_poll_node(char *node, char *secret, char *host, char *port) { if (network_talking_to(node, NTT_CHECK)) return; network_talking_to(node, NTT_ADD); - lprintf(5, "Polling node <%s> at %s:%s\n", node, host, port); + lprintf(CTDL_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port); sock = sock_connect(host, port, "tcp"); if (sock < 0) { - lprintf(7, "Could not connect: %s\n", strerror(errno)); + lprintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno)); network_talking_to(node, NTT_REMOVE); return; } - lprintf(9, "Connected!\n"); + lprintf(CTDL_DEBUG, "Connected!\n"); /* Read the server greeting */ if (sock_gets(sock, buf) < 0) goto bail; - lprintf(9, ">%s\n", buf); + lprintf(CTDL_DEBUG, ">%s\n", buf); /* Identify ourselves */ snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret); - lprintf(9, "<%s\n", buf); + lprintf(CTDL_DEBUG, "<%s\n", buf); if (sock_puts(sock, buf) <0) goto bail; if (sock_gets(sock, buf) < 0) goto bail; - lprintf(9, ">%s\n", buf); + lprintf(CTDL_DEBUG, ">%s\n", buf); if (buf[0] != '2') goto bail; /* At this point we are authenticated. */ @@ -1231,38 +1835,68 @@ bail: sock_close(sock); /* * Poll other Citadel nodes and transfer inbound/outbound network data. + * Set "full" to nonzero to force a poll of every node, or to zero to poll + * only nodes to which we have data to send. */ -void network_poll_other_citadel_nodes(void) { - char *ignetcfg = NULL; +void network_poll_other_citadel_nodes(int full_poll) { int i; - char linebuf[SIZ]; + char linebuf[256]; char node[SIZ]; - char host[SIZ]; - char port[SIZ]; - char secret[SIZ]; - - ignetcfg = CtdlGetSysConfig(IGNETCFG); - if (ignetcfg == NULL) return; /* no nodes defined */ + char host[256]; + char port[256]; + char secret[256]; + int poll = 0; + char spoolfile[256]; + + if (working_ignetcfg == NULL) { + lprintf(CTDL_DEBUG, "No nodes defined - not polling\n"); + return; + } /* Use the string tokenizer to grab one line at a time */ - for (i=0; i 0) && (strlen(secret) > 0) && (strlen(host) > 0) && strlen(port) > 0) { - network_poll_node(node, secret, host, port); + poll = full_poll; + if (poll == 0) { + snprintf(spoolfile, + sizeof spoolfile, + "%s/%s", + ctdl_netout_dir, + node); + if (access(spoolfile, R_OK) == 0) { + poll = 1; + } + } + if (poll) { + network_poll_node(node, secret, host, port); + } } } - phree(ignetcfg); } +/* + * It's ok if these directories already exist. Just fail silently. + */ +void create_spool_dirs(void) { + mkdir(ctdl_spool_dir, 0700); + chown(ctdl_spool_dir, CTDLUID, (-1)); + mkdir(ctdl_netin_dir, 0700); + chown(ctdl_netin_dir, CTDLUID, (-1)); + mkdir(ctdl_netout_dir, 0700); + chown(ctdl_netout_dir, CTDLUID, (-1)); +} + + @@ -1272,14 +1906,17 @@ void network_poll_other_citadel_nodes(void) { * Run through the rooms doing various types of network stuff. */ void network_do_queue(void) { - static int doing_queue = 0; static time_t last_run = 0L; struct RoomProcList *ptr; + int full_processing = 1; /* - * Run no more frequently than once every n seconds + * Run the full set of processing tasks no more frequently + * than once every n seconds */ - if ( (time(NULL) - last_run) < config.c_net_freq ) return; + if ( (time(NULL) - last_run) < config.c_net_freq ) { + full_processing = 0; + } /* * This is a simple concurrency check to make sure only one queue run @@ -1289,12 +1926,16 @@ void network_do_queue(void) { */ if (doing_queue) return; doing_queue = 1; - last_run = time(NULL); + + /* Load the IGnet Configuration into memory */ + load_working_ignetcfg(); /* - * Poll other Citadel nodes. + * Poll other Citadel nodes. Maybe. If "full_processing" is set + * then we poll everyone. Otherwise we only poll nodes we have stuff + * to send to. */ - network_poll_other_citadel_nodes(); + network_poll_other_citadel_nodes(full_processing); /* * Load the network map and filter list into memory. @@ -1305,18 +1946,20 @@ void network_do_queue(void) { /* * Go ahead and run the queue */ - lprintf(7, "network: loading outbound queue\n"); - ForEachRoom(network_queue_room, NULL); - - lprintf(7, "network: running outbound queue\n"); - while (rplist != NULL) { - network_spoolout_room(rplist->name); - ptr = rplist; - rplist = rplist->next; - phree(ptr); + if (full_processing) { + lprintf(CTDL_DEBUG, "network: loading outbound queue\n"); + ForEachRoom(network_queue_room, NULL); + + lprintf(CTDL_DEBUG, "network: running outbound queue\n"); + while (rplist != NULL) { + network_spoolout_room(rplist->name); + ptr = rplist; + rplist = rplist->next; + free(ptr); + } } - lprintf(7, "network: processing inbound queue\n"); + /* If there is anything in the inbound queue, process it */ network_do_spoolin(); /* Save the network map back to disk */ @@ -1326,59 +1969,82 @@ void network_do_queue(void) { free_filter_list(filterlist); filterlist = NULL; - lprintf(7, "network: queue run completed\n"); + network_purge_spoolout(); + + lprintf(CTDL_DEBUG, "network: queue run completed\n"); + + if (full_processing) { + last_run = time(NULL); + } + doing_queue = 0; } /* * cmd_netp() - authenticate to the server as another Citadel node polling - * for network traffic + * for network traffic */ void cmd_netp(char *cmdbuf) { - char node[SIZ]; - char pass[SIZ]; + char node[256]; + char pass[256]; + int v; - char secret[SIZ]; - char nexthop[SIZ]; + char secret[256]; + char nexthop[256]; + + /* Authenticate */ + extract_token(node, cmdbuf, 0, '|', sizeof node); + extract_token(pass, cmdbuf, 1, '|', sizeof pass); + + if (doing_queue) { + lprintf(CTDL_WARNING, "Network node <%s> refused - spooling", node); + cprintf("%d spooling - try again in a few minutes\n", + ERROR + RESOURCE_BUSY); + return; + } - extract(node, cmdbuf, 0); - extract(pass, cmdbuf, 1); + /* load the IGnet Configuration to check node validity */ + load_working_ignetcfg(); + v = is_valid_node(nexthop, secret, node); - if (is_valid_node(nexthop, secret, node) != 0) { - cprintf("%d authentication failed\n", ERROR); + if (v != 0) { + lprintf(CTDL_WARNING, "Unknown node <%s>\n", node); + cprintf("%d authentication failed\n", + ERROR + PASSWORD_REQUIRED); return; } if (strcasecmp(pass, secret)) { - cprintf("%d authentication failed\n", ERROR); + lprintf(CTDL_WARNING, "Bad password for network node <%s>", node); + cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED); return; } if (network_talking_to(node, NTT_CHECK)) { - cprintf("%d Already talking to %s right now\n", ERROR, node); + lprintf(CTDL_WARNING, "Duplicate session for network node <%s>", node); + cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node); return; } safestrncpy(CC->net_node, node, sizeof CC->net_node); network_talking_to(node, NTT_ADD); + lprintf(CTDL_NOTICE, "Network node <%s> logged in\n", CC->net_node); cprintf("%d authenticated as network node '%s'\n", CIT_OK, CC->net_node); } - - - - /* * Module entry point */ -char *Dynamic_Module_Init(void) +char *serv_network_init(void) { + create_spool_dirs(); CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config"); CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config"); CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller"); + CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node"); CtdlRegisterSessionHook(network_do_queue, EVT_TIMER); return "$Id$"; }