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