Implemented RFC2919, the 'List-ID' field for mailing
[citadel.git] / citadel / modules / network / 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-2005 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  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
21  * requests that have not been confirmed will be deleted.
22  */
23 #define EXP     259200  /* three days */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <signal.h>
32 #include <pwd.h>
33 #include <errno.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <dirent.h>
37 #if TIME_WITH_SYS_TIME
38 # include <sys/time.h>
39 # include <time.h>
40 #else
41 # if HAVE_SYS_TIME_H
42 #  include <sys/time.h>
43 # else
44 #  include <time.h>
45 # endif
46 #endif
47
48 #include <sys/wait.h>
49 #include <string.h>
50 #include <limits.h>
51 #include <libcitadel.h>
52 #include "citadel.h"
53 #include "server.h"
54 #include "citserver.h"
55 #include "support.h"
56 #include "config.h"
57 #include "room_ops.h"
58 #include "user_ops.h"
59 #include "policy.h"
60 #include "database.h"
61 #include "msgbase.h"
62 #include "internet_addressing.h"
63 #include "serv_network.h"
64 #include "clientsocket.h"
65 #include "file_ops.h"
66 #include "citadel_dirs.h"
67 #include "threads.h"
68
69 #ifndef HAVE_SNPRINTF
70 #include "snprintf.h"
71 #endif
72
73
74 #include "ctdl_module.h"
75
76
77
78 /* Nonzero while we are doing network processing */
79 static int doing_queue = 0;
80
81 /*
82  * When we do network processing, it's accomplished in two passes; one to
83  * gather a list of rooms and one to actually do them.  It's ok that rplist
84  * is global; we have a mutex that keeps it safe.
85  */
86 struct RoomProcList *rplist = NULL;
87
88 /*
89  * We build a map of network nodes during processing.
90  */
91 NetMap *the_netmap = NULL;
92 int netmap_changed = 0;
93 char *working_ignetcfg = NULL;
94
95 /*
96  * Load or refresh the Citadel network (IGnet) configuration for this node.
97  */
98 void load_working_ignetcfg(void) {
99         char *cfg;
100         char *oldcfg;
101
102         cfg = CtdlGetSysConfig(IGNETCFG);
103         if (cfg == NULL) {
104                 cfg = strdup("");
105         }
106
107         oldcfg = working_ignetcfg;
108         working_ignetcfg = cfg;
109         if (oldcfg != NULL) {
110                 free(oldcfg);
111         }
112 }
113
114
115
116
117
118 /*
119  * Keep track of what messages to reject
120  */
121 FilterList *load_filter_list(void) {
122         char *serialized_list = NULL;
123         int i;
124         char buf[SIZ];
125         FilterList *newlist = NULL;
126         FilterList *nptr;
127
128         serialized_list = CtdlGetSysConfig(FILTERLIST);
129         if (serialized_list == NULL) return(NULL); /* if null, no entries */
130
131         /* Use the string tokenizer to grab one line at a time */
132         for (i=0; i<num_tokens(serialized_list, '\n'); ++i) {
133                 extract_token(buf, serialized_list, i, '\n', sizeof buf);
134                 nptr = (FilterList *) malloc(sizeof(FilterList));
135                 extract_token(nptr->fl_user, buf, 0, '|', sizeof nptr->fl_user);
136                 striplt(nptr->fl_user);
137                 extract_token(nptr->fl_room, buf, 1, '|', sizeof nptr->fl_room);
138                 striplt(nptr->fl_room);
139                 extract_token(nptr->fl_node, buf, 2, '|', sizeof nptr->fl_node);
140                 striplt(nptr->fl_node);
141
142                 /* Cowardly refuse to add an any/any/any entry that would
143                  * end up filtering every single message.
144                  */
145                 if (IsEmptyStr(nptr->fl_user) && 
146                     IsEmptyStr(nptr->fl_room) &&
147                     IsEmptyStr(nptr->fl_node)) {
148                         free(nptr);
149                 }
150                 else {
151                         nptr->next = newlist;
152                         newlist = nptr;
153                 }
154         }
155
156         free(serialized_list);
157         return newlist;
158 }
159
160
161 void free_filter_list(FilterList *fl) {
162         if (fl == NULL) return;
163         free_filter_list(fl->next);
164         free(fl);
165 }
166
167
168
169 /*
170  * Check the use table.  This is a list of messages which have recently
171  * arrived on the system.  It is maintained and queried to prevent the same
172  * message from being entered into the database multiple times if it happens
173  * to arrive multiple times by accident.
174  */
175 int network_usetable(struct CtdlMessage *msg) {
176
177         char msgid[SIZ];
178         struct cdbdata *cdbut;
179         struct UseTable ut;
180
181         /* Bail out if we can't generate a message ID */
182         if (msg == NULL) {
183                 return(0);
184         }
185         if (msg->cm_fields['I'] == NULL) {
186                 return(0);
187         }
188         if (IsEmptyStr(msg->cm_fields['I'])) {
189                 return(0);
190         }
191
192         /* Generate the message ID */
193         strcpy(msgid, msg->cm_fields['I']);
194         if (haschar(msgid, '@') == 0) {
195                 strcat(msgid, "@");
196                 if (msg->cm_fields['N'] != NULL) {
197                         strcat(msgid, msg->cm_fields['N']);
198                 }
199                 else {
200                         return(0);
201                 }
202         }
203
204         cdbut = cdb_fetch(CDB_USETABLE, msgid, strlen(msgid));
205         if (cdbut != NULL) {
206                 cdb_free(cdbut);
207                 return(1);
208         }
209
210         /* If we got to this point, it's unique: add it. */
211         strcpy(ut.ut_msgid, msgid);
212         ut.ut_timestamp = time(NULL);
213         cdb_store(CDB_USETABLE, msgid, strlen(msgid),
214                 &ut, sizeof(struct UseTable) );
215         return(0);
216 }
217
218
219 /* 
220  * Read the network map from its configuration file into memory.
221  */
222 void read_network_map(void) {
223         char *serialized_map = NULL;
224         int i;
225         char buf[SIZ];
226         NetMap *nmptr;
227
228         serialized_map = CtdlGetSysConfig(IGNETMAP);
229         if (serialized_map == NULL) return;     /* if null, no entries */
230
231         /* Use the string tokenizer to grab one line at a time */
232         for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
233                 extract_token(buf, serialized_map, i, '\n', sizeof buf);
234                 nmptr = (NetMap *) malloc(sizeof(NetMap));
235                 extract_token(nmptr->nodename, buf, 0, '|', sizeof nmptr->nodename);
236                 nmptr->lastcontact = extract_long(buf, 1);
237                 extract_token(nmptr->nexthop, buf, 2, '|', sizeof nmptr->nexthop);
238                 nmptr->next = the_netmap;
239                 the_netmap = nmptr;
240         }
241
242         free(serialized_map);
243         netmap_changed = 0;
244 }
245
246
247 /*
248  * Write the network map from memory back to the configuration file.
249  */
250 void write_network_map(void) {
251         char *serialized_map = NULL;
252         NetMap *nmptr;
253
254
255         if (netmap_changed) {
256                 serialized_map = strdup("");
257         
258                 if (the_netmap != NULL) {
259                         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
260                                 serialized_map = realloc(serialized_map,
261                                                         (strlen(serialized_map)+SIZ) );
262                                 if (!IsEmptyStr(nmptr->nodename)) {
263                                         snprintf(&serialized_map[strlen(serialized_map)],
264                                                 SIZ,
265                                                 "%s|%ld|%s\n",
266                                                 nmptr->nodename,
267                                                 (long)nmptr->lastcontact,
268                                                 nmptr->nexthop);
269                                 }
270                         }
271                 }
272
273                 CtdlPutSysConfig(IGNETMAP, serialized_map);
274                 free(serialized_map);
275         }
276
277         /* Now free the list */
278         while (the_netmap != NULL) {
279                 nmptr = the_netmap->next;
280                 free(the_netmap);
281                 the_netmap = nmptr;
282         }
283         netmap_changed = 0;
284 }
285
286
287
288 /* 
289  * Check the network map and determine whether the supplied node name is
290  * valid.  If it is not a neighbor node, supply the name of a neighbor node
291  * which is the next hop.  If it *is* a neighbor node, we also fill in the
292  * shared secret.
293  */
294 int is_valid_node(char *nexthop, char *secret, char *node) {
295         int i;
296         char linebuf[SIZ];
297         char buf[SIZ];
298         int retval;
299         NetMap *nmptr;
300
301         if (node == NULL) {
302                 return(-1);
303         }
304
305         /*
306          * First try the neighbor nodes
307          */
308         if (working_ignetcfg == NULL) {
309                 CtdlLogPrintf(CTDL_ERR, "working_ignetcfg is NULL!\n");
310                 if (nexthop != NULL) {
311                         strcpy(nexthop, "");
312                 }
313                 return(-1);
314         }
315
316         retval = (-1);
317         if (nexthop != NULL) {
318                 strcpy(nexthop, "");
319         }
320
321         /* Use the string tokenizer to grab one line at a time */
322         for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
323                 extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
324                 extract_token(buf, linebuf, 0, '|', sizeof buf);
325                 if (!strcasecmp(buf, node)) {
326                         if (nexthop != NULL) {
327                                 strcpy(nexthop, "");
328                         }
329                         if (secret != NULL) {
330                                 extract_token(secret, linebuf, 1, '|', 256);
331                         }
332                         retval = 0;
333                 }
334         }
335
336         if (retval == 0) {
337                 return(retval);         /* yup, it's a direct neighbor */
338         }
339
340         /*      
341          * If we get to this point we have to see if we know the next hop
342          */
343         if (the_netmap != NULL) {
344                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
345                         if (!strcasecmp(nmptr->nodename, node)) {
346                                 if (nexthop != NULL) {
347                                         strcpy(nexthop, nmptr->nexthop);
348                                 }
349                                 return(0);
350                         }
351                 }
352         }
353
354         /*
355          * If we get to this point, the supplied node name is bogus.
356          */
357         CtdlLogPrintf(CTDL_ERR, "Invalid node name <%s>\n", node);
358         return(-1);
359 }
360
361
362
363
364
365 void cmd_gnet(char *argbuf) {
366         char filename[PATH_MAX];
367         char buf[SIZ];
368         FILE *fp;
369
370         if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
371                 /* users can edit the netconfigs for their own mailbox rooms */
372         }
373         else if (CtdlAccessCheck(ac_room_aide)) return;
374
375         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
376         cprintf("%d Network settings for room #%ld <%s>\n",
377                 LISTING_FOLLOWS,
378                 CC->room.QRnumber, CC->room.QRname);
379
380         fp = fopen(filename, "r");
381         if (fp != NULL) {
382                 while (fgets(buf, sizeof buf, fp) != NULL) {
383                         buf[strlen(buf)-1] = 0;
384                         cprintf("%s\n", buf);
385                 }
386                 fclose(fp);
387         }
388
389         cprintf("000\n");
390 }
391
392
393 void cmd_snet(char *argbuf) {
394         char tempfilename[PATH_MAX];
395         char filename[PATH_MAX];
396         char buf[SIZ];
397         FILE *fp, *newfp;
398
399         unbuffer_output();
400
401         if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
402                 /* users can edit the netconfigs for their own mailbox rooms */
403         }
404         else if (CtdlAccessCheck(ac_room_aide)) return;
405
406         CtdlMakeTempFileName(tempfilename, sizeof tempfilename);
407         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
408
409         fp = fopen(tempfilename, "w");
410         if (fp == NULL) {
411                 cprintf("%d Cannot open %s: %s\n",
412                         ERROR + INTERNAL_ERROR,
413                         tempfilename,
414                         strerror(errno));
415         }
416
417         cprintf("%d %s\n", SEND_LISTING, tempfilename);
418         while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
419                 fprintf(fp, "%s\n", buf);
420         }
421         fclose(fp);
422
423         /* Now copy the temp file to its permanent location.
424          * (We copy instead of link because they may be on different filesystems)
425          */
426         begin_critical_section(S_NETCONFIGS);
427         fp = fopen(tempfilename, "r");
428         if (fp != NULL) {
429                 newfp = fopen(filename, "w");
430                 if (newfp != NULL) {
431                         while (fgets(buf, sizeof buf, fp) != NULL) {
432                                 fprintf(newfp, "%s", buf);
433                         }
434                         fclose(newfp);
435                 }
436                 fclose(fp);
437         }
438         end_critical_section(S_NETCONFIGS);
439         unlink(tempfilename);
440 }
441
442
443 /*
444  * Deliver digest messages
445  */
446 void network_deliver_digest(SpoolControl *sc) {
447         char buf[SIZ];
448         int i;
449         struct CtdlMessage *msg = NULL;
450         long msglen;
451         char *recps = NULL;
452         size_t recps_len = SIZ;
453         struct recptypes *valid;
454         namelist *nptr;
455
456         if (sc->num_msgs_spooled < 1) {
457                 fclose(sc->digestfp);
458                 sc->digestfp = NULL;
459                 return;
460         }
461
462         msg = malloc(sizeof(struct CtdlMessage));
463         memset(msg, 0, sizeof(struct CtdlMessage));
464         msg->cm_magic = CTDLMESSAGE_MAGIC;
465         msg->cm_format_type = FMT_RFC822;
466         msg->cm_anon_type = MES_NORMAL;
467
468         sprintf(buf, "%ld", time(NULL));
469         msg->cm_fields['T'] = strdup(buf);
470         msg->cm_fields['A'] = strdup(CC->room.QRname);
471         snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
472         msg->cm_fields['U'] = strdup(buf);
473         sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
474         for (i=0; buf[i]; ++i) {
475                 if (isspace(buf[i])) buf[i]='_';
476                 buf[i] = tolower(buf[i]);
477         }
478         msg->cm_fields['F'] = strdup(buf);
479         msg->cm_fields['R'] = strdup(buf);
480
481         /* Set the 'List-ID' header */
482         msg->cm_fields['L'] = malloc(1024);
483         snprintf(msg->cm_fields['L'], 1024,
484                 "%s <%ld.list-id.%s>",
485                 CC->room.QRname,
486                 CC->room.QRnumber,
487                 config.c_fqdn
488         );
489
490         /*
491          * Go fetch the contents of the digest
492          */
493         fseek(sc->digestfp, 0L, SEEK_END);
494         msglen = ftell(sc->digestfp);
495
496         msg->cm_fields['M'] = malloc(msglen + 1);
497         fseek(sc->digestfp, 0L, SEEK_SET);
498         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
499         msg->cm_fields['M'][msglen] = 0;
500
501         fclose(sc->digestfp);
502         sc->digestfp = NULL;
503
504         /* Now generate the delivery instructions */
505
506         /* 
507          * Figure out how big a buffer we need to allocate
508          */
509         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
510                 recps_len = recps_len + strlen(nptr->name) + 2;
511         }
512         
513         recps = malloc(recps_len);
514
515         if (recps == NULL) {
516                 CtdlLogPrintf(CTDL_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
517                 abort();
518         }
519
520         strcpy(recps, "");
521
522         /* Each recipient */
523         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
524                 if (nptr != sc->digestrecps) {
525                         strcat(recps, ",");
526                 }
527                 strcat(recps, nptr->name);
528         }
529
530         /* Now submit the message */
531         valid = validate_recipients(recps, NULL, 0);
532         free(recps);
533         CtdlSubmitMsg(msg, valid, NULL, 0);
534         CtdlFreeMessage(msg);
535         free_recipients(valid);
536 }
537
538
539 /*
540  * Deliver list messages to everyone on the list ... efficiently
541  */
542 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc) {
543         char *recps = NULL;
544         size_t recps_len = SIZ;
545         struct recptypes *valid;
546         namelist *nptr;
547
548         /* Don't do this if there were no recipients! */
549         if (sc->listrecps == NULL) return;
550
551         /* Now generate the delivery instructions */
552
553         /* 
554          * Figure out how big a buffer we need to allocate
555          */
556         for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
557                 recps_len = recps_len + strlen(nptr->name) + 2;
558         }
559         
560         recps = malloc(recps_len);
561
562         if (recps == NULL) {
563                 CtdlLogPrintf(CTDL_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len);
564                 abort();
565         }
566
567         strcpy(recps, "");
568
569         /* Each recipient */
570         for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
571                 if (nptr != sc->listrecps) {
572                         strcat(recps, ",");
573                 }
574                 strcat(recps, nptr->name);
575         }
576
577         /* Now submit the message */
578         valid = validate_recipients(recps, NULL, 0);
579         free(recps);
580         CtdlSubmitMsg(msg, valid, NULL, 0);
581         free_recipients(valid);
582         /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
583 }
584
585
586
587
588 /*
589  * Spools out one message from the list.
590  */
591 void network_spool_msg(long msgnum, void *userdata) {
592         SpoolControl *sc;
593         int i;
594         char *newpath = NULL;
595         size_t instr_len = SIZ;
596         struct CtdlMessage *msg = NULL;
597         namelist *nptr;
598         maplist *mptr;
599         struct ser_ret sermsg;
600         FILE *fp;
601         char filename[PATH_MAX];
602         char buf[SIZ];
603         int bang = 0;
604         int send = 1;
605         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
606         int ok_to_participate = 0;
607         struct recptypes *valid;
608
609         sc = (SpoolControl *)userdata;
610
611         /*
612          * Process mailing list recipients
613          */
614         instr_len = SIZ;
615         if (sc->listrecps != NULL) {
616                 /* Fetch the message.  We're going to need to modify it
617                  * in order to insert the [list name] in it, etc.
618                  */
619                 msg = CtdlFetchMessage(msgnum, 1);
620                 if (msg != NULL) {
621
622                         /* Set the 'List-ID' header */
623                         if (msg->cm_fields['L'] != NULL) {
624                                 free(msg->cm_fields['L']);
625                         }
626                         msg->cm_fields['L'] = malloc(1024);
627                         snprintf(msg->cm_fields['L'], 1024,
628                                 "%s <%ld.list-id.%s>",
629                                 CC->room.QRname,
630                                 CC->room.QRnumber,
631                                 config.c_fqdn
632                         );
633
634                         /* Prepend "[List name]" to the subject */
635                         if (msg->cm_fields['U'] == NULL) {
636                                 msg->cm_fields['U'] = strdup("(no subject)");
637                         }
638                         snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, msg->cm_fields['U']);
639                         free(msg->cm_fields['U']);
640                         msg->cm_fields['U'] = strdup(buf);
641
642                         /* Set the recipient of the list message to the
643                          * email address of the room itself.
644                          * FIXME ... I want to be able to pick any address
645                          */
646                         if (msg->cm_fields['R'] != NULL) {
647                                 free(msg->cm_fields['R']);
648                         }
649                         msg->cm_fields['R'] = malloc(256);
650                         snprintf(msg->cm_fields['R'], 256,
651                                 "room_%s@%s", CC->room.QRname,
652                                 config.c_fqdn);
653                         for (i=0; msg->cm_fields['R'][i]; ++i) {
654                                 if (isspace(msg->cm_fields['R'][i])) {
655                                         msg->cm_fields['R'][i] = '_';
656                                 }
657                         }
658
659                         /* Handle delivery */
660                         network_deliver_list(msg, sc);
661                         CtdlFreeMessage(msg);
662                 }
663         }
664
665         /*
666          * Process digest recipients
667          */
668         if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
669                 msg = CtdlFetchMessage(msgnum, 1);
670                 if (msg != NULL) {
671                         fprintf(sc->digestfp,   " -----------------------------------"
672                                                 "------------------------------------"
673                                                 "-------\n");
674                         fprintf(sc->digestfp, "From: ");
675                         if (msg->cm_fields['A'] != NULL) {
676                                 fprintf(sc->digestfp, "%s ", msg->cm_fields['A']);
677                         }
678                         if (msg->cm_fields['F'] != NULL) {
679                                 fprintf(sc->digestfp, "<%s> ", msg->cm_fields['F']);
680                         }
681                         else if (msg->cm_fields['N'] != NULL) {
682                                 fprintf(sc->digestfp, "@%s ", msg->cm_fields['N']);
683                         }
684                         fprintf(sc->digestfp, "\n");
685                         if (msg->cm_fields['U'] != NULL) {
686                                 fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields['U']);
687                         }
688
689                         CC->redirect_buffer = malloc(SIZ);
690                         CC->redirect_len = 0;
691                         CC->redirect_alloc = SIZ;
692
693                         safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats);
694                         CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
695
696                         striplt(CC->redirect_buffer);
697                         fprintf(sc->digestfp, "\n%s\n", CC->redirect_buffer);
698
699                         free(CC->redirect_buffer);
700                         CC->redirect_buffer = NULL;
701                         CC->redirect_len = 0;
702                         CC->redirect_alloc = 0;
703
704                         sc->num_msgs_spooled += 1;
705                         free(msg);
706                 }
707         }
708
709         /*
710          * Process client-side list participations for this room
711          */
712         instr_len = SIZ;
713         if (sc->participates != NULL) {
714                 msg = CtdlFetchMessage(msgnum, 1);
715                 if (msg != NULL) {
716
717                         /* Only send messages which originated on our own Citadel
718                          * network, otherwise we'll end up sending the remote
719                          * mailing list's messages back to it, which is rude...
720                          */
721                         ok_to_participate = 0;
722                         if (msg->cm_fields['N'] != NULL) {
723                                 if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) {
724                                         ok_to_participate = 1;
725                                 }
726                                 if (is_valid_node(NULL, NULL, msg->cm_fields['N']) == 0) {
727                                         ok_to_participate = 1;
728                                 }
729                         }
730                         if (ok_to_participate) {
731                                 if (msg->cm_fields['F'] != NULL) {
732                                         free(msg->cm_fields['F']);
733                                 }
734                                 msg->cm_fields['F'] = malloc(SIZ);
735                                 /* Replace the Internet email address of the actual
736                                 * author with the email address of the room itself,
737                                 * so the remote listserv doesn't reject us.
738                                 * FIXME ... I want to be able to pick any address
739                                 */
740                                 snprintf(msg->cm_fields['F'], SIZ,
741                                         "room_%s@%s", CC->room.QRname,
742                                         config.c_fqdn);
743                                 for (i=0; msg->cm_fields['F'][i]; ++i) {
744                                         if (isspace(msg->cm_fields['F'][i])) {
745                                                 msg->cm_fields['F'][i] = '_';
746                                         }
747                                 }
748
749                                 /* 
750                                  * Figure out how big a buffer we need to allocate
751                                  */
752                                 for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
753
754                                         if (msg->cm_fields['R'] == NULL) {
755                                                 free(msg->cm_fields['R']);
756                                         }
757                                         msg->cm_fields['R'] = strdup(nptr->name);
758         
759                                         valid = validate_recipients(nptr->name, NULL, 0);
760                                         CtdlSubmitMsg(msg, valid, "", 0);
761                                         free_recipients(valid);
762                                 }
763                         
764                         }
765                         CtdlFreeMessage(msg);
766                 }
767         }
768         
769         /*
770          * Process IGnet push shares
771          */
772         msg = CtdlFetchMessage(msgnum, 1);
773         if (msg != NULL) {
774                 size_t newpath_len;
775
776                 /* Prepend our node name to the Path field whenever
777                  * sending a message to another IGnet node
778                  */
779                 if (msg->cm_fields['P'] == NULL) {
780                         msg->cm_fields['P'] = strdup("username");
781                 }
782                 newpath_len = strlen(msg->cm_fields['P']) +
783                          strlen(config.c_nodename) + 2;
784                 newpath = malloc(newpath_len);
785                 snprintf(newpath, newpath_len, "%s!%s",
786                          config.c_nodename, msg->cm_fields['P']);
787                 free(msg->cm_fields['P']);
788                 msg->cm_fields['P'] = newpath;
789
790                 /*
791                  * Determine if this message is set to be deleted
792                  * after sending out on the network
793                  */
794                 if (msg->cm_fields['S'] != NULL) {
795                         if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
796                                 delete_after_send = 1;
797                         }
798                 }
799
800                 /* Now send it to every node */
801                 if (sc->ignet_push_shares != NULL)
802                   for (mptr = sc->ignet_push_shares; mptr != NULL;
803                     mptr = mptr->next) {
804
805                         send = 1;
806
807                         /* Check for valid node name */
808                         if (is_valid_node(NULL, NULL, mptr->remote_nodename) != 0) {
809                                 CtdlLogPrintf(CTDL_ERR, "Invalid node <%s>\n", mptr->remote_nodename);
810                                 send = 0;
811                         }
812
813                         /* Check for split horizon */
814                         CtdlLogPrintf(CTDL_DEBUG, "Path is %s\n", msg->cm_fields['P']);
815                         bang = num_tokens(msg->cm_fields['P'], '!');
816                         if (bang > 1) for (i=0; i<(bang-1); ++i) {
817                                 extract_token(buf, msg->cm_fields['P'], i, '!', sizeof buf);
818                                 CtdlLogPrintf(CTDL_DEBUG, "Compare <%s> to <%s>\n",
819                                         buf, mptr->remote_nodename) ;
820                                 if (!strcasecmp(buf, mptr->remote_nodename)) {
821                                         send = 0;
822                                         CtdlLogPrintf(CTDL_DEBUG, "Not sending to %s\n",
823                                                 mptr->remote_nodename);
824                                 }
825                                 else {
826                                         CtdlLogPrintf(CTDL_DEBUG, "Sending to %s\n", mptr->remote_nodename);
827                                 }
828                         }
829
830                         /* Send the message */
831                         if (send == 1) {
832
833                                 /*
834                                  * Force the message to appear in the correct room
835                                  * on the far end by setting the C field correctly
836                                  */
837                                 if (msg->cm_fields['C'] != NULL) {
838                                         free(msg->cm_fields['C']);
839                                 }
840                                 if (!IsEmptyStr(mptr->remote_roomname)) {
841                                         msg->cm_fields['C'] = strdup(mptr->remote_roomname);
842                                 }
843                                 else {
844                                         msg->cm_fields['C'] = strdup(CC->room.QRname);
845                                 }
846
847                                 /* serialize it for transmission */
848                                 serialize_message(&sermsg, msg);
849                                 if (sermsg.len > 0) {
850
851                                         /* write it to the spool file */
852                                         snprintf(filename, sizeof filename,"%s/%s",
853                                                         ctdl_netout_dir,
854                                                         mptr->remote_nodename);
855                                         CtdlLogPrintf(CTDL_DEBUG, "Appending to %s\n", filename);
856                                         fp = fopen(filename, "ab");
857                                         if (fp != NULL) {
858                                                 fwrite(sermsg.ser,
859                                                         sermsg.len, 1, fp);
860                                                 fclose(fp);
861                                         }
862                                         else {
863                                                 CtdlLogPrintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno));
864                                         }
865         
866                                         /* free the serialized version */
867                                         free(sermsg.ser);
868                                 }
869
870                         }
871                 }
872                 CtdlFreeMessage(msg);
873         }
874
875         /* update lastsent */
876         sc->lastsent = msgnum;
877
878         /* Delete this message if delete-after-send is set */
879         if (delete_after_send) {
880                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
881         }
882
883 }
884         
885
886 int read_spoolcontrol_file(SpoolControl **scc, char *filename)
887 {
888         FILE *fp;
889         char instr[SIZ];
890         char buf[SIZ];
891         char nodename[256];
892         char roomname[ROOMNAMELEN];
893         size_t miscsize = 0;
894         size_t linesize = 0;
895         int skipthisline = 0;
896         namelist *nptr = NULL;
897         maplist *mptr = NULL;
898         SpoolControl *sc;
899
900         fp = fopen(filename, "r");
901         if (fp == NULL) {
902                 return 0;
903         }
904         sc = malloc(sizeof(SpoolControl));
905         memset(sc, 0, sizeof(SpoolControl));
906         *scc = sc;
907
908         while (fgets(buf, sizeof buf, fp) != NULL) {
909                 buf[strlen(buf)-1] = 0;
910
911                 extract_token(instr, buf, 0, '|', sizeof instr);
912                 if (!strcasecmp(instr, "lastsent")) {
913                         sc->lastsent = extract_long(buf, 1);
914                 }
915                 else if (!strcasecmp(instr, "listrecp")) {
916                         nptr = (namelist *)
917                                 malloc(sizeof(namelist));
918                         nptr->next = sc->listrecps;
919                         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
920                         sc->listrecps = nptr;
921                 }
922                 else if (!strcasecmp(instr, "participate")) {
923                         nptr = (namelist *)
924                                 malloc(sizeof(namelist));
925                         nptr->next = sc->participates;
926                         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
927                         sc->participates = nptr;
928                 }
929                 else if (!strcasecmp(instr, "digestrecp")) {
930                         nptr = (namelist *)
931                                 malloc(sizeof(namelist));
932                         nptr->next = sc->digestrecps;
933                         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
934                         sc->digestrecps = nptr;
935                 }
936                 else if (!strcasecmp(instr, "ignet_push_share")) {
937                         extract_token(nodename, buf, 1, '|', sizeof nodename);
938                         extract_token(roomname, buf, 2, '|', sizeof roomname);
939                         mptr = (maplist *) malloc(sizeof(maplist));
940                         mptr->next = sc->ignet_push_shares;
941                         strcpy(mptr->remote_nodename, nodename);
942                         strcpy(mptr->remote_roomname, roomname);
943                         sc->ignet_push_shares = mptr;
944                 }
945                 else {
946                         /* Preserve 'other' lines ... *unless* they happen to
947                          * be subscribe/unsubscribe pendings with expired
948                          * timestamps.
949                          */
950                         skipthisline = 0;
951                         if (!strncasecmp(buf, "subpending|", 11)) {
952                                 if (time(NULL) - extract_long(buf, 4) > EXP) {
953                                         skipthisline = 1;
954                                 }
955                         }
956                         if (!strncasecmp(buf, "unsubpending|", 13)) {
957                                 if (time(NULL) - extract_long(buf, 3) > EXP) {
958                                         skipthisline = 1;
959                                 }
960                         }
961
962                         if (skipthisline == 0) {
963                                 linesize = strlen(buf);
964                                 sc->misc = realloc(sc->misc,
965                                         (miscsize + linesize + 2) );
966                                 sprintf(&sc->misc[miscsize], "%s\n", buf);
967                                 miscsize = miscsize + linesize + 1;
968                         }
969                 }
970
971
972         }
973         fclose(fp);
974         return 1;
975 }
976
977 void free_spoolcontrol_struct(SpoolControl **scc)
978 {
979         SpoolControl *sc;
980         namelist *nptr = NULL;
981         maplist *mptr = NULL;
982
983         sc = *scc;
984         while (sc->listrecps != NULL) {
985                 nptr = sc->listrecps->next;
986                 free(sc->listrecps);
987                 sc->listrecps = nptr;
988         }
989         /* Do the same for digestrecps */
990         while (sc->digestrecps != NULL) {
991                 nptr = sc->digestrecps->next;
992                 free(sc->digestrecps);
993                 sc->digestrecps = nptr;
994         }
995         /* Do the same for participates */
996         while (sc->participates != NULL) {
997                 nptr = sc->participates->next;
998                 free(sc->participates);
999                 sc->participates = nptr;
1000         }
1001         while (sc->ignet_push_shares != NULL) {
1002                 mptr = sc->ignet_push_shares->next;
1003                 free(sc->ignet_push_shares);
1004                 sc->ignet_push_shares = mptr;
1005         }
1006         free(sc->misc);
1007         free(sc);
1008         *scc=NULL;
1009 }
1010
1011 int writenfree_spoolcontrol_file(SpoolControl **scc, char *filename)
1012 {
1013         FILE *fp;
1014         SpoolControl *sc;
1015         namelist *nptr = NULL;
1016         maplist *mptr = NULL;
1017
1018         sc = *scc;
1019         fp = fopen(filename, "w");
1020         if (fp == NULL) {
1021                 CtdlLogPrintf(CTDL_CRIT, "ERROR: cannot open %s: %s\n",
1022                         filename, strerror(errno));
1023                 free_spoolcontrol_struct(scc);
1024         }
1025         else {
1026                 fprintf(fp, "lastsent|%ld\n", sc->lastsent);
1027
1028                 /* Write out the listrecps while freeing from memory at the
1029                  * same time.  Am I clever or what?  :)
1030                  */
1031                 while (sc->listrecps != NULL) {
1032                         fprintf(fp, "listrecp|%s\n", sc->listrecps->name);
1033                         nptr = sc->listrecps->next;
1034                         free(sc->listrecps);
1035                         sc->listrecps = nptr;
1036                 }
1037                 /* Do the same for digestrecps */
1038                 while (sc->digestrecps != NULL) {
1039                         fprintf(fp, "digestrecp|%s\n", sc->digestrecps->name);
1040                         nptr = sc->digestrecps->next;
1041                         free(sc->digestrecps);
1042                         sc->digestrecps = nptr;
1043                 }
1044                 /* Do the same for participates */
1045                 while (sc->participates != NULL) {
1046                         fprintf(fp, "participate|%s\n", sc->participates->name);
1047                         nptr = sc->participates->next;
1048                         free(sc->participates);
1049                         sc->participates = nptr;
1050                 }
1051                 while (sc->ignet_push_shares != NULL) {
1052                         fprintf(fp, "ignet_push_share|%s", sc->ignet_push_shares->remote_nodename);
1053                         if (!IsEmptyStr(sc->ignet_push_shares->remote_roomname)) {
1054                                 fprintf(fp, "|%s", sc->ignet_push_shares->remote_roomname);
1055                         }
1056                         fprintf(fp, "\n");
1057                         mptr = sc->ignet_push_shares->next;
1058                         free(sc->ignet_push_shares);
1059                         sc->ignet_push_shares = mptr;
1060                 }
1061                 if (sc->misc != NULL) {
1062                         fwrite(sc->misc, strlen(sc->misc), 1, fp);
1063                 }
1064                 free(sc->misc);
1065
1066                 fclose(fp);
1067                 free(sc);
1068                 *scc=NULL;
1069         }
1070         return 1;
1071 }
1072 int is_recipient(SpoolControl *sc, const char *Name)
1073 {
1074         namelist *nptr;
1075         size_t len;
1076
1077         len = strlen(Name);
1078         nptr = sc->listrecps;
1079         while (nptr != NULL) {
1080                 if (strncmp(Name, nptr->name, len)==0)
1081                         return 1;
1082                 nptr = nptr->next;
1083         }
1084         /* Do the same for digestrecps */
1085         nptr = sc->digestrecps;
1086         while (nptr != NULL) {
1087                 if (strncmp(Name, nptr->name, len)==0)
1088                         return 1;
1089                 nptr = nptr->next;
1090         }
1091         /* Do the same for participates */
1092         nptr = sc->participates;
1093         while (nptr != NULL) {
1094                 if (strncmp(Name, nptr->name, len)==0)
1095                         return 1;
1096                 nptr = nptr->next;
1097         }
1098         return 0;
1099 }
1100
1101
1102 /*
1103  * Batch up and send all outbound traffic from the current room
1104  */
1105 void network_spoolout_room(char *room_to_spool) {
1106         char buf[SIZ];
1107         char filename[PATH_MAX];
1108         SpoolControl *sc;
1109         int i;
1110
1111         /*
1112          * If the room doesn't exist, don't try to perform its networking tasks.
1113          * Normally this should never happen, but once in a while maybe a room gets
1114          * queued for networking and then deleted before it can happen.
1115          */
1116         if (getroom(&CC->room, room_to_spool) != 0) {
1117                 CtdlLogPrintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", room_to_spool);
1118                 return;
1119         }
1120
1121         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
1122         begin_critical_section(S_NETCONFIGS);
1123
1124         /* Only do net processing for rooms that have netconfigs */
1125         if (!read_spoolcontrol_file(&sc, filename))
1126         {
1127                 end_critical_section(S_NETCONFIGS);
1128                 return;
1129         }
1130         CtdlLogPrintf(CTDL_INFO, "Networking started for <%s>\n", CC->room.QRname);
1131
1132         /* If there are digest recipients, we have to build a digest */
1133         if (sc->digestrecps != NULL) {
1134                 sc->digestfp = tmpfile();
1135                 fprintf(sc->digestfp, "Content-type: text/plain\n\n");
1136         }
1137
1138         /* Do something useful */
1139         CtdlForEachMessage(MSGS_GT, sc->lastsent, NULL, NULL, NULL,
1140                 network_spool_msg, sc);
1141
1142         /* If we wrote a digest, deliver it and then close it */
1143         snprintf(buf, sizeof buf, "room_%s@%s",
1144                 CC->room.QRname, config.c_fqdn);
1145         for (i=0; buf[i]; ++i) {
1146                 buf[i] = tolower(buf[i]);
1147                 if (isspace(buf[i])) buf[i] = '_';
1148         }
1149         if (sc->digestfp != NULL) {
1150                 fprintf(sc->digestfp,   " -----------------------------------"
1151                                         "------------------------------------"
1152                                         "-------\n"
1153                                         "You are subscribed to the '%s' "
1154                                         "list.\n"
1155                                         "To post to the list: %s\n",
1156                                         CC->room.QRname, buf
1157                 );
1158                 network_deliver_digest(sc);     /* deliver and close */
1159         }
1160
1161         /* Now rewrite the config file */
1162         writenfree_spoolcontrol_file (&sc, filename);
1163         end_critical_section(S_NETCONFIGS);
1164 }
1165
1166
1167
1168 /*
1169  * Send the *entire* contents of the current room to one specific network node,
1170  * ignoring anything we know about which messages have already undergone
1171  * network processing.  This can be used to bring a new node into sync.
1172  */
1173 int network_sync_to(char *target_node) {
1174         SpoolControl sc;
1175         int num_spooled = 0;
1176         int found_node = 0;
1177         char buf[256];
1178         char sc_type[256];
1179         char sc_node[256];
1180         char sc_room[256];
1181         char filename[PATH_MAX];
1182         FILE *fp;
1183
1184         /* Grab the configuration line we're looking for */
1185         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
1186         begin_critical_section(S_NETCONFIGS);
1187         fp = fopen(filename, "r");
1188         if (fp == NULL) {
1189                 end_critical_section(S_NETCONFIGS);
1190                 return(-1);
1191         }
1192         while (fgets(buf, sizeof buf, fp) != NULL) {
1193                 buf[strlen(buf)-1] = 0;
1194                 extract_token(sc_type, buf, 0, '|', sizeof sc_type);
1195                 extract_token(sc_node, buf, 1, '|', sizeof sc_node);
1196                 extract_token(sc_room, buf, 2, '|', sizeof sc_room);
1197                 if ( (!strcasecmp(sc_type, "ignet_push_share"))
1198                    && (!strcasecmp(sc_node, target_node)) ) {
1199                         found_node = 1;
1200                         
1201                         /* Concise syntax because we don't need a full linked-list */
1202                         memset(&sc, 0, sizeof(SpoolControl));
1203                         sc.ignet_push_shares = (maplist *)
1204                                 malloc(sizeof(maplist));
1205                         sc.ignet_push_shares->next = NULL;
1206                         safestrncpy(sc.ignet_push_shares->remote_nodename,
1207                                 sc_node,
1208                                 sizeof sc.ignet_push_shares->remote_nodename);
1209                         safestrncpy(sc.ignet_push_shares->remote_roomname,
1210                                 sc_room,
1211                                 sizeof sc.ignet_push_shares->remote_roomname);
1212                 }
1213         }
1214         fclose(fp);
1215         end_critical_section(S_NETCONFIGS);
1216
1217         if (!found_node) return(-1);
1218
1219         /* Send ALL messages */
1220         num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
1221                 network_spool_msg, &sc);
1222
1223         /* Concise cleanup because we know there's only one node in the sc */
1224         free(sc.ignet_push_shares);
1225
1226         CtdlLogPrintf(CTDL_NOTICE, "Synchronized %d messages to <%s>\n",
1227                 num_spooled, target_node);
1228         return(num_spooled);
1229 }
1230
1231
1232 /*
1233  * Implements the NSYN command
1234  */
1235 void cmd_nsyn(char *argbuf) {
1236         int num_spooled;
1237         char target_node[256];
1238
1239         if (CtdlAccessCheck(ac_aide)) return;
1240
1241         extract_token(target_node, argbuf, 0, '|', sizeof target_node);
1242         num_spooled = network_sync_to(target_node);
1243         if (num_spooled >= 0) {
1244                 cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled);
1245         }
1246         else {
1247                 cprintf("%d No such room/node share exists.\n",
1248                         ERROR + ROOM_NOT_FOUND);
1249         }
1250 }
1251
1252
1253
1254 /*
1255  * Batch up and send all outbound traffic from the current room
1256  */
1257 void network_queue_room(struct ctdlroom *qrbuf, void *data) {
1258         struct RoomProcList *ptr;
1259
1260         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
1261         if (ptr == NULL) return;
1262
1263         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
1264         begin_critical_section(S_RPLIST);
1265         ptr->next = rplist;
1266         rplist = ptr;
1267         end_critical_section(S_RPLIST);
1268 }
1269
1270 void destroy_network_queue_room(void)
1271 {
1272         struct RoomProcList *cur, *p;
1273         NetMap *nmcur, *nmp;
1274
1275         cur = rplist;
1276         begin_critical_section(S_RPLIST);
1277         while (cur != NULL)
1278         {
1279                 p = cur->next;
1280                 free (cur);
1281                 cur = p;                
1282         }
1283         rplist = NULL;
1284         end_critical_section(S_RPLIST);
1285
1286         nmcur = the_netmap;
1287         while (nmcur != NULL)
1288         {
1289                 nmp = nmcur->next;
1290                 free (nmcur);
1291                 nmcur = nmp;            
1292         }
1293         the_netmap = NULL;
1294         if (working_ignetcfg != NULL)
1295                 free (working_ignetcfg);
1296         working_ignetcfg = NULL;
1297 }
1298
1299
1300 /*
1301  * Learn topology from path fields
1302  */
1303 void network_learn_topology(char *node, char *path) {
1304         char nexthop[256];
1305         NetMap *nmptr;
1306
1307         strcpy(nexthop, "");
1308
1309         if (num_tokens(path, '!') < 3) return;
1310         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
1311                 if (!strcasecmp(nmptr->nodename, node)) {
1312                         extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
1313                         nmptr->lastcontact = time(NULL);
1314                         ++netmap_changed;
1315                         return;
1316                 }
1317         }
1318
1319         /* If we got here then it's not in the map, so add it. */
1320         nmptr = (NetMap *) malloc(sizeof (NetMap));
1321         strcpy(nmptr->nodename, node);
1322         nmptr->lastcontact = time(NULL);
1323         extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
1324         nmptr->next = the_netmap;
1325         the_netmap = nmptr;
1326         ++netmap_changed;
1327 }
1328
1329
1330
1331
1332 /*
1333  * Bounce a message back to the sender
1334  */
1335 void network_bounce(struct CtdlMessage *msg, char *reason) {
1336         char *oldpath = NULL;
1337         char buf[SIZ];
1338         char bouncesource[SIZ];
1339         char recipient[SIZ];
1340         struct recptypes *valid = NULL;
1341         char force_room[ROOMNAMELEN];
1342         static int serialnum = 0;
1343         size_t size;
1344
1345         CtdlLogPrintf(CTDL_DEBUG, "entering network_bounce()\n");
1346
1347         if (msg == NULL) return;
1348
1349         snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
1350
1351         /* 
1352          * Give it a fresh message ID
1353          */
1354         if (msg->cm_fields['I'] != NULL) {
1355                 free(msg->cm_fields['I']);
1356         }
1357         snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s",
1358                 (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn);
1359         msg->cm_fields['I'] = strdup(buf);
1360
1361         /*
1362          * FIXME ... right now we're just sending a bounce; we really want to
1363          * include the text of the bounced message.
1364          */
1365         if (msg->cm_fields['M'] != NULL) {
1366                 free(msg->cm_fields['M']);
1367         }
1368         msg->cm_fields['M'] = strdup(reason);
1369         msg->cm_format_type = 0;
1370
1371         /*
1372          * Turn the message around
1373          */
1374         if (msg->cm_fields['R'] == NULL) {
1375                 free(msg->cm_fields['R']);
1376         }
1377
1378         if (msg->cm_fields['D'] == NULL) {
1379                 free(msg->cm_fields['D']);
1380         }
1381
1382         snprintf(recipient, sizeof recipient, "%s@%s",
1383                 msg->cm_fields['A'], msg->cm_fields['N']);
1384
1385         if (msg->cm_fields['A'] == NULL) {
1386                 free(msg->cm_fields['A']);
1387         }
1388
1389         if (msg->cm_fields['N'] == NULL) {
1390                 free(msg->cm_fields['N']);
1391         }
1392
1393         if (msg->cm_fields['U'] == NULL) {
1394                 free(msg->cm_fields['U']);
1395         }
1396
1397         msg->cm_fields['A'] = strdup(BOUNCESOURCE);
1398         msg->cm_fields['N'] = strdup(config.c_nodename);
1399         msg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1400
1401         /* prepend our node to the path */
1402         if (msg->cm_fields['P'] != NULL) {
1403                 oldpath = msg->cm_fields['P'];
1404                 msg->cm_fields['P'] = NULL;
1405         }
1406         else {
1407                 oldpath = strdup("unknown_user");
1408         }
1409         size = strlen(oldpath) + SIZ;
1410         msg->cm_fields['P'] = malloc(size);
1411         snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath);
1412         free(oldpath);
1413
1414         /* Now submit the message */
1415         valid = validate_recipients(recipient, NULL, 0);
1416         if (valid != NULL) if (valid->num_error != 0) {
1417                 free_recipients(valid);
1418                 valid = NULL;
1419         }
1420         if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
1421                 strcpy(force_room, config.c_aideroom);
1422         }
1423         else {
1424                 strcpy(force_room, "");
1425         }
1426         if ( (valid == NULL) && IsEmptyStr(force_room) ) {
1427                 strcpy(force_room, config.c_aideroom);
1428         }
1429         CtdlSubmitMsg(msg, valid, force_room, 0);
1430
1431         /* Clean up */
1432         if (valid != NULL) free_recipients(valid);
1433         CtdlFreeMessage(msg);
1434         CtdlLogPrintf(CTDL_DEBUG, "leaving network_bounce()\n");
1435 }
1436
1437
1438
1439
1440 /*
1441  * Process a buffer containing a single message from a single file
1442  * from the inbound queue 
1443  */
1444 void network_process_buffer(char *buffer, long size) {
1445         struct CtdlMessage *msg = NULL;
1446         long pos;
1447         int field;
1448         struct recptypes *recp = NULL;
1449         char target_room[ROOMNAMELEN];
1450         struct ser_ret sermsg;
1451         char *oldpath = NULL;
1452         char filename[PATH_MAX];
1453         FILE *fp;
1454         char nexthop[SIZ];
1455         unsigned char firstbyte;
1456         unsigned char lastbyte;
1457
1458         /* Validate just a little bit.  First byte should be FF and * last byte should be 00. */
1459         firstbyte = buffer[0];
1460         lastbyte = buffer[size-1];
1461         if ( (firstbyte != 255) || (lastbyte != 0) ) {
1462                 CtdlLogPrintf(CTDL_ERR, "Corrupt message ignored.  Length=%ld, firstbyte = %d, lastbyte = %d\n",
1463                         size, firstbyte, lastbyte);
1464                 return;
1465         }
1466
1467         /* Set default target room to trash */
1468         strcpy(target_room, TWITROOM);
1469
1470         /* Load the message into memory */
1471         msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1472         memset(msg, 0, sizeof(struct CtdlMessage));
1473         msg->cm_magic = CTDLMESSAGE_MAGIC;
1474         msg->cm_anon_type = buffer[1];
1475         msg->cm_format_type = buffer[2];
1476
1477         for (pos = 3; pos < size; ++pos) {
1478                 field = buffer[pos];
1479                 msg->cm_fields[field] = strdup(&buffer[pos+1]);
1480                 pos = pos + strlen(&buffer[(int)pos]);
1481         }
1482
1483         /* Check for message routing */
1484         if (msg->cm_fields['D'] != NULL) {
1485                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
1486
1487                         /* route the message */
1488                         strcpy(nexthop, "");
1489                         if (is_valid_node(nexthop, NULL, msg->cm_fields['D']) == 0) {
1490                                 /* prepend our node to the path */
1491                                 if (msg->cm_fields['P'] != NULL) {
1492                                         oldpath = msg->cm_fields['P'];
1493                                         msg->cm_fields['P'] = NULL;
1494                                 }
1495                                 else {
1496                                         oldpath = strdup("unknown_user");
1497                                 }
1498                                 size = strlen(oldpath) + SIZ;
1499                                 msg->cm_fields['P'] = malloc(size);
1500                                 snprintf(msg->cm_fields['P'], size, "%s!%s",
1501                                         config.c_nodename, oldpath);
1502                                 free(oldpath);
1503
1504                                 /* serialize the message */
1505                                 serialize_message(&sermsg, msg);
1506
1507                                 /* now send it */
1508                                 if (IsEmptyStr(nexthop)) {
1509                                         strcpy(nexthop, msg->cm_fields['D']);
1510                                 }
1511                                 snprintf(filename, 
1512                                                  sizeof filename,
1513                                                  "%s/%s",
1514                                                  ctdl_netout_dir,
1515                                                  nexthop);
1516                                 CtdlLogPrintf(CTDL_DEBUG, "Appending to %s\n", filename);
1517                                 fp = fopen(filename, "ab");
1518                                 if (fp != NULL) {
1519                                         fwrite(sermsg.ser,
1520                                                 sermsg.len, 1, fp);
1521                                         fclose(fp);
1522                                 }
1523                                 else {
1524                                         CtdlLogPrintf(CTDL_ERR, "%s: %s\n", filename, strerror(errno));
1525                                 }
1526                                 free(sermsg.ser);
1527                                 CtdlFreeMessage(msg);
1528                                 return;
1529                         }
1530                         
1531                         else {  /* invalid destination node name */
1532
1533                                 network_bounce(msg,
1534 "A message you sent could not be delivered due to an invalid destination node"
1535 " name.  Please check the address and try sending the message again.\n");
1536                                 msg = NULL;
1537                                 return;
1538
1539                         }
1540                 }
1541         }
1542
1543         /*
1544          * Check to see if we already have a copy of this message, and
1545          * abort its processing if so.  (We used to post a warning to Aide>
1546          * every time this happened, but the network is now so densely
1547          * connected that it's inevitable.)
1548          */
1549         if (network_usetable(msg) != 0) {
1550                 CtdlFreeMessage(msg);
1551                 return;
1552         }
1553
1554         /* Learn network topology from the path */
1555         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
1556                 network_learn_topology(msg->cm_fields['N'], 
1557                                         msg->cm_fields['P']);
1558         }
1559
1560         /* Is the sending node giving us a very persuasive suggestion about
1561          * which room this message should be saved in?  If so, go with that.
1562          */
1563         if (msg->cm_fields['C'] != NULL) {
1564                 safestrncpy(target_room,
1565                         msg->cm_fields['C'],
1566                         sizeof target_room);
1567         }
1568
1569         /* Otherwise, does it have a recipient?  If so, validate it... */
1570         else if (msg->cm_fields['R'] != NULL) {
1571                 recp = validate_recipients(msg->cm_fields['R'], NULL, 0);
1572                 if (recp != NULL) if (recp->num_error != 0) {
1573                         network_bounce(msg,
1574                                 "A message you sent could not be delivered due to an invalid address.\n"
1575                                 "Please check the address and try sending the message again.\n");
1576                         msg = NULL;
1577                         free_recipients(recp);
1578                         return;
1579                 }
1580                 strcpy(target_room, "");        /* no target room if mail */
1581         }
1582
1583         /* Our last shot at finding a home for this message is to see if
1584          * it has the O field (Originating room) set.
1585          */
1586         else if (msg->cm_fields['O'] != NULL) {
1587                 safestrncpy(target_room,
1588                         msg->cm_fields['O'],
1589                         sizeof target_room);
1590         }
1591
1592         /* Strip out fields that are only relevant during transit */
1593         if (msg->cm_fields['D'] != NULL) {
1594                 free(msg->cm_fields['D']);
1595                 msg->cm_fields['D'] = NULL;
1596         }
1597         if (msg->cm_fields['C'] != NULL) {
1598                 free(msg->cm_fields['C']);
1599                 msg->cm_fields['C'] = NULL;
1600         }
1601
1602         /* save the message into a room */
1603         if (PerformNetprocHooks(msg, target_room) == 0) {
1604                 msg->cm_flags = CM_SKIP_HOOKS;
1605                 CtdlSubmitMsg(msg, recp, target_room, 0);
1606         }
1607         CtdlFreeMessage(msg);
1608         free_recipients(recp);
1609 }
1610
1611
1612 /*
1613  * Process a single message from a single file from the inbound queue 
1614  */
1615 void network_process_message(FILE *fp, long msgstart, long msgend) {
1616         long hold_pos;
1617         long size;
1618         char *buffer;
1619
1620         hold_pos = ftell(fp);
1621         size = msgend - msgstart + 1;
1622         buffer = malloc(size);
1623         if (buffer != NULL) {
1624                 fseek(fp, msgstart, SEEK_SET);
1625                 fread(buffer, size, 1, fp);
1626                 network_process_buffer(buffer, size);
1627                 free(buffer);
1628         }
1629
1630         fseek(fp, hold_pos, SEEK_SET);
1631 }
1632
1633
1634 /*
1635  * Process a single file from the inbound queue 
1636  */
1637 void network_process_file(char *filename) {
1638         FILE *fp;
1639         long msgstart = (-1L);
1640         long msgend = (-1L);
1641         long msgcur = 0L;
1642         int ch;
1643
1644
1645         fp = fopen(filename, "rb");
1646         if (fp == NULL) {
1647                 CtdlLogPrintf(CTDL_CRIT, "Error opening %s: %s\n", filename, strerror(errno));
1648                 return;
1649         }
1650
1651         fseek(fp, 0L, SEEK_END);
1652         CtdlLogPrintf(CTDL_INFO, "network: processing %ld bytes from %s\n", ftell(fp), filename);
1653         rewind(fp);
1654
1655         /* Look for messages in the data stream and break them out */
1656         while (ch = getc(fp), ch >= 0) {
1657         
1658                 if (ch == 255) {
1659                         if (msgstart >= 0L) {
1660                                 msgend = msgcur - 1;
1661                                 network_process_message(fp, msgstart, msgend);
1662                         }
1663                         msgstart = msgcur;
1664                 }
1665
1666                 ++msgcur;
1667         }
1668
1669         msgend = msgcur - 1;
1670         if (msgstart >= 0L) {
1671                 network_process_message(fp, msgstart, msgend);
1672         }
1673
1674         fclose(fp);
1675         unlink(filename);
1676 }
1677
1678
1679 /*
1680  * Process anything in the inbound queue
1681  */
1682 void network_do_spoolin(void) {
1683         DIR *dp;
1684         struct dirent *d;
1685         struct stat statbuf;
1686         char filename[PATH_MAX];
1687         static time_t last_spoolin_mtime = 0L;
1688
1689         /*
1690          * Check the spoolin directory's modification time.  If it hasn't
1691          * been touched, we don't need to scan it.
1692          */
1693         if (stat(ctdl_netin_dir, &statbuf)) return;
1694         if (statbuf.st_mtime == last_spoolin_mtime) {
1695                 CtdlLogPrintf(CTDL_DEBUG, "network: nothing in inbound queue\n");
1696                 return;
1697         }
1698         last_spoolin_mtime = statbuf.st_mtime;
1699         CtdlLogPrintf(CTDL_DEBUG, "network: processing inbound queue\n");
1700
1701         /*
1702          * Ok, there's something interesting in there, so scan it.
1703          */
1704         dp = opendir(ctdl_netin_dir);
1705         if (dp == NULL) return;
1706
1707         while (d = readdir(dp), d != NULL) {
1708                 if ((strcmp(d->d_name, ".")) && (strcmp(d->d_name, ".."))) {
1709                         snprintf(filename, 
1710                                          sizeof filename,
1711                                          "%s/%s",
1712                                          ctdl_netin_dir,
1713                                          d->d_name);
1714                         network_process_file(filename);
1715                 }
1716         }
1717
1718         closedir(dp);
1719 }
1720
1721 /*
1722  * Delete any files in the outbound queue that were intended
1723  * to be sent to nodes which no longer exist.
1724  */
1725 void network_purge_spoolout(void) {
1726         DIR *dp;
1727         struct dirent *d;
1728         char filename[PATH_MAX];
1729         char nexthop[256];
1730         int i;
1731
1732         dp = opendir(ctdl_netout_dir);
1733         if (dp == NULL) return;
1734
1735         while (d = readdir(dp), d != NULL) {
1736                 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
1737                         continue;
1738                 snprintf(filename, 
1739                                  sizeof filename,
1740                                  "%s/%s",
1741                                  ctdl_netout_dir,
1742                                  d->d_name);
1743
1744                 strcpy(nexthop, "");
1745                 i = is_valid_node(nexthop, NULL, d->d_name);
1746         
1747                 if ( (i != 0) || !IsEmptyStr(nexthop) ) {
1748                         unlink(filename);
1749                 }
1750         }
1751
1752
1753         closedir(dp);
1754 }
1755
1756
1757 /*
1758  * receive network spool from the remote system
1759  */
1760 void receive_spool(int sock, char *remote_nodename) {
1761         long download_len = 0L;
1762         long bytes_received = 0L;
1763         long bytes_copied = 0L;
1764         char buf[SIZ];
1765         static char pbuf[IGNET_PACKET_SIZE];
1766         char tempfilename[PATH_MAX];
1767         char filename[PATH_MAX];
1768         long plen;
1769         FILE *fp, *newfp;
1770
1771         CtdlMakeTempFileName(tempfilename, sizeof tempfilename);
1772         if (sock_puts(sock, "NDOP") < 0) return;
1773         if (sock_getln(sock, buf, sizeof buf) < 0) return;
1774         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1775         if (buf[0] != '2') {
1776                 return;
1777         }
1778         download_len = extract_long(&buf[4], 0);
1779
1780         bytes_received = 0L;
1781         fp = fopen(tempfilename, "w");
1782         if (fp == NULL) {
1783                 CtdlLogPrintf(CTDL_CRIT, "cannot open download file locally: %s\n",
1784                         strerror(errno));
1785                 return;
1786         }
1787
1788         while (bytes_received < download_len) {
1789                 /**
1790                  * If shutting down we can exit here and unlink the temp file.
1791                  * this shouldn't loose us any messages.
1792                  */
1793                 if (CtdlThreadCheckStop())
1794                 {
1795                         fclose(fp);
1796                         unlink(tempfilename);
1797                         return;
1798                 }
1799                 snprintf(buf, sizeof buf, "READ %ld|%ld",
1800                         bytes_received,
1801                      ((download_len - bytes_received > IGNET_PACKET_SIZE)
1802                  ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
1803                 if (sock_puts(sock, buf) < 0) {
1804                         fclose(fp);
1805                         unlink(tempfilename);
1806                         return;
1807                 }
1808                 if (sock_getln(sock, buf, sizeof buf) < 0) {
1809                         fclose(fp);
1810                         unlink(tempfilename);
1811                         return;
1812                 }
1813                 if (buf[0] == '6') {
1814                         plen = extract_long(&buf[4], 0);
1815                         if (sock_read(sock, pbuf, plen, 1) < 0) {
1816                                 fclose(fp);
1817                                 unlink(tempfilename);
1818                                 return;
1819                         }
1820                         fwrite((char *) pbuf, plen, 1, fp);
1821                         bytes_received = bytes_received + plen;
1822                 }
1823         }
1824
1825         fclose(fp);
1826         /** Last chance for shutdown exit */
1827         if (CtdlThreadCheckStop())
1828         {
1829                 unlink(tempfilename);
1830                 return;
1831         }
1832         if (sock_puts(sock, "CLOS") < 0) {
1833                 unlink(tempfilename);
1834                 return;
1835         }
1836         /**
1837          * From here on we must complete or messages will get lost
1838          */
1839         if (sock_getln(sock, buf, sizeof buf) < 0) {
1840                 unlink(tempfilename);
1841                 return;
1842         }
1843         if (download_len > 0) {
1844                 CtdlLogPrintf(CTDL_NOTICE, "Received %ld octets from <%s>\n", download_len, remote_nodename);
1845         }
1846         CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1847         
1848         /* Now copy the temp file to its permanent location.
1849          * (We copy instead of link because they may be on different filesystems)
1850          */
1851         begin_critical_section(S_NETSPOOL);
1852         snprintf(filename, 
1853                          sizeof filename, 
1854                          "%s/%s.%ld",
1855                          ctdl_netin_dir,
1856                          remote_nodename, 
1857                          (long) getpid()
1858         );
1859         fp = fopen(tempfilename, "r");
1860         if (fp != NULL) {
1861                 newfp = fopen(filename, "w");
1862                 if (newfp != NULL) {
1863                         bytes_copied = 0L;
1864                         while (bytes_copied < download_len) {
1865                                 plen = download_len - bytes_copied;
1866                                 if (plen > sizeof buf) {
1867                                         plen = sizeof buf;
1868                                 }
1869                                 fread(buf, plen, 1, fp);
1870                                 fwrite(buf, plen, 1, newfp);
1871                                 bytes_copied += plen;
1872                         }
1873                         fclose(newfp);
1874                 }
1875                 fclose(fp);
1876         }
1877         end_critical_section(S_NETSPOOL);
1878         unlink(tempfilename);
1879 }
1880
1881
1882
1883 /*
1884  * transmit network spool to the remote system
1885  */
1886 void transmit_spool(int sock, char *remote_nodename)
1887 {
1888         char buf[SIZ];
1889         char pbuf[4096];
1890         long plen;
1891         long bytes_to_write, thisblock, bytes_written;
1892         int fd;
1893         char sfname[128];
1894
1895         if (sock_puts(sock, "NUOP") < 0) return;
1896         if (sock_getln(sock, buf, sizeof buf) < 0) return;
1897         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1898         if (buf[0] != '2') {
1899                 return;
1900         }
1901
1902         snprintf(sfname, sizeof sfname, 
1903                          "%s/%s",
1904                          ctdl_netout_dir,
1905                          remote_nodename);
1906         fd = open(sfname, O_RDONLY);
1907         if (fd < 0) {
1908                 if (errno != ENOENT) {
1909                         CtdlLogPrintf(CTDL_CRIT, "cannot open upload file locally: %s\n",
1910                                 strerror(errno));
1911                 }
1912                 return;
1913         }
1914         bytes_written = 0;
1915         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1916                 bytes_to_write = plen;
1917                 while (bytes_to_write > 0L) {
1918                         /** Exit if shutting down */
1919                         if (CtdlThreadCheckStop())
1920                         {
1921                                 close(fd);
1922                                 return;
1923                         }
1924                         
1925                         snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
1926                         if (sock_puts(sock, buf) < 0) {
1927                                 close(fd);
1928                                 return;
1929                         }
1930                         if (sock_getln(sock, buf, sizeof buf) < 0) {
1931                                 close(fd);
1932                                 return;
1933                         }
1934                         thisblock = atol(&buf[4]);
1935                         if (buf[0] == '7') {
1936                                 if (sock_write(sock, pbuf,
1937                                    (int) thisblock) < 0) {
1938                                         close(fd);
1939                                         return;
1940                                 }
1941                                 bytes_to_write -= thisblock;
1942                                 bytes_written += thisblock;
1943                         } else {
1944                                 goto ABORTUPL;
1945                         }
1946                 }
1947         }
1948
1949 ABORTUPL:
1950         close(fd);
1951         /** Last chance for shutdown exit */
1952         if(CtdlThreadCheckStop())
1953                 return;
1954                 
1955         if (sock_puts(sock, "UCLS 1") < 0) return;
1956         /**
1957          * From here on we must complete or messages will get lost
1958          */
1959         if (sock_getln(sock, buf, sizeof buf) < 0) return;
1960         CtdlLogPrintf(CTDL_NOTICE, "Sent %ld octets to <%s>\n",
1961                         bytes_written, remote_nodename);
1962         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1963         if (buf[0] == '2') {
1964                 CtdlLogPrintf(CTDL_DEBUG, "Removing <%s>\n", sfname);
1965                 unlink(sfname);
1966         }
1967 }
1968
1969
1970
1971 /*
1972  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1973  */
1974 void network_poll_node(char *node, char *secret, char *host, char *port) {
1975         int sock;
1976         char buf[SIZ];
1977         char err_buf[SIZ];
1978         char connected_to[SIZ];
1979
1980         if (network_talking_to(node, NTT_CHECK)) return;
1981         network_talking_to(node, NTT_ADD);
1982         CtdlLogPrintf(CTDL_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port);
1983
1984         sock = sock_connect(host, port, "tcp");
1985         if (sock < 0) {
1986                 CtdlLogPrintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno));
1987                 network_talking_to(node, NTT_REMOVE);
1988                 return;
1989         }
1990         
1991         CtdlLogPrintf(CTDL_DEBUG, "Connected!\n");
1992
1993         /* Read the server greeting */
1994         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
1995         CtdlLogPrintf(CTDL_DEBUG, ">%s\n", buf);
1996
1997         /* Check that the remote is who we think it is and warn the Aide if not */
1998         extract_token (connected_to, buf, 1, ' ', sizeof connected_to);
1999         if (strcmp(connected_to, node))
2000         {
2001                 snprintf (err_buf, sizeof(err_buf), "Connected to node \"%s\" but I was expecting to connect to node \"%s\".", connected_to, node);
2002                 aide_message(err_buf, "IGNet Networking error.");
2003         }
2004
2005         /* Identify ourselves */
2006         snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
2007         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
2008         if (sock_puts(sock, buf) <0) goto bail;
2009         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
2010         CtdlLogPrintf(CTDL_DEBUG, ">%s\n", buf);
2011         if (buf[0] != '2') goto bail;
2012
2013         /* At this point we are authenticated. */
2014         if (!CtdlThreadCheckStop())
2015                 receive_spool(sock, node);
2016         if (!CtdlThreadCheckStop())
2017                 transmit_spool(sock, node);
2018
2019         sock_puts(sock, "QUIT");
2020 bail:   sock_close(sock);
2021         network_talking_to(node, NTT_REMOVE);
2022 }
2023
2024
2025
2026 /*
2027  * Poll other Citadel nodes and transfer inbound/outbound network data.
2028  * Set "full" to nonzero to force a poll of every node, or to zero to poll
2029  * only nodes to which we have data to send.
2030  */
2031 void network_poll_other_citadel_nodes(int full_poll) {
2032         int i;
2033         char linebuf[256];
2034         char node[SIZ];
2035         char host[256];
2036         char port[256];
2037         char secret[256];
2038         int poll = 0;
2039         char spoolfile[256];
2040
2041         if (working_ignetcfg == NULL) {
2042                 CtdlLogPrintf(CTDL_DEBUG, "No nodes defined - not polling\n");
2043                 return;
2044         }
2045
2046         /* Use the string tokenizer to grab one line at a time */
2047         for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
2048                 if(CtdlThreadCheckStop())
2049                         return;
2050                 extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
2051                 extract_token(node, linebuf, 0, '|', sizeof node);
2052                 extract_token(secret, linebuf, 1, '|', sizeof secret);
2053                 extract_token(host, linebuf, 2, '|', sizeof host);
2054                 extract_token(port, linebuf, 3, '|', sizeof port);
2055                 if ( !IsEmptyStr(node) && !IsEmptyStr(secret) 
2056                    && !IsEmptyStr(host) && !IsEmptyStr(port)) {
2057                         poll = full_poll;
2058                         if (poll == 0) {
2059                                 snprintf(spoolfile, 
2060                                                  sizeof spoolfile,
2061                                                  "%s/%s",
2062                                                  ctdl_netout_dir, 
2063                                                  node);
2064                                 if (access(spoolfile, R_OK) == 0) {
2065                                         poll = 1;
2066                                 }
2067                         }
2068                         if (poll) {
2069                                 network_poll_node(node, secret, host, port);
2070                         }
2071                 }
2072         }
2073
2074 }
2075
2076
2077
2078
2079 /*
2080  * It's ok if these directories already exist.  Just fail silently.
2081  */
2082 void create_spool_dirs(void) {
2083         mkdir(ctdl_spool_dir, 0700);
2084         chown(ctdl_spool_dir, CTDLUID, (-1));
2085         mkdir(ctdl_netin_dir, 0700);
2086         chown(ctdl_netin_dir, CTDLUID, (-1));
2087         mkdir(ctdl_netout_dir, 0700);
2088         chown(ctdl_netout_dir, CTDLUID, (-1));
2089 }
2090
2091
2092
2093
2094
2095 /*
2096  * network_do_queue()
2097  * 
2098  * Run through the rooms doing various types of network stuff.
2099  */
2100 void *network_do_queue(void *args) {
2101         static time_t last_run = 0L;
2102         struct RoomProcList *ptr;
2103         int full_processing = 1;
2104         struct CitContext networkerCC;
2105
2106         /* Give the networker its own private CitContext */
2107         CtdlFillSystemContext(&networkerCC, "network");
2108         citthread_setspecific(MyConKey, (void *)&networkerCC );
2109
2110         /*
2111          * Run the full set of processing tasks no more frequently
2112          * than once every n seconds
2113          */
2114         if ( (time(NULL) - last_run) < config.c_net_freq ) {
2115                 full_processing = 0;
2116                 CtdlLogPrintf(CTDL_DEBUG, "Network full processing in %ld seconds.\n", config.c_net_freq - (time(NULL)- last_run));
2117         }
2118
2119         /*
2120          * This is a simple concurrency check to make sure only one queue run
2121          * is done at a time.  We could do this with a mutex, but since we
2122          * don't really require extremely fine granularity here, we'll do it
2123          * with a static variable instead.
2124          */
2125         if (doing_queue) return NULL;
2126         doing_queue = 1;
2127
2128         /* Load the IGnet Configuration into memory */
2129         load_working_ignetcfg();
2130
2131         /*
2132          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
2133          * then we poll everyone.  Otherwise we only poll nodes we have stuff
2134          * to send to.
2135          */
2136         network_poll_other_citadel_nodes(full_processing);
2137
2138         /*
2139          * Load the network map and filter list into memory.
2140          */
2141         read_network_map();
2142         filterlist = load_filter_list();
2143
2144         /* 
2145          * Go ahead and run the queue
2146          */
2147         if (full_processing && !CtdlThreadCheckStop()) {
2148                 CtdlLogPrintf(CTDL_DEBUG, "network: loading outbound queue\n");
2149                 ForEachRoom(network_queue_room, NULL);
2150         }
2151
2152         if (rplist != NULL) {
2153                 CtdlLogPrintf(CTDL_DEBUG, "network: running outbound queue\n");
2154                 while (rplist != NULL && !CtdlThreadCheckStop()) {
2155                         char spoolroomname[ROOMNAMELEN];
2156                         safestrncpy(spoolroomname, rplist->name, sizeof spoolroomname);
2157                         begin_critical_section(S_RPLIST);
2158
2159                         /* pop this record off the list */
2160                         ptr = rplist;
2161                         rplist = rplist->next;
2162                         free(ptr);
2163
2164                         /* invalidate any duplicate entries to prevent double processing */
2165                         for (ptr=rplist; ptr!=NULL; ptr=ptr->next) {
2166                                 if (!strcasecmp(ptr->name, spoolroomname)) {
2167                                         ptr->name[0] = 0;
2168                                 }
2169                         }
2170
2171                         end_critical_section(S_RPLIST);
2172                         if (spoolroomname[0] != 0) {
2173                                 network_spoolout_room(spoolroomname);
2174                         }
2175                 }
2176         }
2177
2178         /* If there is anything in the inbound queue, process it */
2179         if (!CtdlThreadCheckStop())
2180                 network_do_spoolin();
2181
2182         /* Save the network map back to disk */
2183         write_network_map();
2184
2185         /* Free the filter list in memory */
2186         free_filter_list(filterlist);
2187         filterlist = NULL;
2188
2189         network_purge_spoolout();
2190
2191         CtdlLogPrintf(CTDL_DEBUG, "network: queue run completed\n");
2192
2193         if (full_processing) {
2194                 last_run = time(NULL);
2195         }
2196
2197         doing_queue = 0;
2198         if (!CtdlThreadCheckStop()) // Only reschedule if system is not stopping
2199                 CtdlThreadSchedule("IGnet Network", CTDLTHREAD_BIGSTACK, network_do_queue, NULL, time(NULL) + 60);
2200         else
2201                 CtdlLogPrintf(CTDL_DEBUG, "network: Task STOPPED.\n");
2202         return NULL;
2203 }
2204
2205
2206 /*
2207  * cmd_netp() - authenticate to the server as another Citadel node polling
2208  *            for network traffic
2209  */
2210 void cmd_netp(char *cmdbuf)
2211 {
2212         char node[256];
2213         char pass[256];
2214         int v;
2215
2216         char secret[256];
2217         char nexthop[256];
2218         char err_buf[SIZ];
2219
2220         /* Authenticate */
2221         extract_token(node, cmdbuf, 0, '|', sizeof node);
2222         extract_token(pass, cmdbuf, 1, '|', sizeof pass);
2223
2224         if (doing_queue) {
2225                 CtdlLogPrintf(CTDL_WARNING, "Network node <%s> refused - spooling", node);
2226                 cprintf("%d spooling - try again in a few minutes\n",
2227                         ERROR + RESOURCE_BUSY);
2228                 return;
2229         }
2230
2231         /* load the IGnet Configuration to check node validity */
2232         load_working_ignetcfg();
2233         v = is_valid_node(nexthop, secret, node);
2234
2235         if (v != 0) {
2236                 snprintf (err_buf, sizeof(err_buf), "Unknown node <%s>\n", node);
2237                 CtdlLogPrintf(CTDL_WARNING, err_buf);
2238                 cprintf("%d authentication failed\n",
2239                         ERROR + PASSWORD_REQUIRED);
2240                 aide_message(err_buf, "IGNet Networking.");
2241                 return;
2242         }
2243
2244         if (strcasecmp(pass, secret)) {
2245                 snprintf (err_buf, sizeof(err_buf), "Bad password for network node <%s>", node);
2246                 CtdlLogPrintf(CTDL_WARNING, err_buf);
2247                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
2248                 aide_message(err_buf, "IGNet Networking.");
2249                 return;
2250         }
2251
2252         if (network_talking_to(node, NTT_CHECK)) {
2253                 CtdlLogPrintf(CTDL_WARNING, "Duplicate session for network node <%s>", node);
2254                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
2255                 return;
2256         }
2257
2258         safestrncpy(CC->net_node, node, sizeof CC->net_node);
2259         network_talking_to(node, NTT_ADD);
2260         CtdlLogPrintf(CTDL_NOTICE, "Network node <%s> logged in\n", CC->net_node);
2261         cprintf("%d authenticated as network node '%s'\n", CIT_OK,
2262                 CC->net_node);
2263 }
2264
2265 int network_room_handler (struct ctdlroom *room)
2266 {
2267         network_queue_room(room, NULL);
2268         return 0;
2269 }
2270
2271 /*
2272  * Module entry point
2273  */
2274 CTDL_MODULE_INIT(network)
2275 {
2276         if (!threading)
2277         {
2278                 create_spool_dirs();
2279                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
2280                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
2281                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
2282                 CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node");
2283                 CtdlRegisterRoomHook(network_room_handler);
2284                 CtdlRegisterCleanupHook(destroy_network_queue_room);
2285         }
2286         else
2287                 CtdlThreadSchedule("IGnet Network", CTDLTHREAD_BIGSTACK, network_do_queue, NULL, 0);
2288         /* return our Subversion id for the Log */
2289         return "$Id$";
2290 }