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