4 * This module handles shared rooms, inter-Citadel mail, and outbound
5 * mailing list processing.
7 * Copyright (C) 2000-2001 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 static int serialnum = 0;
741 struct ser_ret sermsg;
742 char *oldpath = NULL;
745 lprintf(9, "entering network_bounce()\n");
747 if (msg == NULL) return;
750 * Give it a fresh message ID
752 if (msg->cm_fields['I'] != NULL) {
753 phree(msg->cm_fields['I']);
755 sprintf(buf, "%ld.%04x.%04x@%s",
756 (long)time(NULL), getpid(), ++serialnum, config.c_fqdn);
757 msg->cm_fields['I'] = strdoop(buf);
760 * FIXME ... right now we're just sending a bounce; we really want to
761 * include the text of the bounced message.
763 if (msg->cm_fields['M'] != NULL) {
764 phree(msg->cm_fields['M']);
766 msg->cm_fields['M'] = strdoop(reason);
767 msg->cm_format_type = 0;
770 * Turn the message around
772 if (msg->cm_fields['R'] == NULL) {
773 phree(msg->cm_fields['R']);
776 if (msg->cm_fields['D'] == NULL) {
777 phree(msg->cm_fields['D']);
780 msg->cm_fields['R'] = msg->cm_fields['A'];
781 msg->cm_fields['D'] = msg->cm_fields['N'];
782 msg->cm_fields['A'] = strdoop(BOUNCESOURCE);
783 msg->cm_fields['N'] = strdoop(config.c_nodename);
785 if (!strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
786 phree(msg->cm_fields['D']);
790 * If this is a bounce of a bounce, send it to the Aide> room
791 * instead of looping around forever
793 if (msg->cm_fields['D'] == NULL) if (msg->cm_fields['R'] != NULL)
794 if (!strcasecmp(msg->cm_fields['R'], BOUNCESOURCE)) {
795 phree(msg->cm_fields['R']);
796 if (msg->cm_fields['C'] != NULL) {
797 phree(msg->cm_fields['C']);
799 msg->cm_fields['C'] = strdoop(AIDEROOM);
802 /* prepend our node to the path */
803 if (msg->cm_fields['P'] != NULL) {
804 oldpath = msg->cm_fields['P'];
805 msg->cm_fields['P'] = NULL;
808 oldpath = strdoop("unknown_user");
810 msg->cm_fields['P'] = mallok(strlen(oldpath) + SIZ);
811 sprintf(msg->cm_fields['P'], "%s!%s", config.c_nodename, oldpath);
814 /* serialize the message */
815 serialize_message(&sermsg, msg);
818 sprintf(filename, "./network/spoolin/bounce.%04x.%04x",
819 getpid(), serialnum);
821 fp = fopen(filename, "ab");
828 CtdlFreeMessage(msg);
829 lprintf(9, "leaving network_bounce()\n");
836 * Process a buffer containing a single message from a single file
837 * from the inbound queue
839 void network_process_buffer(char *buffer, long size) {
840 struct CtdlMessage *msg;
845 struct usersupp tempUS;
847 char target_room[ROOMNAMELEN];
848 struct ser_ret sermsg;
849 char *oldpath = NULL;
854 /* Set default target room to trash */
855 strcpy(target_room, TWITROOM);
857 /* Load the message into memory */
858 msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
859 memset(msg, 0, sizeof(struct CtdlMessage));
860 msg->cm_magic = CTDLMESSAGE_MAGIC;
861 msg->cm_anon_type = buffer[1];
862 msg->cm_format_type = buffer[2];
864 for (pos = 3; pos < size; ++pos) {
866 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
867 pos = pos + strlen(&buffer[(int)pos]);
870 /* Check for message routing */
871 if (msg->cm_fields['D'] != NULL) {
872 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
874 /* route the message */
875 if (is_valid_node(NULL, NULL,
876 msg->cm_fields['D']) == 0) {
878 /* prepend our node to the path */
879 if (msg->cm_fields['P'] != NULL) {
880 oldpath = msg->cm_fields['P'];
881 msg->cm_fields['P'] = NULL;
884 oldpath = strdoop("unknown_user");
886 msg->cm_fields['P'] =
887 mallok(strlen(oldpath) + SIZ);
888 sprintf(msg->cm_fields['P'], "%s!%s",
889 config.c_nodename, oldpath);
892 /* serialize the message */
893 serialize_message(&sermsg, msg);
897 "./network/spoolout/%s",
898 msg->cm_fields['D']);
899 fp = fopen(filename, "ab");
906 CtdlFreeMessage(msg);
910 else { /* invalid destination node name */
913 "A message you sent could not be delivered due to an invalid destination node"
914 " name. Please check the address and try sending the message again.\n");
923 * Check to see if we already have a copy of this message
925 if (network_usetable(UT_INSERT, msg) != 0) {
926 sprintf(buf, "Loopzapper rejected message <%s>\n",
927 msg->cm_fields['I']);
929 CtdlFreeMessage(msg);
934 /* Learn network topology from the path */
935 if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
936 network_learn_topology(msg->cm_fields['N'],
937 msg->cm_fields['P']);
940 /* Does it have a recipient? If so, validate it... */
941 if (msg->cm_fields['R'] != NULL) {
943 safestrncpy(recp, msg->cm_fields['R'], sizeof(recp));
945 e = alias(recp); /* alias and mail type */
946 if ((recp[0] == 0) || (e == MES_ERROR)) {
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 else if (e == MES_LOCAL) {
956 a = getuser(&tempUS, recp);
960 "A message you sent could not be delivered because the user does not exist\n"
961 "on this system. Please check the address and try again.\n");
967 MailboxName(target_room, &tempUS, MAILROOM);
972 else if (msg->cm_fields['C'] != NULL) {
973 safestrncpy(target_room,
978 else if (msg->cm_fields['O'] != NULL) {
979 safestrncpy(target_room,
984 /* save the message into a room */
985 msg->cm_flags = CM_SKIP_HOOKS;
986 CtdlSubmitMsg(msg, NULL, target_room);
987 CtdlFreeMessage(msg);
992 * Process a single message from a single file from the inbound queue
994 void network_process_message(FILE *fp, long msgstart, long msgend) {
999 hold_pos = ftell(fp);
1000 size = msgend - msgstart + 1;
1001 buffer = mallok(size);
1002 if (buffer != NULL) {
1003 fseek(fp, msgstart, SEEK_SET);
1004 fread(buffer, size, 1, fp);
1005 network_process_buffer(buffer, size);
1009 fseek(fp, hold_pos, SEEK_SET);
1014 * Process a single file from the inbound queue
1016 void network_process_file(char *filename) {
1018 long msgstart = (-1L);
1019 long msgend = (-1L);
1023 lprintf(7, "network: processing <%s>\n", filename);
1025 fp = fopen(filename, "rb");
1027 lprintf(5, "Error opening %s: %s\n",
1028 filename, strerror(errno));
1032 /* Look for messages in the data stream and break them out */
1033 while (ch = getc(fp), ch >= 0) {
1036 if (msgstart >= 0L) {
1037 msgend = msgcur - 1;
1038 network_process_message(fp, msgstart, msgend);
1046 msgend = msgcur - 1;
1047 if (msgstart >= 0L) {
1048 network_process_message(fp, msgstart, msgend);
1057 * Process anything in the inbound queue
1059 void network_do_spoolin(void) {
1064 dp = opendir("./network/spoolin");
1065 if (dp == NULL) return;
1067 while (d = readdir(dp), d != NULL) {
1068 sprintf(filename, "./network/spoolin/%s", d->d_name);
1069 network_process_file(filename);
1081 * receive network spool from the remote system
1083 void receive_spool(int sock, char *remote_nodename) {
1085 long bytes_received;
1087 static char pbuf[IGNET_PACKET_SIZE];
1088 char tempfilename[PATH_MAX];
1092 strcpy(tempfilename, tmpnam(NULL));
1093 if (sock_puts(sock, "NDOP") < 0) return;
1094 if (sock_gets(sock, buf) < 0) return;
1095 lprintf(9, "<%s\n", buf);
1096 if (buf[0] != '2') {
1099 download_len = extract_long(&buf[4], 0);
1101 bytes_received = 0L;
1102 fp = fopen(tempfilename, "w");
1104 lprintf(9, "cannot open download file locally: %s\n",
1109 while (bytes_received < download_len) {
1110 sprintf(buf, "READ %ld|%ld",
1112 ((download_len - bytes_received > IGNET_PACKET_SIZE)
1113 ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
1114 if (sock_puts(sock, buf) < 0) {
1116 unlink(tempfilename);
1119 if (sock_gets(sock, buf) < 0) {
1121 unlink(tempfilename);
1124 if (buf[0] == '6') {
1125 plen = extract_long(&buf[4], 0);
1126 if (sock_read(sock, pbuf, plen) < 0) {
1128 unlink(tempfilename);
1131 fwrite((char *) pbuf, plen, 1, fp);
1132 bytes_received = bytes_received + plen;
1137 if (sock_puts(sock, "CLOS") < 0) {
1138 unlink(tempfilename);
1141 if (sock_gets(sock, buf) < 0) {
1142 unlink(tempfilename);
1145 lprintf(9, "%s\n", buf);
1146 sprintf(buf, "mv %s ./network/spoolin/%s.%ld",
1147 tempfilename, remote_nodename, (long) getpid());
1154 * transmit network spool to the remote system
1156 void transmit_spool(int sock, char *remote_nodename)
1161 long bytes_to_write, thisblock;
1165 if (sock_puts(sock, "NUOP") < 0) return;
1166 if (sock_gets(sock, buf) < 0) return;
1167 lprintf(9, "<%s\n", buf);
1168 if (buf[0] != '2') {
1172 sprintf(sfname, "./network/spoolout/%s", remote_nodename);
1173 fd = open(sfname, O_RDONLY);
1175 if (errno == ENOENT) {
1176 lprintf(9, "Nothing to send.\n");
1178 lprintf(5, "cannot open upload file locally: %s\n",
1183 while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1184 bytes_to_write = plen;
1185 while (bytes_to_write > 0L) {
1186 sprintf(buf, "WRIT %ld", bytes_to_write);
1187 if (sock_puts(sock, buf) < 0) {
1191 if (sock_gets(sock, buf) < 0) {
1195 thisblock = atol(&buf[4]);
1196 if (buf[0] == '7') {
1197 if (sock_write(sock, pbuf,
1198 (int) thisblock) < 0) {
1202 bytes_to_write = bytes_to_write - thisblock;
1211 if (sock_puts(sock, "UCLS 1") < 0) return;
1212 if (sock_gets(sock, buf) < 0) return;
1213 lprintf(9, "<%s\n", buf);
1214 if (buf[0] == '2') {
1222 * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1224 void network_poll_node(char *node, char *secret, char *host, char *port) {
1228 if (network_talking_to(node, NTT_CHECK)) return;
1229 network_talking_to(node, NTT_ADD);
1230 lprintf(5, "Polling node <%s> at %s:%s\n", node, host, port);
1232 sock = sock_connect(host, port, "tcp");
1234 lprintf(7, "Could not connect: %s\n", strerror(errno));
1235 network_talking_to(node, NTT_REMOVE);
1239 lprintf(9, "Connected!\n");
1241 /* Read the server greeting */
1242 if (sock_gets(sock, buf) < 0) goto bail;
1243 lprintf(9, ">%s\n", buf);
1245 /* Identify ourselves */
1246 sprintf(buf, "NETP %s|%s", config.c_nodename, secret);
1247 lprintf(9, "<%s\n", buf);
1248 if (sock_puts(sock, buf) <0) goto bail;
1249 if (sock_gets(sock, buf) < 0) goto bail;
1250 lprintf(9, ">%s\n", buf);
1251 if (buf[0] != '2') goto bail;
1253 /* At this point we are authenticated. */
1254 receive_spool(sock, node);
1255 transmit_spool(sock, node);
1257 sock_puts(sock, "QUIT");
1258 bail: sock_close(sock);
1259 network_talking_to(node, NTT_REMOVE);
1265 * Poll other Citadel nodes and transfer inbound/outbound network data.
1267 void network_poll_other_citadel_nodes(void) {
1268 char *ignetcfg = NULL;
1276 ignetcfg = CtdlGetSysConfig(IGNETCFG);
1277 if (ignetcfg == NULL) return; /* no nodes defined */
1279 /* Use the string tokenizer to grab one line at a time */
1280 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
1281 extract_token(linebuf, ignetcfg, i, '\n');
1282 extract(node, linebuf, 0);
1283 extract(secret, linebuf, 1);
1284 extract(host, linebuf, 2);
1285 extract(port, linebuf, 3);
1286 if ( (strlen(node) > 0) && (strlen(secret) > 0)
1287 && (strlen(host) > 0) && strlen(port) > 0) {
1288 network_poll_node(node, secret, host, port);
1302 * network_do_queue()
1304 * Run through the rooms doing various types of network stuff.
1306 void network_do_queue(void) {
1307 static int doing_queue = 0;
1308 static time_t last_run = 0L;
1309 struct RoomProcList *ptr;
1312 * Run no more frequently than once every n seconds
1314 if ( (time(NULL) - last_run) < config.c_net_freq ) return;
1317 * This is a simple concurrency check to make sure only one queue run
1318 * is done at a time. We could do this with a mutex, but since we
1319 * don't really require extremely fine granularity here, we'll do it
1320 * with a static variable instead.
1322 if (doing_queue) return;
1324 last_run = time(NULL);
1327 * Poll other Citadel nodes.
1329 network_poll_other_citadel_nodes();
1332 * Load the network map and use table into memory.
1335 network_usetable(UT_LOAD, NULL);
1338 * Go ahead and run the queue
1340 lprintf(7, "network: loading outbound queue\n");
1341 ForEachRoom(network_queue_room, NULL);
1343 lprintf(7, "network: running outbound queue\n");
1344 while (rplist != NULL) {
1345 network_spoolout_room(rplist->name);
1347 rplist = rplist->next;
1351 lprintf(7, "network: processing inbound queue\n");
1352 network_do_spoolin();
1354 /* Save the usetable and network map back to disk */
1355 network_usetable(UT_SAVE, NULL);
1356 write_network_map();
1358 lprintf(7, "network: queue run completed\n");
1365 * cmd_netp() - authenticate to the server as another Citadel node polling
1366 * for network traffic
1368 void cmd_netp(char *cmdbuf)
1376 extract(node, cmdbuf, 0);
1377 extract(pass, cmdbuf, 1);
1379 if (is_valid_node(nexthop, secret, node) != 0) {
1380 cprintf("%d authentication failed\n", ERROR);
1384 if (strcasecmp(pass, secret)) {
1385 cprintf("%d authentication failed\n", ERROR);
1389 if (network_talking_to(node, NTT_CHECK)) {
1390 cprintf("%d Already talking to %s right now\n", ERROR, node);
1394 safestrncpy(CC->net_node, node, sizeof CC->net_node);
1395 network_talking_to(node, NTT_ADD);
1396 cprintf("%d authenticated as network node '%s'\n", OK,
1403 * Module entry point
1405 char *Dynamic_Module_Init(void)
1407 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1408 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1409 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1410 CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);