4 * This module handles shared rooms, inter-Citadel mail, and outbound
5 * mailing list processing.
7 * Copyright (C) 2000-2002 by Art Cancro and others.
8 * This code is released under the terms of the GNU General Public License.
14 * Don't allow polls during network processing
25 #include <sys/types.h>
27 #if TIME_WITH_SYS_TIME
28 # include <sys/time.h>
32 # include <sys/time.h>
43 #include "sysdep_decls.h"
44 #include "citserver.h"
47 #include "dynloader.h"
54 #include "internet_addressing.h"
55 #include "serv_network.h"
56 #include "clientsocket.h"
65 * When we do network processing, it's accomplished in two passes; one to
66 * gather a list of rooms and one to actually do them. It's ok that rplist
67 * is global; this process *only* runs as part of the housekeeping loop and
68 * therefore only one will run at a time.
70 struct RoomProcList *rplist = NULL;
73 * We build a map of network nodes during processing.
75 struct NetMap *the_netmap = NULL;
79 * Manage the use table. This is a list of messages which have recently
80 * arrived on the system. It is maintained and queried to prevent the same
81 * message from being entered into the database multiple times if it happens
82 * to arrive multiple times by accident.
84 int network_usetable(int operation, struct CtdlMessage *msg) {
86 static struct UseTable *ut = NULL;
87 struct UseTable *uptr = NULL;
88 char *serialized_table = NULL;
98 serialized_table = CtdlGetSysConfig(USETABLE);
99 if (serialized_table == NULL) return(0);
101 ptr = serialized_table;
104 while (ptr[0] != 0) {
109 if (strlen(buf) > 0) {
111 uptr = (struct UseTable *)
112 mallok(sizeof(struct UseTable));
115 extract(msgid, buf, 0);
116 uptr->message_id = strdoop(msgid);
117 uptr->timestamp = extract_long(buf, 1);
131 phree(serialized_table);
132 serialized_table = NULL;
136 /* Bail out if we can't generate a message ID */
140 if (msg->cm_fields['I'] == NULL) {
143 if (strlen(msg->cm_fields['I']) == 0) {
147 /* Generate the message ID */
148 strcpy(msgid, msg->cm_fields['I']);
149 if (haschar(msgid, '@') == 0) {
151 if (msg->cm_fields['N'] != NULL) {
152 strcat(msgid, msg->cm_fields['N']);
159 /* Compare to the existing list */
160 for (uptr = ut; uptr != NULL; uptr = uptr->next) {
161 if (!strcasecmp(msgid, uptr->message_id)) {
166 /* If we got to this point, it's unique: add it. */
167 uptr = (struct UseTable *)
168 mallok(sizeof(struct UseTable));
169 if (uptr == NULL) return(0);
171 uptr->message_id = strdoop(msgid);
172 uptr->timestamp = time(NULL);
177 /* Figure out how big the serialized buffer should be */
179 for (uptr = ut; uptr != NULL; uptr = uptr->next) {
180 stlen = stlen + strlen(uptr->message_id) + 20;
182 serialized_table = mallok(stlen);
183 memset(serialized_table, 0, stlen);
186 if ( (serialized_table != NULL)
187 && ( (ut->timestamp - time(NULL)) <
189 sprintf(&serialized_table[strlen(
190 serialized_table)], "%s|%ld\n",
192 (long)ut->timestamp);
195 /* Now free the memory */
198 phree(uptr->message_id);
203 CtdlPutSysConfig(USETABLE, serialized_table);
204 phree(serialized_table);
209 /* should never get here unless illegal operation specified */
215 * Read the network map from its configuration file into memory.
217 void read_network_map(void) {
218 char *serialized_map = NULL;
221 struct NetMap *nmptr;
223 serialized_map = CtdlGetSysConfig(IGNETMAP);
224 if (serialized_map == NULL) return; /* if null, no entries */
226 /* Use the string tokenizer to grab one line at a time */
227 for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
228 extract_token(buf, serialized_map, i, '\n');
229 nmptr = (struct NetMap *) mallok(sizeof(struct NetMap));
230 extract(nmptr->nodename, buf, 0);
231 nmptr->lastcontact = extract_long(buf, 1);
232 extract(nmptr->nexthop, buf, 2);
233 nmptr->next = the_netmap;
237 phree(serialized_map);
242 * Write the network map from memory back to the configuration file.
244 void write_network_map(void) {
245 char *serialized_map = NULL;
246 struct NetMap *nmptr;
248 serialized_map = strdoop("");
250 if (the_netmap != NULL) {
251 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
252 serialized_map = reallok(serialized_map,
253 (strlen(serialized_map)+SIZ) );
254 if (strlen(nmptr->nodename) > 0) {
255 sprintf(&serialized_map[strlen(serialized_map)],
258 (long)nmptr->lastcontact,
264 CtdlPutSysConfig(IGNETMAP, serialized_map);
265 phree(serialized_map);
267 /* Now free the list */
268 while (the_netmap != NULL) {
269 nmptr = the_netmap->next;
278 * Check the network map and determine whether the supplied node name is
279 * valid. If it is not a neighbor node, supply the name of a neighbor node
280 * which is the next hop. If it *is* a neighbor node, we also fill in the
283 int is_valid_node(char *nexthop, char *secret, char *node) {
284 char *ignetcfg = NULL;
289 struct NetMap *nmptr;
296 * First try the neighbor nodes
298 ignetcfg = CtdlGetSysConfig(IGNETCFG);
299 if (ignetcfg == NULL) {
300 if (nexthop != NULL) {
307 if (nexthop != NULL) {
311 /* Use the string tokenizer to grab one line at a time */
312 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
313 extract_token(linebuf, ignetcfg, i, '\n');
314 extract(buf, linebuf, 0);
315 if (!strcasecmp(buf, node)) {
316 if (nexthop != NULL) {
319 if (secret != NULL) {
320 extract(secret, linebuf, 1);
328 return(retval); /* yup, it's a direct neighbor */
332 * If we get to this point we have to see if we know the next hop
334 if (the_netmap != NULL) {
335 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
336 if (!strcasecmp(nmptr->nodename, node)) {
337 if (nexthop != NULL) {
338 strcpy(nexthop, nmptr->nexthop);
346 * If we get to this point, the supplied node name is bogus.
348 lprintf(5, "Invalid node name <%s>\n", node);
356 void cmd_gnet(char *argbuf) {
361 if (CtdlAccessCheck(ac_room_aide)) return;
362 assoc_file_name(filename, &CC->quickroom, "netconfigs");
363 cprintf("%d Network settings for room #%ld <%s>\n",
365 CC->quickroom.QRnumber, CC->quickroom.QRname);
367 fp = fopen(filename, "r");
369 while (fgets(buf, sizeof buf, fp) != NULL) {
370 buf[strlen(buf)-1] = 0;
371 cprintf("%s\n", buf);
380 void cmd_snet(char *argbuf) {
381 char tempfilename[SIZ];
386 if (CtdlAccessCheck(ac_room_aide)) return;
387 safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
388 assoc_file_name(filename, &CC->quickroom, "netconfigs");
390 fp = fopen(tempfilename, "w");
392 cprintf("%d Cannot open %s: %s\n",
393 ERROR+INTERNAL_ERROR,
398 cprintf("%d %s\n", SEND_LISTING, tempfilename);
399 while (client_gets(buf), strcmp(buf, "000")) {
400 fprintf(fp, "%s\n", buf);
404 /* Now copy the temp file to its permanent location
405 * (We use /bin/mv instead of link() because they may be on
406 * different filesystems)
409 snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
416 * Spools out one message from the list.
418 void network_spool_msg(long msgnum, void *userdata) {
419 struct SpoolControl *sc;
420 struct namelist *nptr;
424 char *newpath = NULL;
425 size_t instr_len = SIZ;
426 struct CtdlMessage *msg = NULL;
427 struct CtdlMessage *imsg;
428 struct ser_ret sermsg;
434 int delete_after_send = 0; /* Set to 1 to delete after spooling */
436 sc = (struct SpoolControl *)userdata;
439 * Process mailing list recipients
441 if (sc->listrecps != NULL) {
443 /* First, copy it to the spoolout room */
444 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
445 if (err != 0) return;
448 * Figure out how big a buffer we need to allocate
450 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
451 instr_len = instr_len + strlen(nptr->name);
457 lprintf(9, "Generating delivery instructions\n");
458 instr = mallok(instr_len);
460 lprintf(1, "Cannot allocate %ld bytes for instr...\n",
465 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
466 "bounceto|postmaster@%s\n" ,
467 SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn );
469 /* Generate delivery instructions for each recipient */
470 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
471 sprintf(&instr[strlen(instr)], "remote|%s|0||\n",
476 * Generate a message from the instructions
478 imsg = mallok(sizeof(struct CtdlMessage));
479 memset(imsg, 0, sizeof(struct CtdlMessage));
480 imsg->cm_magic = CTDLMESSAGE_MAGIC;
481 imsg->cm_anon_type = MES_NORMAL;
482 imsg->cm_format_type = FMT_RFC822;
483 imsg->cm_fields['A'] = strdoop("Citadel");
484 imsg->cm_fields['M'] = instr;
486 /* Save delivery instructions in spoolout room */
487 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
488 CtdlFreeMessage(imsg);
492 * Process IGnet push shares
494 if (sc->ignet_push_shares != NULL) {
496 msg = CtdlFetchMessage(msgnum);
499 /* Prepend our node name to the Path field whenever
500 * sending a message to another IGnet node
502 if (msg->cm_fields['P'] == NULL) {
503 msg->cm_fields['P'] = strdoop("username");
505 newpath = mallok(strlen(msg->cm_fields['P']) +
506 strlen(config.c_nodename) + 2);
507 sprintf(newpath, "%s!%s", config.c_nodename,
508 msg->cm_fields['P']);
509 phree(msg->cm_fields['P']);
510 msg->cm_fields['P'] = newpath;
513 * Force the message to appear in the correct room
514 * on the far end by setting the C field correctly
516 if (msg->cm_fields['C'] != NULL) {
517 phree(msg->cm_fields['C']);
519 msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
522 * Determine if this message is set to be deleted
523 * after sending out on the network
525 if (msg->cm_fields['S'] != NULL) {
526 if (!strcasecmp(msg->cm_fields['S'],
528 delete_after_send = 1;
533 * Now serialize it for transmission
535 serialize_message(&sermsg, msg);
537 /* Now send it to every node */
538 for (nptr = sc->ignet_push_shares; nptr != NULL;
543 /* Check for valid node name */
544 if (is_valid_node(NULL,NULL,nptr->name) != 0) {
545 lprintf(3, "Invalid node <%s>\n",
550 /* Check for split horizon */
551 lprintf(9, "Path is %s\n", msg->cm_fields['P']);
552 bang = num_tokens(msg->cm_fields['P'], '!');
553 if (bang > 1) for (i=0; i<(bang-1); ++i) {
554 extract_token(buf, msg->cm_fields['P'],
556 if (!strcasecmp(buf, nptr->name)) {
561 /* Send the message */
564 "./network/spoolout/%s",
566 fp = fopen(filename, "ab");
575 CtdlFreeMessage(msg);
579 /* update lastsent */
580 sc->lastsent = msgnum;
582 /* Delete this message if delete-after-send is set */
583 if (delete_after_send) {
584 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
593 * Batch up and send all outbound traffic from the current room
595 void network_spoolout_room(char *room_to_spool) {
600 struct SpoolControl sc;
601 /* struct namelist *digestrecps = NULL; */
602 struct namelist *nptr;
604 lprintf(7, "Spooling <%s>\n", room_to_spool);
605 if (getroom(&CC->quickroom, room_to_spool) != 0) {
606 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
610 memset(&sc, 0, sizeof(struct SpoolControl));
611 assoc_file_name(filename, &CC->quickroom, "netconfigs");
613 fp = fopen(filename, "r");
615 lprintf(7, "Outbound batch processing skipped for <%s>\n",
616 CC->quickroom.QRname);
620 lprintf(5, "Outbound batch processing started for <%s>\n",
621 CC->quickroom.QRname);
623 while (fgets(buf, sizeof buf, fp) != NULL) {
624 buf[strlen(buf)-1] = 0;
626 extract(instr, buf, 0);
627 if (!strcasecmp(instr, "lastsent")) {
628 sc.lastsent = extract_long(buf, 1);
630 else if (!strcasecmp(instr, "listrecp")) {
631 nptr = (struct namelist *)
632 mallok(sizeof(struct namelist));
633 nptr->next = sc.listrecps;
634 extract(nptr->name, buf, 1);
637 else if (!strcasecmp(instr, "ignet_push_share")) {
638 nptr = (struct namelist *)
639 mallok(sizeof(struct namelist));
640 nptr->next = sc.ignet_push_shares;
641 extract(nptr->name, buf, 1);
642 sc.ignet_push_shares = nptr;
650 /* Do something useful */
651 CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
652 network_spool_msg, &sc);
655 /* Now rewrite the config file */
656 fp = fopen(filename, "w");
658 lprintf(1, "ERROR: cannot open %s: %s\n",
659 filename, strerror(errno));
662 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
664 /* Write out the listrecps while freeing from memory at the
665 * same time. Am I clever or what? :)
667 while (sc.listrecps != NULL) {
668 fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
669 nptr = sc.listrecps->next;
673 while (sc.ignet_push_shares != NULL) {
674 fprintf(fp, "ignet_push_share|%s\n",
675 sc.ignet_push_shares->name);
676 nptr = sc.ignet_push_shares->next;
677 phree(sc.ignet_push_shares);
678 sc.ignet_push_shares = nptr;
684 lprintf(5, "Outbound batch processing finished for <%s>\n",
685 CC->quickroom.QRname);
690 * Batch up and send all outbound traffic from the current room
692 void network_queue_room(struct quickroom *qrbuf, void *data) {
693 struct RoomProcList *ptr;
695 ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
696 if (ptr == NULL) return;
698 safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
705 * Learn topology from path fields
707 void network_learn_topology(char *node, char *path) {
709 struct NetMap *nmptr;
713 if (num_tokens(path, '!') < 3) return;
714 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
715 if (!strcasecmp(nmptr->nodename, node)) {
716 extract_token(nmptr->nexthop, path, 0, '!');
717 nmptr->lastcontact = time(NULL);
722 /* If we got here then it's not in the map, so add it. */
723 nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
724 strcpy(nmptr->nodename, node);
725 nmptr->lastcontact = time(NULL);
726 extract_token(nmptr->nexthop, path, 0, '!');
727 nmptr->next = the_netmap;
735 * Bounce a message back to the sender
737 void network_bounce(struct CtdlMessage *msg, char *reason) {
738 char *oldpath = NULL;
740 char bouncesource[SIZ];
742 struct recptypes *valid = NULL;
743 char force_room[ROOMNAMELEN];
744 static int serialnum = 0;
746 lprintf(9, "entering network_bounce()\n");
748 if (msg == NULL) return;
750 sprintf(bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
753 * Give it a fresh message ID
755 if (msg->cm_fields['I'] != NULL) {
756 phree(msg->cm_fields['I']);
758 sprintf(buf, "%ld.%04x.%04x@%s",
759 (long)time(NULL), getpid(), ++serialnum, config.c_fqdn);
760 msg->cm_fields['I'] = strdoop(buf);
763 * FIXME ... right now we're just sending a bounce; we really want to
764 * include the text of the bounced message.
766 if (msg->cm_fields['M'] != NULL) {
767 phree(msg->cm_fields['M']);
769 msg->cm_fields['M'] = strdoop(reason);
770 msg->cm_format_type = 0;
773 * Turn the message around
775 if (msg->cm_fields['R'] == NULL) {
776 phree(msg->cm_fields['R']);
779 if (msg->cm_fields['D'] == NULL) {
780 phree(msg->cm_fields['D']);
783 snprintf(recipient, sizeof recipient, "%s@%s",
784 msg->cm_fields['A'], msg->cm_fields['N']);
786 if (msg->cm_fields['A'] == NULL) {
787 phree(msg->cm_fields['A']);
790 if (msg->cm_fields['N'] == NULL) {
791 phree(msg->cm_fields['N']);
794 msg->cm_fields['A'] = strdoop(BOUNCESOURCE);
795 msg->cm_fields['N'] = strdoop(config.c_nodename);
798 /* prepend our node to the path */
799 if (msg->cm_fields['P'] != NULL) {
800 oldpath = msg->cm_fields['P'];
801 msg->cm_fields['P'] = NULL;
804 oldpath = strdoop("unknown_user");
806 msg->cm_fields['P'] = mallok(strlen(oldpath) + SIZ);
807 sprintf(msg->cm_fields['P'], "%s!%s", config.c_nodename, oldpath);
810 /* Now submit the message */
811 valid = validate_recipients(recipient);
812 if (valid != NULL) if (valid->num_error > 0) {
816 if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
817 strcpy(force_room, AIDEROOM);
820 strcpy(force_room, "");
822 if ( (valid == NULL) && (strlen(force_room) == 0) ) {
823 strcpy(force_room, AIDEROOM);
825 CtdlSubmitMsg(msg, valid, force_room);
828 if (valid != NULL) phree(valid);
829 CtdlFreeMessage(msg);
830 lprintf(9, "leaving network_bounce()\n");
837 * Process a buffer containing a single message from a single file
838 * from the inbound queue
840 void network_process_buffer(char *buffer, long size) {
841 struct CtdlMessage *msg;
844 struct recptypes *recp = NULL;
845 char target_room[ROOMNAMELEN];
846 struct ser_ret sermsg;
847 char *oldpath = NULL;
852 /* Set default target room to trash */
853 strcpy(target_room, TWITROOM);
855 /* Load the message into memory */
856 msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
857 memset(msg, 0, sizeof(struct CtdlMessage));
858 msg->cm_magic = CTDLMESSAGE_MAGIC;
859 msg->cm_anon_type = buffer[1];
860 msg->cm_format_type = buffer[2];
862 for (pos = 3; pos < size; ++pos) {
864 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
865 pos = pos + strlen(&buffer[(int)pos]);
868 /* Check for message routing */
869 if (msg->cm_fields['D'] != NULL) {
870 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
872 /* route the message */
873 if (is_valid_node(NULL, NULL,
874 msg->cm_fields['D']) == 0) {
876 /* prepend our node to the path */
877 if (msg->cm_fields['P'] != NULL) {
878 oldpath = msg->cm_fields['P'];
879 msg->cm_fields['P'] = NULL;
882 oldpath = strdoop("unknown_user");
884 msg->cm_fields['P'] =
885 mallok(strlen(oldpath) + SIZ);
886 sprintf(msg->cm_fields['P'], "%s!%s",
887 config.c_nodename, oldpath);
890 /* serialize the message */
891 serialize_message(&sermsg, msg);
895 "./network/spoolout/%s",
896 msg->cm_fields['D']);
897 fp = fopen(filename, "ab");
904 CtdlFreeMessage(msg);
908 else { /* invalid destination node name */
911 "A message you sent could not be delivered due to an invalid destination node"
912 " name. Please check the address and try sending the message again.\n");
921 * Check to see if we already have a copy of this message
923 if (network_usetable(UT_INSERT, msg) != 0) {
925 "Loopzapper rejected message <%s> "
926 "from <%s> in <%s> @ <%s>\n",
927 ((msg->cm_fields['I']!=NULL)?(msg->cm_fields['I']):""),
928 ((msg->cm_fields['A']!=NULL)?(msg->cm_fields['A']):""),
929 ((msg->cm_fields['O']!=NULL)?(msg->cm_fields['O']):""),
930 ((msg->cm_fields['N']!=NULL)?(msg->cm_fields['N']):"")
933 CtdlFreeMessage(msg);
938 /* Learn network topology from the path */
939 if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
940 network_learn_topology(msg->cm_fields['N'],
941 msg->cm_fields['P']);
944 /* Does it have a recipient? If so, validate it... */
945 if (msg->cm_fields['R'] != NULL) {
946 recp = validate_recipients(msg->cm_fields['R']);
947 if (recp != NULL) if (recp->num_error > 0) {
949 "A message you sent could not be delivered due to an invalid address.\n"
950 "Please check the address and try sending the message again.\n");
955 strcpy(target_room, ""); /* no target room if mail */
958 else if (msg->cm_fields['C'] != NULL) {
959 safestrncpy(target_room,
964 else if (msg->cm_fields['O'] != NULL) {
965 safestrncpy(target_room,
970 /* Strip out fields that are only relevant during transit */
971 if (msg->cm_fields['D'] != NULL) {
972 phree(msg->cm_fields['D']);
973 msg->cm_fields['D'] = NULL;
975 if (msg->cm_fields['C'] != NULL) {
976 phree(msg->cm_fields['C']);
977 msg->cm_fields['C'] = NULL;
980 /* save the message into a room */
981 if (PerformNetprocHooks(msg, target_room) == 0) {
982 msg->cm_flags = CM_SKIP_HOOKS;
983 CtdlSubmitMsg(msg, recp, target_room);
985 CtdlFreeMessage(msg);
991 * Process a single message from a single file from the inbound queue
993 void network_process_message(FILE *fp, long msgstart, long msgend) {
998 hold_pos = ftell(fp);
999 size = msgend - msgstart + 1;
1000 buffer = mallok(size);
1001 if (buffer != NULL) {
1002 fseek(fp, msgstart, SEEK_SET);
1003 fread(buffer, size, 1, fp);
1004 network_process_buffer(buffer, size);
1008 fseek(fp, hold_pos, SEEK_SET);
1013 * Process a single file from the inbound queue
1015 void network_process_file(char *filename) {
1017 long msgstart = (-1L);
1018 long msgend = (-1L);
1022 lprintf(7, "network: processing <%s>\n", filename);
1024 fp = fopen(filename, "rb");
1026 lprintf(5, "Error opening %s: %s\n",
1027 filename, strerror(errno));
1031 /* Look for messages in the data stream and break them out */
1032 while (ch = getc(fp), ch >= 0) {
1035 if (msgstart >= 0L) {
1036 msgend = msgcur - 1;
1037 network_process_message(fp, msgstart, msgend);
1045 msgend = msgcur - 1;
1046 if (msgstart >= 0L) {
1047 network_process_message(fp, msgstart, msgend);
1056 * Process anything in the inbound queue
1058 void network_do_spoolin(void) {
1063 dp = opendir("./network/spoolin");
1064 if (dp == NULL) return;
1066 while (d = readdir(dp), d != NULL) {
1067 sprintf(filename, "./network/spoolin/%s", d->d_name);
1068 network_process_file(filename);
1080 * receive network spool from the remote system
1082 void receive_spool(int sock, char *remote_nodename) {
1084 long bytes_received;
1086 static char pbuf[IGNET_PACKET_SIZE];
1087 char tempfilename[PATH_MAX];
1091 strcpy(tempfilename, tmpnam(NULL));
1092 if (sock_puts(sock, "NDOP") < 0) return;
1093 if (sock_gets(sock, buf) < 0) return;
1094 lprintf(9, "<%s\n", buf);
1095 if (buf[0] != '2') {
1098 download_len = extract_long(&buf[4], 0);
1100 bytes_received = 0L;
1101 fp = fopen(tempfilename, "w");
1103 lprintf(9, "cannot open download file locally: %s\n",
1108 while (bytes_received < download_len) {
1109 sprintf(buf, "READ %ld|%ld",
1111 ((download_len - bytes_received > IGNET_PACKET_SIZE)
1112 ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
1113 if (sock_puts(sock, buf) < 0) {
1115 unlink(tempfilename);
1118 if (sock_gets(sock, buf) < 0) {
1120 unlink(tempfilename);
1123 if (buf[0] == '6') {
1124 plen = extract_long(&buf[4], 0);
1125 if (sock_read(sock, pbuf, plen) < 0) {
1127 unlink(tempfilename);
1130 fwrite((char *) pbuf, plen, 1, fp);
1131 bytes_received = bytes_received + plen;
1136 if (sock_puts(sock, "CLOS") < 0) {
1137 unlink(tempfilename);
1140 if (sock_gets(sock, buf) < 0) {
1141 unlink(tempfilename);
1144 lprintf(9, "%s\n", buf);
1145 sprintf(buf, "mv %s ./network/spoolin/%s.%ld",
1146 tempfilename, remote_nodename, (long) getpid());
1153 * transmit network spool to the remote system
1155 void transmit_spool(int sock, char *remote_nodename)
1160 long bytes_to_write, thisblock;
1164 if (sock_puts(sock, "NUOP") < 0) return;
1165 if (sock_gets(sock, buf) < 0) return;
1166 lprintf(9, "<%s\n", buf);
1167 if (buf[0] != '2') {
1171 sprintf(sfname, "./network/spoolout/%s", remote_nodename);
1172 fd = open(sfname, O_RDONLY);
1174 if (errno == ENOENT) {
1175 lprintf(9, "Nothing to send.\n");
1177 lprintf(5, "cannot open upload file locally: %s\n",
1182 while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1183 bytes_to_write = plen;
1184 while (bytes_to_write > 0L) {
1185 sprintf(buf, "WRIT %ld", bytes_to_write);
1186 if (sock_puts(sock, buf) < 0) {
1190 if (sock_gets(sock, buf) < 0) {
1194 thisblock = atol(&buf[4]);
1195 if (buf[0] == '7') {
1196 if (sock_write(sock, pbuf,
1197 (int) thisblock) < 0) {
1201 bytes_to_write = bytes_to_write - thisblock;
1210 if (sock_puts(sock, "UCLS 1") < 0) return;
1211 if (sock_gets(sock, buf) < 0) return;
1212 lprintf(9, "<%s\n", buf);
1213 if (buf[0] == '2') {
1221 * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1223 void network_poll_node(char *node, char *secret, char *host, char *port) {
1227 if (network_talking_to(node, NTT_CHECK)) return;
1228 network_talking_to(node, NTT_ADD);
1229 lprintf(5, "Polling node <%s> at %s:%s\n", node, host, port);
1231 sock = sock_connect(host, port, "tcp");
1233 lprintf(7, "Could not connect: %s\n", strerror(errno));
1234 network_talking_to(node, NTT_REMOVE);
1238 lprintf(9, "Connected!\n");
1240 /* Read the server greeting */
1241 if (sock_gets(sock, buf) < 0) goto bail;
1242 lprintf(9, ">%s\n", buf);
1244 /* Identify ourselves */
1245 sprintf(buf, "NETP %s|%s", config.c_nodename, secret);
1246 lprintf(9, "<%s\n", buf);
1247 if (sock_puts(sock, buf) <0) goto bail;
1248 if (sock_gets(sock, buf) < 0) goto bail;
1249 lprintf(9, ">%s\n", buf);
1250 if (buf[0] != '2') goto bail;
1252 /* At this point we are authenticated. */
1253 receive_spool(sock, node);
1254 transmit_spool(sock, node);
1256 sock_puts(sock, "QUIT");
1257 bail: sock_close(sock);
1258 network_talking_to(node, NTT_REMOVE);
1264 * Poll other Citadel nodes and transfer inbound/outbound network data.
1266 void network_poll_other_citadel_nodes(void) {
1267 char *ignetcfg = NULL;
1275 ignetcfg = CtdlGetSysConfig(IGNETCFG);
1276 if (ignetcfg == NULL) return; /* no nodes defined */
1278 /* Use the string tokenizer to grab one line at a time */
1279 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
1280 extract_token(linebuf, ignetcfg, i, '\n');
1281 extract(node, linebuf, 0);
1282 extract(secret, linebuf, 1);
1283 extract(host, linebuf, 2);
1284 extract(port, linebuf, 3);
1285 if ( (strlen(node) > 0) && (strlen(secret) > 0)
1286 && (strlen(host) > 0) && strlen(port) > 0) {
1287 network_poll_node(node, secret, host, port);
1301 * network_do_queue()
1303 * Run through the rooms doing various types of network stuff.
1305 void network_do_queue(void) {
1306 static int doing_queue = 0;
1307 static time_t last_run = 0L;
1308 struct RoomProcList *ptr;
1311 * Run no more frequently than once every n seconds
1313 if ( (time(NULL) - last_run) < config.c_net_freq ) return;
1316 * This is a simple concurrency check to make sure only one queue run
1317 * is done at a time. We could do this with a mutex, but since we
1318 * don't really require extremely fine granularity here, we'll do it
1319 * with a static variable instead.
1321 if (doing_queue) return;
1323 last_run = time(NULL);
1326 * Poll other Citadel nodes.
1328 network_poll_other_citadel_nodes();
1331 * Load the network map and use table into memory.
1334 network_usetable(UT_LOAD, NULL);
1337 * Go ahead and run the queue
1339 lprintf(7, "network: loading outbound queue\n");
1340 ForEachRoom(network_queue_room, NULL);
1342 lprintf(7, "network: running outbound queue\n");
1343 while (rplist != NULL) {
1344 network_spoolout_room(rplist->name);
1346 rplist = rplist->next;
1350 lprintf(7, "network: processing inbound queue\n");
1351 network_do_spoolin();
1353 /* Save the usetable and network map back to disk */
1354 network_usetable(UT_SAVE, NULL);
1355 write_network_map();
1357 lprintf(7, "network: queue run completed\n");
1364 * cmd_netp() - authenticate to the server as another Citadel node polling
1365 * for network traffic
1367 void cmd_netp(char *cmdbuf)
1375 extract(node, cmdbuf, 0);
1376 extract(pass, cmdbuf, 1);
1378 if (is_valid_node(nexthop, secret, node) != 0) {
1379 cprintf("%d authentication failed\n", ERROR);
1383 if (strcasecmp(pass, secret)) {
1384 cprintf("%d authentication failed\n", ERROR);
1388 if (network_talking_to(node, NTT_CHECK)) {
1389 cprintf("%d Already talking to %s right now\n", ERROR, node);
1393 safestrncpy(CC->net_node, node, sizeof CC->net_node);
1394 network_talking_to(node, NTT_ADD);
1395 cprintf("%d authenticated as network node '%s'\n", OK,
1402 * Module entry point
1404 char *Dynamic_Module_Init(void)
1406 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1407 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1408 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1409 CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);