2624b9d3371ecb97d08484c8c124af67a7014a09
[citadel.git] / citadel / serv_network.c
1 /*
2  * $Id$ 
3  *
4  * This module handles shared rooms, inter-Citadel mail, and outbound
5  * mailing list processing.
6  *
7  * Copyright (C) 2000-2002 by Art Cancro and others.
8  * This code is released under the terms of the GNU General Public License.
9  *
10  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
11  * This is a fairly high-level type of critical section.  It ensures that no
12  * two threads work on the netconfigs files at the same time.  Since we do
13  * so many things inside these, here are the rules:
14  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
15  *  2. Do *not* perform any I/O with the client during these sections.
16  *
17  */
18
19 /*
20  * FIXME
21  * Don't allow polls during network processing
22  */
23
24 /*
25  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
26  * requests that have not been confirmed will be deleted.
27  */
28 #define EXP     259200  /* three days */
29
30 #include "sysdep.h"
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <stdio.h>
34 #include <fcntl.h>
35 #include <ctype.h>
36 #include <signal.h>
37 #include <pwd.h>
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <dirent.h>
41 #if TIME_WITH_SYS_TIME
42 # include <sys/time.h>
43 # include <time.h>
44 #else
45 # if HAVE_SYS_TIME_H
46 #  include <sys/time.h>
47 # else
48 #  include <time.h>
49 # endif
50 #endif
51
52 #include <sys/wait.h>
53 #include <string.h>
54 #include <limits.h>
55 #include "citadel.h"
56 #include "server.h"
57 #include "sysdep_decls.h"
58 #include "citserver.h"
59 #include "support.h"
60 #include "config.h"
61 #include "dynloader.h"
62 #include "room_ops.h"
63 #include "user_ops.h"
64 #include "policy.h"
65 #include "database.h"
66 #include "msgbase.h"
67 #include "tools.h"
68 #include "internet_addressing.h"
69 #include "serv_network.h"
70 #include "clientsocket.h"
71 #include "file_ops.h"
72
73 #ifndef HAVE_SNPRINTF
74 #include "snprintf.h"
75 #endif
76
77
78 /*
79  * When we do network processing, it's accomplished in two passes; one to
80  * gather a list of rooms and one to actually do them.  It's ok that rplist
81  * is global; this process *only* runs as part of the housekeeping loop and
82  * therefore only one will run at a time.
83  */
84 struct RoomProcList *rplist = NULL;
85
86 /*
87  * We build a map of network nodes during processing.
88  */
89 struct NetMap *the_netmap = NULL;
90
91 /*
92  * Keep track of what messages to reject
93  */
94 struct FilterList *load_filter_list(void) {
95         char *serialized_list = NULL;
96         int i;
97         char buf[SIZ];
98         struct FilterList *newlist = NULL;
99         struct FilterList *nptr;
100
101         serialized_list = CtdlGetSysConfig(FILTERLIST);
102         if (serialized_list == NULL) return(NULL); /* if null, no entries */
103
104         /* Use the string tokenizer to grab one line at a time */
105         for (i=0; i<num_tokens(serialized_list, '\n'); ++i) {
106                 extract_token(buf, serialized_list, i, '\n');
107                 nptr = (struct FilterList *) mallok(sizeof(struct FilterList));
108                 extract(nptr->fl_user, buf, 0);
109                 striplt(nptr->fl_user);
110                 extract(nptr->fl_room, buf, 1);
111                 striplt(nptr->fl_room);
112                 extract(nptr->fl_node, buf, 2);
113                 striplt(nptr->fl_node);
114
115                 /* Cowardly refuse to add an any/any/any entry that would
116                  * end up filtering every single message.
117                  */
118                 if (strlen(nptr->fl_user) + strlen(nptr->fl_room)
119                    + strlen(nptr->fl_node) == 0) {
120                         phree(nptr);
121                 }
122                 else {
123                         nptr->next = newlist;
124                         newlist = nptr;
125                 }
126         }
127
128         phree(serialized_list);
129         return newlist;
130 }
131
132
133 void free_filter_list(struct FilterList *fl) {
134         if (fl == NULL) return;
135         free_filter_list(fl->next);
136         phree(fl);
137 }
138
139
140
141 /*
142  * Check the use table.  This is a list of messages which have recently
143  * arrived on the system.  It is maintained and queried to prevent the same
144  * message from being entered into the database multiple times if it happens
145  * to arrive multiple times by accident.
146  */
147 int network_usetable(struct CtdlMessage *msg) {
148
149         char msgid[SIZ];
150         struct cdbdata *cdbut;
151         struct UseTable ut;
152
153         /* Bail out if we can't generate a message ID */
154         if (msg == NULL) {
155                 return(0);
156         }
157         if (msg->cm_fields['I'] == NULL) {
158                 return(0);
159         }
160         if (strlen(msg->cm_fields['I']) == 0) {
161                 return(0);
162         }
163
164         /* Generate the message ID */
165         strcpy(msgid, msg->cm_fields['I']);
166         if (haschar(msgid, '@') == 0) {
167                 strcat(msgid, "@");
168                 if (msg->cm_fields['N'] != NULL) {
169                         strcat(msgid, msg->cm_fields['N']);
170                 }
171                 else {
172                         return(0);
173                 }
174         }
175
176         cdbut = cdb_fetch(CDB_USETABLE, msgid, strlen(msgid));
177         if (cdbut != NULL) {
178                 cdb_free(cdbut);
179                 return(1);
180         }
181
182         /* If we got to this point, it's unique: add it. */
183         strcpy(ut.ut_msgid, msgid);
184         ut.ut_timestamp = time(NULL);
185         cdb_store(CDB_USETABLE, msgid, strlen(msgid),
186                 &ut, sizeof(struct UseTable) );
187         return(0);
188 }
189
190
191 /* 
192  * Read the network map from its configuration file into memory.
193  */
194 void read_network_map(void) {
195         char *serialized_map = NULL;
196         int i;
197         char buf[SIZ];
198         struct NetMap *nmptr;
199
200         serialized_map = CtdlGetSysConfig(IGNETMAP);
201         if (serialized_map == NULL) return;     /* if null, no entries */
202
203         /* Use the string tokenizer to grab one line at a time */
204         for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
205                 extract_token(buf, serialized_map, i, '\n');
206                 nmptr = (struct NetMap *) mallok(sizeof(struct NetMap));
207                 extract(nmptr->nodename, buf, 0);
208                 nmptr->lastcontact = extract_long(buf, 1);
209                 extract(nmptr->nexthop, buf, 2);
210                 nmptr->next = the_netmap;
211                 the_netmap = nmptr;
212         }
213
214         phree(serialized_map);
215 }
216
217
218 /*
219  * Write the network map from memory back to the configuration file.
220  */
221 void write_network_map(void) {
222         char *serialized_map = NULL;
223         struct NetMap *nmptr;
224
225         serialized_map = strdoop("");
226
227         if (the_netmap != NULL) {
228                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
229                         serialized_map = reallok(serialized_map,
230                                                 (strlen(serialized_map)+SIZ) );
231                         if (strlen(nmptr->nodename) > 0) {
232                                 snprintf(&serialized_map[strlen(serialized_map)],
233                                         SIZ,
234                                         "%s|%ld|%s\n",
235                                         nmptr->nodename,
236                                         (long)nmptr->lastcontact,
237                                         nmptr->nexthop);
238                         }
239                 }
240         }
241
242         CtdlPutSysConfig(IGNETMAP, serialized_map);
243         phree(serialized_map);
244
245         /* Now free the list */
246         while (the_netmap != NULL) {
247                 nmptr = the_netmap->next;
248                 phree(the_netmap);
249                 the_netmap = nmptr;
250         }
251 }
252
253
254
255 /* 
256  * Check the network map and determine whether the supplied node name is
257  * valid.  If it is not a neighbor node, supply the name of a neighbor node
258  * which is the next hop.  If it *is* a neighbor node, we also fill in the
259  * shared secret.
260  */
261 int is_valid_node(char *nexthop, char *secret, char *node) {
262         char *ignetcfg = NULL;
263         int i;
264         char linebuf[SIZ];
265         char buf[SIZ];
266         int retval;
267         struct NetMap *nmptr;
268
269         if (node == NULL) {
270                 return(-1);
271         }
272
273         /*
274          * First try the neighbor nodes
275          */
276         ignetcfg = CtdlGetSysConfig(IGNETCFG);
277         if (ignetcfg == NULL) {
278                 if (nexthop != NULL) {
279                         strcpy(nexthop, "");
280                 }
281                 return(-1);
282         }
283
284         retval = (-1);
285         if (nexthop != NULL) {
286                 strcpy(nexthop, "");
287         }
288
289         /* Use the string tokenizer to grab one line at a time */
290         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
291                 extract_token(linebuf, ignetcfg, i, '\n');
292                 extract(buf, linebuf, 0);
293                 if (!strcasecmp(buf, node)) {
294                         if (nexthop != NULL) {
295                                 strcpy(nexthop, "");
296                         }
297                         if (secret != NULL) {
298                                 extract(secret, linebuf, 1);
299                         }
300                         retval = 0;
301                 }
302         }
303
304         phree(ignetcfg);
305         if (retval == 0) {
306                 return(retval);         /* yup, it's a direct neighbor */
307         }
308
309         /*      
310          * If we get to this point we have to see if we know the next hop
311          */
312         if (the_netmap != NULL) {
313                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
314                         if (!strcasecmp(nmptr->nodename, node)) {
315                                 if (nexthop != NULL) {
316                                         strcpy(nexthop, nmptr->nexthop);
317                                 }
318                                 return(0);
319                         }
320                 }
321         }
322
323         /*
324          * If we get to this point, the supplied node name is bogus.
325          */
326         lprintf(5, "Invalid node name <%s>\n", node);
327         return(-1);
328 }
329
330
331
332
333
334 void cmd_gnet(char *argbuf) {
335         char filename[SIZ];
336         char buf[SIZ];
337         FILE *fp;
338
339         if (CtdlAccessCheck(ac_room_aide)) return;
340         assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs");
341         cprintf("%d Network settings for room #%ld <%s>\n",
342                 LISTING_FOLLOWS,
343                 CC->quickroom.QRnumber, CC->quickroom.QRname);
344
345         fp = fopen(filename, "r");
346         if (fp != NULL) {
347                 while (fgets(buf, sizeof buf, fp) != NULL) {
348                         buf[strlen(buf)-1] = 0;
349                         cprintf("%s\n", buf);
350                 }
351                 fclose(fp);
352         }
353
354         cprintf("000\n");
355 }
356
357
358 void cmd_snet(char *argbuf) {
359         char tempfilename[SIZ];
360         char filename[SIZ];
361         char buf[SIZ];
362         FILE *fp;
363
364         if (CtdlAccessCheck(ac_room_aide)) return;
365         safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
366         assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs");
367
368         fp = fopen(tempfilename, "w");
369         if (fp == NULL) {
370                 cprintf("%d Cannot open %s: %s\n",
371                         ERROR+INTERNAL_ERROR,
372                         tempfilename,
373                         strerror(errno));
374         }
375
376         cprintf("%d %s\n", SEND_LISTING, tempfilename);
377         while (client_gets(buf), strcmp(buf, "000")) {
378                 fprintf(fp, "%s\n", buf);
379         }
380         fclose(fp);
381
382         /* Now copy the temp file to its permanent location
383          * (We use /bin/mv instead of link() because they may be on
384          * different filesystems)
385          */
386         unlink(filename);
387         snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
388         begin_critical_section(S_NETCONFIGS);
389         system(buf);
390         end_critical_section(S_NETCONFIGS);
391 }
392
393
394 /*
395  * Spools out one message from the list.
396  */
397 void network_spool_msg(long msgnum, void *userdata) {
398         struct SpoolControl *sc;
399         int err;
400         int i;
401         char *newpath = NULL;
402         char *instr = NULL;
403         size_t instr_len = SIZ;
404         struct CtdlMessage *msg = NULL;
405         struct CtdlMessage *imsg;
406         struct namelist *nptr;
407         struct ser_ret sermsg;
408         FILE *fp;
409         char filename[SIZ];
410         char buf[SIZ];
411         int bang = 0;
412         int send = 1;
413         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
414
415         sc = (struct SpoolControl *)userdata;
416
417         /*
418          * Process mailing list recipients
419          */
420         if (sc->listrecps != NULL) {
421         
422                 /* First, copy it to the spoolout room */
423                 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
424                 if (err != 0) return;
425
426                 /* 
427                  * Figure out how big a buffer we need to allocate
428                  */
429                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
430                         instr_len = instr_len + strlen(nptr->name);
431                 }
432         
433                 /*
434                  * allocate...
435                  */
436                 lprintf(9, "Generating delivery instructions\n");
437                 instr = mallok(instr_len);
438                 if (instr == NULL) {
439                         lprintf(1, "Cannot allocate %ld bytes for instr...\n",
440                                 (long)instr_len);
441                         abort();
442                 }
443                 snprintf(instr, instr_len,
444                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
445                         "bounceto|postmaster@%s\n" ,
446                         SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn );
447         
448                 /* Generate delivery instructions for each recipient */
449                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
450                         size_t tmp = strlen(instr);
451                         snprintf(&instr[tmp], instr_len - tmp,
452                                  "remote|%s|0||\n", nptr->name);
453                 }
454         
455                 /*
456                  * Generate a message from the instructions
457                  */
458                 imsg = mallok(sizeof(struct CtdlMessage));
459                 memset(imsg, 0, sizeof(struct CtdlMessage));
460                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
461                 imsg->cm_anon_type = MES_NORMAL;
462                 imsg->cm_format_type = FMT_RFC822;
463                 imsg->cm_fields['A'] = strdoop("Citadel");
464                 imsg->cm_fields['M'] = instr;
465         
466                 /* Save delivery instructions in spoolout room */
467                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
468                 CtdlFreeMessage(imsg);
469         }
470
471         /*
472          * Process digest recipients
473          */
474         if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
475                 fprintf(sc->digestfp,   " -----------------------------------"
476                                         "------------------------------------"
477                                         "-------\n");
478                 CtdlRedirectOutput(sc->digestfp, -1);
479                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 0);
480                 CtdlRedirectOutput(NULL, -1);
481                 sc->num_msgs_spooled += 1;
482         }
483         
484         /*
485          * Process IGnet push shares
486          */
487         if (sc->ignet_push_shares != NULL) {
488         
489                 msg = CtdlFetchMessage(msgnum);
490                 if (msg != NULL) {
491                         size_t newpath_len;
492
493                         /* Prepend our node name to the Path field whenever
494                          * sending a message to another IGnet node
495                          */
496                         if (msg->cm_fields['P'] == NULL) {
497                                 msg->cm_fields['P'] = strdoop("username");
498                         }
499                         newpath_len = strlen(msg->cm_fields['P']) +
500                                  strlen(config.c_nodename) + 2;
501                         newpath = mallok(newpath_len);
502                         snprintf(newpath, newpath_len, "%s!%s",
503                                  config.c_nodename, msg->cm_fields['P']);
504                         phree(msg->cm_fields['P']);
505                         msg->cm_fields['P'] = newpath;
506
507                         /*
508                          * Force the message to appear in the correct room
509                          * on the far end by setting the C field correctly
510                          */
511                         if (msg->cm_fields['C'] != NULL) {
512                                 phree(msg->cm_fields['C']);
513                         }
514                         msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
515
516                         /*
517                          * Determine if this message is set to be deleted
518                          * after sending out on the network
519                          */
520                         if (msg->cm_fields['S'] != NULL) {
521                                 if (!strcasecmp(msg->cm_fields['S'],
522                                    "CANCEL")) {
523                                         delete_after_send = 1;
524                                 }
525                         }
526
527                         /* 
528                          * Now serialize it for transmission
529                          */
530                         serialize_message(&sermsg, msg);
531
532                         /* Now send it to every node */
533                         for (nptr = sc->ignet_push_shares; nptr != NULL;
534                             nptr = nptr->next) {
535
536                                 send = 1;
537
538                                 /* Check for valid node name */
539                                 if (is_valid_node(NULL,NULL,nptr->name) != 0) {
540                                         lprintf(3, "Invalid node <%s>\n",
541                                                 nptr->name);
542                                         send = 0;
543                                 }
544
545                                 /* Check for split horizon */
546                                 lprintf(9, "Path is %s\n", msg->cm_fields['P']);
547                                 bang = num_tokens(msg->cm_fields['P'], '!');
548                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
549                                         extract_token(buf, msg->cm_fields['P'],
550                                                 i, '!');
551                                         if (!strcasecmp(buf, nptr->name)) {
552                                                 send = 0;
553                                         }
554                                 }
555
556                                 /* Send the message */
557                                 if (send == 1) {
558                                         snprintf(filename, sizeof filename,
559                                                 "./network/spoolout/%s",
560                                                 nptr->name);
561                                         fp = fopen(filename, "ab");
562                                         if (fp != NULL) {
563                                                 fwrite(sermsg.ser,
564                                                         sermsg.len, 1, fp);
565                                                 fclose(fp);
566                                         }
567                                 }
568                         }
569                         phree(sermsg.ser);
570                         CtdlFreeMessage(msg);
571                 }
572         }
573
574         /* update lastsent */
575         sc->lastsent = msgnum;
576
577         /* Delete this message if delete-after-send is set */
578         if (delete_after_send) {
579                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
580         }
581
582 }
583         
584
585 /*
586  * Deliver digest messages
587  */
588 void network_deliver_digest(struct SpoolControl *sc) {
589         char buf[SIZ];
590         int i;
591         struct CtdlMessage *msg;
592         long msglen;
593         long msgnum;
594         char *instr = NULL;
595         size_t instr_len = SIZ;
596         struct CtdlMessage *imsg;
597         struct namelist *nptr;
598
599         if (sc->num_msgs_spooled < 1) {
600                 fclose(sc->digestfp);
601                 sc->digestfp = NULL;
602                 return;
603         }
604
605         msg = mallok(sizeof(struct CtdlMessage));
606         memset(msg, 0, sizeof(struct CtdlMessage));
607         msg->cm_magic = CTDLMESSAGE_MAGIC;
608         msg->cm_format_type = FMT_RFC822;
609         msg->cm_anon_type = MES_NORMAL;
610
611         sprintf(buf, "%ld", time(NULL));
612         msg->cm_fields['T'] = strdoop(buf);
613         msg->cm_fields['A'] = strdoop(CC->quickroom.QRname);
614         msg->cm_fields['U'] = strdoop(CC->quickroom.QRname);
615         sprintf(buf, "room_%s@%s", CC->quickroom.QRname, config.c_fqdn);
616         for (i=0; i<strlen(buf); ++i) {
617                 if (isspace(buf[i])) buf[i]='_';
618                 buf[i] = tolower(buf[i]);
619         }
620         msg->cm_fields['F'] = strdoop(buf);
621
622         fseek(sc->digestfp, 0L, SEEK_END);
623         msglen = ftell(sc->digestfp);
624
625         msg->cm_fields['M'] = mallok(msglen + 1);
626         fseek(sc->digestfp, 0L, SEEK_SET);
627         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
628         msg->cm_fields['M'][msglen] = 0;
629
630         fclose(sc->digestfp);
631         sc->digestfp = NULL;
632
633         msgnum = CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
634         CtdlFreeMessage(msg);
635
636         /* Now generate the delivery instructions */
637
638         /* 
639          * Figure out how big a buffer we need to allocate
640          */
641         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
642                 instr_len = instr_len + strlen(nptr->name);
643         }
644         
645         /*
646          * allocate...
647          */
648         lprintf(9, "Generating delivery instructions\n");
649         instr = mallok(instr_len);
650         if (instr == NULL) {
651                 lprintf(1, "Cannot allocate %ld bytes for instr...\n",
652                         (long)instr_len);
653                 abort();
654         }
655         snprintf(instr, instr_len,
656                 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
657                 "bounceto|postmaster@%s\n" ,
658                 SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn );
659
660         /* Generate delivery instructions for each recipient */
661         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
662                 size_t tmp = strlen(instr);
663                 snprintf(&instr[tmp], instr_len - tmp,
664                          "remote|%s|0||\n", nptr->name);
665         }
666
667         /*
668          * Generate a message from the instructions
669          */
670         imsg = mallok(sizeof(struct CtdlMessage));
671         memset(imsg, 0, sizeof(struct CtdlMessage));
672         imsg->cm_magic = CTDLMESSAGE_MAGIC;
673         imsg->cm_anon_type = MES_NORMAL;
674         imsg->cm_format_type = FMT_RFC822;
675         imsg->cm_fields['A'] = strdoop("Citadel");
676         imsg->cm_fields['M'] = instr;
677
678         /* Save delivery instructions in spoolout room */
679         CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
680         CtdlFreeMessage(imsg);
681 }
682
683
684 /*
685  * Batch up and send all outbound traffic from the current room
686  */
687 void network_spoolout_room(char *room_to_spool) {
688         char filename[SIZ];
689         char buf[SIZ];
690         char instr[SIZ];
691         FILE *fp;
692         struct SpoolControl sc;
693         struct namelist *nptr;
694         size_t miscsize = 0;
695         size_t linesize = 0;
696         int skipthisline = 0;
697
698         lprintf(7, "Spooling <%s>\n", room_to_spool);
699         if (getroom(&CC->quickroom, room_to_spool) != 0) {
700                 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
701                 return;
702         }
703
704         memset(&sc, 0, sizeof(struct SpoolControl));
705         assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs");
706
707         begin_critical_section(S_NETCONFIGS);
708         end_critical_section(S_NETCONFIGS);
709
710         fp = fopen(filename, "r");
711         if (fp == NULL) {
712                 lprintf(7, "Outbound batch processing skipped for <%s>\n",
713                         CC->quickroom.QRname);
714                 end_critical_section(S_NETCONFIGS);
715                 return;
716         }
717
718         lprintf(5, "Outbound batch processing started for <%s>\n",
719                 CC->quickroom.QRname);
720
721         while (fgets(buf, sizeof buf, fp) != NULL) {
722                 buf[strlen(buf)-1] = 0;
723
724                 extract(instr, buf, 0);
725                 if (!strcasecmp(instr, "lastsent")) {
726                         sc.lastsent = extract_long(buf, 1);
727                 }
728                 else if (!strcasecmp(instr, "listrecp")) {
729                         nptr = (struct namelist *)
730                                 mallok(sizeof(struct namelist));
731                         nptr->next = sc.listrecps;
732                         extract(nptr->name, buf, 1);
733                         sc.listrecps = nptr;
734                 }
735                 else if (!strcasecmp(instr, "digestrecp")) {
736                         nptr = (struct namelist *)
737                                 mallok(sizeof(struct namelist));
738                         nptr->next = sc.digestrecps;
739                         extract(nptr->name, buf, 1);
740                         sc.digestrecps = nptr;
741                 }
742                 else if (!strcasecmp(instr, "ignet_push_share")) {
743                         nptr = (struct namelist *)
744                                 mallok(sizeof(struct namelist));
745                         nptr->next = sc.ignet_push_shares;
746                         extract(nptr->name, buf, 1);
747                         sc.ignet_push_shares = nptr;
748                 }
749                 else {
750                         /* Preserve 'other' lines ... *unless* they happen to
751                          * be subscribe/unsubscribe pendings with expired
752                          * timestamps.
753                          */
754                         skipthisline = 0;
755                         if (!strncasecmp(buf, "subpending|", 11)) {
756                                 if (time(NULL) - extract_long(buf, 4) > EXP) {
757                                         skipthisline = 1;
758                                 }
759                         }
760                         if (!strncasecmp(buf, "unsubpending|", 13)) {
761                                 if (time(NULL) - extract_long(buf, 3) > EXP) {
762                                         skipthisline = 1;
763                                 }
764                         }
765
766                         if (skipthisline == 0) {
767                                 linesize = strlen(buf);
768                                 sc.misc = realloc(sc.misc,
769                                         (miscsize + linesize + 2) );
770                                 sprintf(&sc.misc[miscsize], "%s\n", buf);
771                                 miscsize = miscsize + linesize + 1;
772                         }
773                 }
774
775
776         }
777         fclose(fp);
778
779         /* If there are digest recipients, we have to build a digest */
780         if (sc.digestrecps != NULL) {
781                 sc.digestfp = tmpfile();
782                 fprintf(sc.digestfp, "Content-type: text/plain\n\n");
783         }
784
785         /* Do something useful */
786         CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL,
787                 network_spool_msg, &sc);
788
789         /* If we wrote a digest, deliver it and then close it */
790         if (sc.digestfp != NULL) {
791                 fprintf(sc.digestfp,    " -----------------------------------"
792                                         "------------------------------------"
793                                         "-------\n"
794                                         "You are subscribed to the '%s' "
795                                         "list.\n",
796                                         CC->quickroom.QRname
797                 );
798                 network_deliver_digest(&sc);    /* deliver and close */
799         }
800
801         /* Now rewrite the config file */
802         fp = fopen(filename, "w");
803         if (fp == NULL) {
804                 lprintf(1, "ERROR: cannot open %s: %s\n",
805                         filename, strerror(errno));
806         }
807         else {
808                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
809
810                 /* Write out the listrecps while freeing from memory at the
811                  * same time.  Am I clever or what?  :)
812                  */
813                 while (sc.listrecps != NULL) {
814                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
815                         nptr = sc.listrecps->next;
816                         phree(sc.listrecps);
817                         sc.listrecps = nptr;
818                 }
819                 /* Do the same for digestrecps */
820                 while (sc.digestrecps != NULL) {
821                         fprintf(fp, "digestrecp|%s\n", sc.digestrecps->name);
822                         nptr = sc.digestrecps->next;
823                         phree(sc.digestrecps);
824                         sc.digestrecps = nptr;
825                 }
826                 while (sc.ignet_push_shares != NULL) {
827                         fprintf(fp, "ignet_push_share|%s\n",
828                                 sc.ignet_push_shares->name);
829                         nptr = sc.ignet_push_shares->next;
830                         phree(sc.ignet_push_shares);
831                         sc.ignet_push_shares = nptr;
832                 }
833                 if (sc.misc != NULL) {
834                         fwrite(sc.misc, strlen(sc.misc), 1, fp);
835                 }
836                 phree(sc.misc);
837
838                 fclose(fp);
839         }
840         end_critical_section(S_NETCONFIGS);
841
842         lprintf(5, "Outbound batch processing finished for <%s>\n",
843                 CC->quickroom.QRname);
844 }
845
846
847 /*
848  * Batch up and send all outbound traffic from the current room
849  */
850 void network_queue_room(struct quickroom *qrbuf, void *data) {
851         struct RoomProcList *ptr;
852
853         ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
854         if (ptr == NULL) return;
855
856         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
857         ptr->next = rplist;
858         rplist = ptr;
859 }
860
861
862 /*
863  * Learn topology from path fields
864  */
865 void network_learn_topology(char *node, char *path) {
866         char nexthop[SIZ];
867         struct NetMap *nmptr;
868
869         strcpy(nexthop, "");
870
871         if (num_tokens(path, '!') < 3) return;
872         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
873                 if (!strcasecmp(nmptr->nodename, node)) {
874                         extract_token(nmptr->nexthop, path, 0, '!');
875                         nmptr->lastcontact = time(NULL);
876                         return;
877                 }
878         }
879
880         /* If we got here then it's not in the map, so add it. */
881         nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
882         strcpy(nmptr->nodename, node);
883         nmptr->lastcontact = time(NULL);
884         extract_token(nmptr->nexthop, path, 0, '!');
885         nmptr->next = the_netmap;
886         the_netmap = nmptr;
887 }
888
889
890
891
892 /*
893  * Bounce a message back to the sender
894  */
895 void network_bounce(struct CtdlMessage *msg, char *reason) {
896         char *oldpath = NULL;
897         char buf[SIZ];
898         char bouncesource[SIZ];
899         char recipient[SIZ];
900         struct recptypes *valid = NULL;
901         char force_room[ROOMNAMELEN];
902         static int serialnum = 0;
903         size_t size;
904
905         lprintf(9, "entering network_bounce()\n");
906
907         if (msg == NULL) return;
908
909         snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
910
911         /* 
912          * Give it a fresh message ID
913          */
914         if (msg->cm_fields['I'] != NULL) {
915                 phree(msg->cm_fields['I']);
916         }
917         snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s",
918                 (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn);
919         msg->cm_fields['I'] = strdoop(buf);
920
921         /*
922          * FIXME ... right now we're just sending a bounce; we really want to
923          * include the text of the bounced message.
924          */
925         if (msg->cm_fields['M'] != NULL) {
926                 phree(msg->cm_fields['M']);
927         }
928         msg->cm_fields['M'] = strdoop(reason);
929         msg->cm_format_type = 0;
930
931         /*
932          * Turn the message around
933          */
934         if (msg->cm_fields['R'] == NULL) {
935                 phree(msg->cm_fields['R']);
936         }
937
938         if (msg->cm_fields['D'] == NULL) {
939                 phree(msg->cm_fields['D']);
940         }
941
942         snprintf(recipient, sizeof recipient, "%s@%s",
943                 msg->cm_fields['A'], msg->cm_fields['N']);
944
945         if (msg->cm_fields['A'] == NULL) {
946                 phree(msg->cm_fields['A']);
947         }
948
949         if (msg->cm_fields['N'] == NULL) {
950                 phree(msg->cm_fields['N']);
951         }
952
953         msg->cm_fields['A'] = strdoop(BOUNCESOURCE);
954         msg->cm_fields['N'] = strdoop(config.c_nodename);
955         
956
957         /* prepend our node to the path */
958         if (msg->cm_fields['P'] != NULL) {
959                 oldpath = msg->cm_fields['P'];
960                 msg->cm_fields['P'] = NULL;
961         }
962         else {
963                 oldpath = strdoop("unknown_user");
964         }
965         size = strlen(oldpath) + SIZ;
966         msg->cm_fields['P'] = mallok(size);
967         snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath);
968         phree(oldpath);
969
970         /* Now submit the message */
971         valid = validate_recipients(recipient);
972         if (valid != NULL) if (valid->num_error > 0) {
973                 phree(valid);
974                 valid = NULL;
975         }
976         if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
977                 strcpy(force_room, config.c_aideroom);
978         }
979         else {
980                 strcpy(force_room, "");
981         }
982         if ( (valid == NULL) && (strlen(force_room) == 0) ) {
983                 strcpy(force_room, config.c_aideroom);
984         }
985         CtdlSubmitMsg(msg, valid, force_room);
986
987         /* Clean up */
988         if (valid != NULL) phree(valid);
989         CtdlFreeMessage(msg);
990         lprintf(9, "leaving network_bounce()\n");
991 }
992
993
994
995
996 /*
997  * Process a buffer containing a single message from a single file
998  * from the inbound queue 
999  */
1000 void network_process_buffer(char *buffer, long size) {
1001         struct CtdlMessage *msg;
1002         long pos;
1003         int field;
1004         struct recptypes *recp = NULL;
1005         char target_room[ROOMNAMELEN];
1006         struct ser_ret sermsg;
1007         char *oldpath = NULL;
1008         char filename[SIZ];
1009         FILE *fp;
1010         char buf[SIZ];
1011
1012         /* Set default target room to trash */
1013         strcpy(target_room, TWITROOM);
1014
1015         /* Load the message into memory */
1016         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1017         memset(msg, 0, sizeof(struct CtdlMessage));
1018         msg->cm_magic = CTDLMESSAGE_MAGIC;
1019         msg->cm_anon_type = buffer[1];
1020         msg->cm_format_type = buffer[2];
1021
1022         for (pos = 3; pos < size; ++pos) {
1023                 field = buffer[pos];
1024                 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
1025                 pos = pos + strlen(&buffer[(int)pos]);
1026         }
1027
1028         /* Check for message routing */
1029         if (msg->cm_fields['D'] != NULL) {
1030                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
1031
1032                         /* route the message */
1033                         if (is_valid_node(NULL, NULL,
1034                            msg->cm_fields['D']) == 0) {
1035
1036                                 /* prepend our node to the path */
1037                                 if (msg->cm_fields['P'] != NULL) {
1038                                         oldpath = msg->cm_fields['P'];
1039                                         msg->cm_fields['P'] = NULL;
1040                                 }
1041                                 else {
1042                                         oldpath = strdoop("unknown_user");
1043                                 }
1044                                 size = strlen(oldpath) + SIZ;
1045                                 msg->cm_fields['P'] = mallok(size);
1046                                 snprintf(msg->cm_fields['P'], size, "%s!%s",
1047                                         config.c_nodename, oldpath);
1048                                 phree(oldpath);
1049
1050                                 /* serialize the message */
1051                                 serialize_message(&sermsg, msg);
1052
1053                                 /* now send it */
1054                                 snprintf(filename, sizeof filename,
1055                                         "./network/spoolout/%s",
1056                                         msg->cm_fields['D']);
1057                                 fp = fopen(filename, "ab");
1058                                 if (fp != NULL) {
1059                                         fwrite(sermsg.ser,
1060                                                 sermsg.len, 1, fp);
1061                                         fclose(fp);
1062                                 }
1063                                 phree(sermsg.ser);
1064                                 CtdlFreeMessage(msg);
1065                                 return;
1066                         }
1067                         
1068                         else {  /* invalid destination node name */
1069
1070                                 network_bounce(msg,
1071 "A message you sent could not be delivered due to an invalid destination node"
1072 " name.  Please check the address and try sending the message again.\n");
1073                                 msg = NULL;
1074                                 return;
1075
1076                         }
1077                 }
1078         }
1079
1080         /*
1081          * Check to see if we already have a copy of this message
1082          */
1083         if (network_usetable(msg) != 0) {
1084                 snprintf(buf, sizeof buf,
1085                         "Loopzapper rejected message <%s> "
1086                         "from <%s> in <%s> @ <%s>\n",
1087                         ((msg->cm_fields['I']!=NULL)?(msg->cm_fields['I']):""),
1088                         ((msg->cm_fields['A']!=NULL)?(msg->cm_fields['A']):""),
1089                         ((msg->cm_fields['O']!=NULL)?(msg->cm_fields['O']):""),
1090                         ((msg->cm_fields['N']!=NULL)?(msg->cm_fields['N']):"")
1091                 );
1092                 aide_message(buf);
1093                 CtdlFreeMessage(msg);
1094                 msg = NULL;
1095                 return;
1096         }
1097
1098         /* Learn network topology from the path */
1099         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
1100                 network_learn_topology(msg->cm_fields['N'], 
1101                                         msg->cm_fields['P']);
1102         }
1103
1104         /* Does it have a recipient?  If so, validate it... */
1105         if (msg->cm_fields['R'] != NULL) {
1106                 recp = validate_recipients(msg->cm_fields['R']);
1107                 if (recp != NULL) if (recp->num_error > 0) {
1108                         network_bounce(msg,
1109 "A message you sent could not be delivered due to an invalid address.\n"
1110 "Please check the address and try sending the message again.\n");
1111                         msg = NULL;
1112                         phree(recp);
1113                         return;
1114                 }
1115                 strcpy(target_room, "");        /* no target room if mail */
1116         }
1117
1118         else if (msg->cm_fields['C'] != NULL) {
1119                 safestrncpy(target_room,
1120                         msg->cm_fields['C'],
1121                         sizeof target_room);
1122         }
1123
1124         else if (msg->cm_fields['O'] != NULL) {
1125                 safestrncpy(target_room,
1126                         msg->cm_fields['O'],
1127                         sizeof target_room);
1128         }
1129
1130         /* Strip out fields that are only relevant during transit */
1131         if (msg->cm_fields['D'] != NULL) {
1132                 phree(msg->cm_fields['D']);
1133                 msg->cm_fields['D'] = NULL;
1134         }
1135         if (msg->cm_fields['C'] != NULL) {
1136                 phree(msg->cm_fields['C']);
1137                 msg->cm_fields['C'] = NULL;
1138         }
1139
1140         /* save the message into a room */
1141         if (PerformNetprocHooks(msg, target_room) == 0) {
1142                 msg->cm_flags = CM_SKIP_HOOKS;
1143                 CtdlSubmitMsg(msg, recp, target_room);
1144         }
1145         CtdlFreeMessage(msg);
1146         phree(recp);
1147 }
1148
1149
1150 /*
1151  * Process a single message from a single file from the inbound queue 
1152  */
1153 void network_process_message(FILE *fp, long msgstart, long msgend) {
1154         long hold_pos;
1155         long size;
1156         char *buffer;
1157
1158         hold_pos = ftell(fp);
1159         size = msgend - msgstart + 1;
1160         buffer = mallok(size);
1161         if (buffer != NULL) {
1162                 fseek(fp, msgstart, SEEK_SET);
1163                 fread(buffer, size, 1, fp);
1164                 network_process_buffer(buffer, size);
1165                 phree(buffer);
1166         }
1167
1168         fseek(fp, hold_pos, SEEK_SET);
1169 }
1170
1171
1172 /*
1173  * Process a single file from the inbound queue 
1174  */
1175 void network_process_file(char *filename) {
1176         FILE *fp;
1177         long msgstart = (-1L);
1178         long msgend = (-1L);
1179         long msgcur = 0L;
1180         int ch;
1181
1182         lprintf(7, "network: processing <%s>\n", filename);
1183
1184         fp = fopen(filename, "rb");
1185         if (fp == NULL) {
1186                 lprintf(5, "Error opening %s: %s\n",
1187                         filename, strerror(errno));
1188                 return;
1189         }
1190
1191         /* Look for messages in the data stream and break them out */
1192         while (ch = getc(fp), ch >= 0) {
1193         
1194                 if (ch == 255) {
1195                         if (msgstart >= 0L) {
1196                                 msgend = msgcur - 1;
1197                                 network_process_message(fp, msgstart, msgend);
1198                         }
1199                         msgstart = msgcur;
1200                 }
1201
1202                 ++msgcur;
1203         }
1204
1205         msgend = msgcur - 1;
1206         if (msgstart >= 0L) {
1207                 network_process_message(fp, msgstart, msgend);
1208         }
1209
1210         fclose(fp);
1211         unlink(filename);
1212 }
1213
1214
1215 /*
1216  * Process anything in the inbound queue
1217  */
1218 void network_do_spoolin(void) {
1219         DIR *dp;
1220         struct dirent *d;
1221         char filename[SIZ];
1222
1223         dp = opendir("./network/spoolin");
1224         if (dp == NULL) return;
1225
1226         while (d = readdir(dp), d != NULL) {
1227                 snprintf(filename, sizeof filename, "./network/spoolin/%s", d->d_name);
1228                 network_process_file(filename);
1229         }
1230
1231
1232         closedir(dp);
1233 }
1234
1235
1236
1237
1238
1239 /*
1240  * receive network spool from the remote system
1241  */
1242 void receive_spool(int sock, char *remote_nodename) {
1243         long download_len;
1244         long bytes_received;
1245         char buf[SIZ];
1246         static char pbuf[IGNET_PACKET_SIZE];
1247         char tempfilename[PATH_MAX];
1248         long plen;
1249         FILE *fp;
1250
1251         strcpy(tempfilename, tmpnam(NULL));
1252         if (sock_puts(sock, "NDOP") < 0) return;
1253         if (sock_gets(sock, buf) < 0) return;
1254         lprintf(9, "<%s\n", buf);
1255         if (buf[0] != '2') {
1256                 return;
1257         }
1258         download_len = extract_long(&buf[4], 0);
1259
1260         bytes_received = 0L;
1261         fp = fopen(tempfilename, "w");
1262         if (fp == NULL) {
1263                 lprintf(9, "cannot open download file locally: %s\n",
1264                         strerror(errno));
1265                 return;
1266         }
1267
1268         while (bytes_received < download_len) {
1269                 snprintf(buf, sizeof buf, "READ %ld|%ld",
1270                         bytes_received,
1271                      ((download_len - bytes_received > IGNET_PACKET_SIZE)
1272                  ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
1273                 if (sock_puts(sock, buf) < 0) {
1274                         fclose(fp);
1275                         unlink(tempfilename);
1276                         return;
1277                 }
1278                 if (sock_gets(sock, buf) < 0) {
1279                         fclose(fp);
1280                         unlink(tempfilename);
1281                         return;
1282                 }
1283                 if (buf[0] == '6') {
1284                         plen = extract_long(&buf[4], 0);
1285                         if (sock_read(sock, pbuf, plen) < 0) {
1286                                 fclose(fp);
1287                                 unlink(tempfilename);
1288                                 return;
1289                         }
1290                         fwrite((char *) pbuf, plen, 1, fp);
1291                         bytes_received = bytes_received + plen;
1292                 }
1293         }
1294
1295         fclose(fp);
1296         if (sock_puts(sock, "CLOS") < 0) {
1297                 unlink(tempfilename);
1298                 return;
1299         }
1300         if (sock_gets(sock, buf) < 0) {
1301                 unlink(tempfilename);
1302                 return;
1303         }
1304         lprintf(9, "%s\n", buf);
1305         snprintf(buf, sizeof buf, "mv %s ./network/spoolin/%s.%ld",
1306                 tempfilename, remote_nodename, (long) getpid());
1307         system(buf);
1308 }
1309
1310
1311
1312 /*
1313  * transmit network spool to the remote system
1314  */
1315 void transmit_spool(int sock, char *remote_nodename)
1316 {
1317         char buf[SIZ];
1318         char pbuf[4096];
1319         long plen;
1320         long bytes_to_write, thisblock;
1321         int fd;
1322         char sfname[128];
1323
1324         if (sock_puts(sock, "NUOP") < 0) return;
1325         if (sock_gets(sock, buf) < 0) return;
1326         lprintf(9, "<%s\n", buf);
1327         if (buf[0] != '2') {
1328                 return;
1329         }
1330
1331         snprintf(sfname, sizeof sfname, "./network/spoolout/%s", remote_nodename);
1332         fd = open(sfname, O_RDONLY);
1333         if (fd < 0) {
1334                 if (errno == ENOENT) {
1335                         lprintf(9, "Nothing to send.\n");
1336                 } else {
1337                         lprintf(5, "cannot open upload file locally: %s\n",
1338                                 strerror(errno));
1339                 }
1340                 return;
1341         }
1342         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1343                 bytes_to_write = plen;
1344                 while (bytes_to_write > 0L) {
1345                         snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
1346                         if (sock_puts(sock, buf) < 0) {
1347                                 close(fd);
1348                                 return;
1349                         }
1350                         if (sock_gets(sock, buf) < 0) {
1351                                 close(fd);
1352                                 return;
1353                         }
1354                         thisblock = atol(&buf[4]);
1355                         if (buf[0] == '7') {
1356                                 if (sock_write(sock, pbuf,
1357                                    (int) thisblock) < 0) {
1358                                         close(fd);
1359                                         return;
1360                                 }
1361                                 bytes_to_write = bytes_to_write - thisblock;
1362                         } else {
1363                                 goto ABORTUPL;
1364                         }
1365                 }
1366         }
1367
1368 ABORTUPL:
1369         close(fd);
1370         if (sock_puts(sock, "UCLS 1") < 0) return;
1371         if (sock_gets(sock, buf) < 0) return;
1372         lprintf(9, "<%s\n", buf);
1373         if (buf[0] == '2') {
1374                 unlink(sfname);
1375         }
1376 }
1377
1378
1379
1380 /*
1381  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1382  */
1383 void network_poll_node(char *node, char *secret, char *host, char *port) {
1384         int sock;
1385         char buf[SIZ];
1386
1387         if (network_talking_to(node, NTT_CHECK)) return;
1388         network_talking_to(node, NTT_ADD);
1389         lprintf(5, "Polling node <%s> at %s:%s\n", node, host, port);
1390
1391         sock = sock_connect(host, port, "tcp");
1392         if (sock < 0) {
1393                 lprintf(7, "Could not connect: %s\n", strerror(errno));
1394                 network_talking_to(node, NTT_REMOVE);
1395                 return;
1396         }
1397         
1398         lprintf(9, "Connected!\n");
1399
1400         /* Read the server greeting */
1401         if (sock_gets(sock, buf) < 0) goto bail;
1402         lprintf(9, ">%s\n", buf);
1403
1404         /* Identify ourselves */
1405         snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
1406         lprintf(9, "<%s\n", buf);
1407         if (sock_puts(sock, buf) <0) goto bail;
1408         if (sock_gets(sock, buf) < 0) goto bail;
1409         lprintf(9, ">%s\n", buf);
1410         if (buf[0] != '2') goto bail;
1411
1412         /* At this point we are authenticated. */
1413         receive_spool(sock, node);
1414         transmit_spool(sock, node);
1415
1416         sock_puts(sock, "QUIT");
1417 bail:   sock_close(sock);
1418         network_talking_to(node, NTT_REMOVE);
1419 }
1420
1421
1422
1423 /*
1424  * Poll other Citadel nodes and transfer inbound/outbound network data.
1425  */
1426 void network_poll_other_citadel_nodes(void) {
1427         char *ignetcfg = NULL;
1428         int i;
1429         char linebuf[SIZ];
1430         char node[SIZ];
1431         char host[SIZ];
1432         char port[SIZ];
1433         char secret[SIZ];
1434
1435         ignetcfg = CtdlGetSysConfig(IGNETCFG);
1436         if (ignetcfg == NULL) return;   /* no nodes defined */
1437
1438         /* Use the string tokenizer to grab one line at a time */
1439         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
1440                 extract_token(linebuf, ignetcfg, i, '\n');
1441                 extract(node, linebuf, 0);
1442                 extract(secret, linebuf, 1);
1443                 extract(host, linebuf, 2);
1444                 extract(port, linebuf, 3);
1445                 if ( (strlen(node) > 0) && (strlen(secret) > 0) 
1446                    && (strlen(host) > 0) && strlen(port) > 0) {
1447                         network_poll_node(node, secret, host, port);
1448                 }
1449         }
1450
1451         phree(ignetcfg);
1452 }
1453
1454
1455
1456
1457
1458
1459
1460 /*
1461  * network_do_queue()
1462  * 
1463  * Run through the rooms doing various types of network stuff.
1464  */
1465 void network_do_queue(void) {
1466         static int doing_queue = 0;
1467         static time_t last_run = 0L;
1468         struct RoomProcList *ptr;
1469
1470         /*
1471          * Run no more frequently than once every n seconds
1472          */
1473         if ( (time(NULL) - last_run) < config.c_net_freq ) return;
1474
1475         /*
1476          * This is a simple concurrency check to make sure only one queue run
1477          * is done at a time.  We could do this with a mutex, but since we
1478          * don't really require extremely fine granularity here, we'll do it
1479          * with a static variable instead.
1480          */
1481         if (doing_queue) return;
1482         doing_queue = 1;
1483         last_run = time(NULL);
1484
1485         /*
1486          * Poll other Citadel nodes.
1487          */
1488         network_poll_other_citadel_nodes();
1489
1490         /*
1491          * Load the network map and filter list into memory.
1492          */
1493         read_network_map();
1494         filterlist = load_filter_list();
1495
1496         /* 
1497          * Go ahead and run the queue
1498          */
1499         lprintf(7, "network: loading outbound queue\n");
1500         ForEachRoom(network_queue_room, NULL);
1501
1502         lprintf(7, "network: running outbound queue\n");
1503         while (rplist != NULL) {
1504                 network_spoolout_room(rplist->name);
1505                 ptr = rplist;
1506                 rplist = rplist->next;
1507                 phree(ptr);
1508         }
1509
1510         lprintf(7, "network: processing inbound queue\n");
1511         network_do_spoolin();
1512
1513         /* Save the network map back to disk */
1514         write_network_map();
1515
1516         /* Free the filter list in memory */
1517         free_filter_list(filterlist);
1518         filterlist = NULL;
1519
1520         lprintf(7, "network: queue run completed\n");
1521         doing_queue = 0;
1522 }
1523
1524
1525 /*
1526  * cmd_netp() - authenticate to the server as another Citadel node polling
1527  *              for network traffic
1528  */
1529 void cmd_netp(char *cmdbuf)
1530 {
1531         char node[SIZ];
1532         char pass[SIZ];
1533
1534         char secret[SIZ];
1535         char nexthop[SIZ];
1536
1537         extract(node, cmdbuf, 0);
1538         extract(pass, cmdbuf, 1);
1539
1540         if (is_valid_node(nexthop, secret, node) != 0) {
1541                 cprintf("%d authentication failed\n", ERROR);
1542                 return;
1543         }
1544
1545         if (strcasecmp(pass, secret)) {
1546                 cprintf("%d authentication failed\n", ERROR);
1547                 return;
1548         }
1549
1550         if (network_talking_to(node, NTT_CHECK)) {
1551                 cprintf("%d Already talking to %s right now\n", ERROR, node);
1552                 return;
1553         }
1554
1555         safestrncpy(CC->net_node, node, sizeof CC->net_node);
1556         network_talking_to(node, NTT_ADD);
1557         cprintf("%d authenticated as network node '%s'\n", CIT_OK,
1558                 CC->net_node);
1559 }
1560
1561
1562
1563
1564
1565 /*
1566  * Module entry point
1567  */
1568 char *Dynamic_Module_Init(void)
1569 {
1570         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1571         CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1572         CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1573         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
1574         return "$Id$";
1575 }