* Inbound network authentication working. Fixed a bug in the split-horizon
[citadel.git] / citadel / serv_network.c
1 /*
2  * $Id$ 
3  *
4  * This module will eventually replace netproc and some of its utilities.  In
5  * the meantime, it serves as a mailing list manager.
6  *
7  * Copyright (C) 2000-2001 by Art Cancro and others.
8  * This code is released under the terms of the GNU General Public License.
9  *
10  */
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <sys/wait.h>
34 #include <string.h>
35 #include <limits.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
40 #include "support.h"
41 #include "config.h"
42 #include "dynloader.h"
43 #include "room_ops.h"
44 #include "user_ops.h"
45 #include "policy.h"
46 #include "database.h"
47 #include "msgbase.h"
48 #include "tools.h"
49 #include "internet_addressing.h"
50 #include "serv_network.h"
51
52
53 /*
54  * When we do network processing, it's accomplished in two passes; one to
55  * gather a list of rooms and one to actually do them.  It's ok that rplist
56  * is global; this process *only* runs as part of the housekeeping loop and
57  * therefore only one will run at a time.
58  */
59 struct RoomProcList {
60         struct RoomProcList *next;
61         char name[ROOMNAMELEN];
62 };
63
64 struct RoomProcList *rplist = NULL;
65
66
67 /*
68  * We build a map of the Citadel network during network runs.
69  */
70 struct NetMap {
71         struct NetMap *next;
72         char nodename[SIZ];
73         time_t lastcontact;
74         char nexthop[SIZ];
75 };
76
77 struct NetMap *the_netmap = NULL;
78
79
80
81 /* 
82  * Read the network map from its configuration file into memory.
83  */
84 void read_network_map(void) {
85         char *serialized_map = NULL;
86         int i;
87         char buf[SIZ];
88         struct NetMap *nmptr;
89
90         serialized_map = CtdlGetSysConfig(IGNETMAP);
91         if (serialized_map == NULL) return;     /* if null, no entries */
92
93         /* Use the string tokenizer to grab one line at a time */
94         for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
95                 extract_token(buf, serialized_map, i, '\n');
96                 nmptr = (struct NetMap *) mallok(sizeof(struct NetMap));
97                 extract(nmptr->nodename, buf, 0);
98                 nmptr->lastcontact = extract_long(buf, 1);
99                 extract(nmptr->nexthop, buf, 2);
100                 nmptr->next = the_netmap;
101                 the_netmap = nmptr;
102         }
103
104         phree(serialized_map);
105 }
106
107
108 /*
109  * Write the network map from memory back to the configuration file.
110  */
111 void write_network_map(void) {
112         char *serialized_map = NULL;
113         struct NetMap *nmptr;
114
115         serialized_map = strdoop("");
116
117         if (the_netmap != NULL) {
118                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
119                         serialized_map = reallok(serialized_map,
120                                                 (strlen(serialized_map)+SIZ) );
121                         if (strlen(nmptr->nodename) > 0) {
122                                 sprintf(&serialized_map[strlen(serialized_map)],
123                                         "%s|%ld|%s\n",
124                                         nmptr->nodename,
125                                         nmptr->lastcontact,
126                                         nmptr->nexthop);
127                         }
128                 }
129         }
130
131         CtdlPutSysConfig(IGNETMAP, serialized_map);
132         phree(serialized_map);
133
134         /* Now free the list */
135         while (the_netmap != NULL) {
136                 nmptr = the_netmap->next;
137                 phree(the_netmap);
138                 the_netmap = nmptr;
139         }
140 }
141
142
143
144 /* 
145  * Check the network map and determine whether the supplied node name is
146  * valid.  If it is not a neighbor node, supply the name of a neighbor node
147  * which is the next hop.  If it *is* a neighbor node, we also fill in the
148  * shared secret.
149  */
150 int is_valid_node(char *nexthop, char *secret, char *node) {
151         char *ignetcfg = NULL;
152         int i;
153         char linebuf[SIZ];
154         char buf[SIZ];
155         int retval;
156         struct NetMap *nmptr;
157
158         if (node == NULL) {
159                 return(-1);
160         }
161
162         /*
163          * First try the neighbor nodes
164          */
165         ignetcfg = CtdlGetSysConfig(IGNETCFG);
166         if (ignetcfg == NULL) {
167                 if (nexthop != NULL) {
168                         strcpy(nexthop, "");
169                 }
170                 return(-1);
171         }
172
173         retval = (-1);
174         if (nexthop != NULL) {
175                 strcpy(nexthop, "");
176         }
177
178         /* Use the string tokenizer to grab one line at a time */
179         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
180                 extract_token(linebuf, ignetcfg, i, '\n');
181                 extract(buf, linebuf, 0);
182                 if (!strcasecmp(buf, node)) {
183                         if (nexthop != NULL) {
184                                 strcpy(nexthop, "");
185                         }
186                         if (secret != NULL) {
187                                 extract(secret, linebuf, 1);
188                         }
189                         retval = 0;
190                 }
191         }
192
193         phree(ignetcfg);
194         if (retval == 0) {
195                 return(retval);         /* yup, it's a direct neighbor */
196         }
197
198         /*      
199          * If we get to this point we have to see if we know the next hop
200          */
201         if (the_netmap != NULL) {
202                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
203                         if (!strcasecmp(nmptr->nodename, node)) {
204                                 if (nexthop != NULL) {
205                                         strcpy(nexthop, nmptr->nexthop);
206                                 }
207                                 return(0);
208                         }
209                 }
210         }
211
212         /*
213          * If we get to this point, the supplied node name is bogus.
214          */
215         lprintf(5, "Invalid node name <%s>\n", node);
216         return(-1);
217 }
218
219
220
221
222
223 void cmd_gnet(char *argbuf) {
224         char filename[SIZ];
225         char buf[SIZ];
226         FILE *fp;
227
228         if (CtdlAccessCheck(ac_room_aide)) return;
229         assoc_file_name(filename, &CC->quickroom, "netconfigs");
230         cprintf("%d Network settings for room #%ld <%s>\n",
231                 LISTING_FOLLOWS,
232                 CC->quickroom.QRnumber, CC->quickroom.QRname);
233
234         fp = fopen(filename, "r");
235         if (fp != NULL) {
236                 while (fgets(buf, sizeof buf, fp) != NULL) {
237                         buf[strlen(buf)-1] = 0;
238                         cprintf("%s\n", buf);
239                 }
240                 fclose(fp);
241         }
242
243         cprintf("000\n");
244 }
245
246
247 void cmd_snet(char *argbuf) {
248         char tempfilename[SIZ];
249         char filename[SIZ];
250         char buf[SIZ];
251         FILE *fp;
252
253         if (CtdlAccessCheck(ac_room_aide)) return;
254         safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
255         assoc_file_name(filename, &CC->quickroom, "netconfigs");
256
257         fp = fopen(tempfilename, "w");
258         if (fp == NULL) {
259                 cprintf("%d Cannot open %s: %s\n",
260                         ERROR+INTERNAL_ERROR,
261                         tempfilename,
262                         strerror(errno));
263         }
264
265         cprintf("%d %s\n", SEND_LISTING, tempfilename);
266         while (client_gets(buf), strcmp(buf, "000")) {
267                 fprintf(fp, "%s\n", buf);
268         }
269         fclose(fp);
270
271         /* Now copy the temp file to its permanent location
272          * (We use /bin/mv instead of link() because they may be on
273          * different filesystems)
274          */
275         unlink(filename);
276         snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
277         system(buf);
278 }
279
280
281
282 /*
283  * Spools out one message from the list.
284  */
285 void network_spool_msg(long msgnum, void *userdata) {
286         struct SpoolControl *sc;
287         struct namelist *nptr;
288         int err;
289         int i;
290         char *instr = NULL;
291         char *newpath = NULL;
292         size_t instr_len = SIZ;
293         struct CtdlMessage *msg = NULL;
294         struct CtdlMessage *imsg;
295         struct ser_ret sermsg;
296         FILE *fp;
297         char filename[SIZ];
298         char buf[SIZ];
299         int bang = 0;
300         int send = 1;
301         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
302
303         sc = (struct SpoolControl *)userdata;
304
305         /*
306          * Process mailing list recipients
307          */
308         if (sc->listrecps != NULL) {
309         
310                 /* First, copy it to the spoolout room */
311                 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
312                 if (err != 0) return;
313
314                 /* 
315                  * Figure out how big a buffer we need to allocate
316                  */
317                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
318                         instr_len = instr_len + strlen(nptr->name);
319                 }
320         
321                 /*
322                  * allocate...
323                  */
324                 lprintf(9, "Generating delivery instructions\n");
325                 instr = mallok(instr_len);
326                 if (instr == NULL) {
327                         lprintf(1, "Cannot allocate %d bytes for instr...\n",
328                                 instr_len);
329                         abort();
330                 }
331                 sprintf(instr,
332                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
333                         "bounceto|postmaster@%s\n" ,
334                         SPOOLMIME, msgnum, time(NULL), config.c_fqdn );
335         
336                 /* Generate delivery instructions for each recipient */
337                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
338                         sprintf(&instr[strlen(instr)], "remote|%s|0||\n",
339                                 nptr->name);
340                 }
341         
342                 /*
343                  * Generate a message from the instructions
344                  */
345                 imsg = mallok(sizeof(struct CtdlMessage));
346                 memset(imsg, 0, sizeof(struct CtdlMessage));
347                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
348                 imsg->cm_anon_type = MES_NORMAL;
349                 imsg->cm_format_type = FMT_RFC822;
350                 imsg->cm_fields['A'] = strdoop("Citadel");
351                 imsg->cm_fields['M'] = instr;
352         
353                 /* Save delivery instructions in spoolout room */
354                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
355                 CtdlFreeMessage(imsg);
356         }
357         
358         /*
359          * Process IGnet push shares
360          */
361         if (sc->ignet_push_shares != NULL) {
362         
363                 msg = CtdlFetchMessage(msgnum);
364                 if (msg != NULL) {
365
366                         /* Prepend our node name to the Path field whenever
367                          * sending a message to another IGnet node
368                          */
369                         if (msg->cm_fields['P'] == NULL) {
370                                 msg->cm_fields['P'] = strdoop("username");
371                         }
372                         newpath = mallok(strlen(msg->cm_fields['P']) + 
373                                         strlen(config.c_nodename) + 2);
374                         sprintf(newpath, "%s!%s", config.c_nodename,
375                                         msg->cm_fields['P']);
376                         phree(msg->cm_fields['P']);
377                         msg->cm_fields['P'] = newpath;
378
379                         /*
380                          * Force the message to appear in the correct room
381                          * on the far end by setting the C field correctly
382                          */
383                         if (msg->cm_fields['C'] != NULL) {
384                                 phree(msg->cm_fields['C']);
385                         }
386                         msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
387
388                         /*
389                          * Determine if this message is set to be deleted
390                          * after sending out on the network
391                          */
392                         if (msg->cm_fields['S'] != NULL) {
393                                 if (!strcasecmp(msg->cm_fields['S'],
394                                    "CANCEL")) {
395                                         delete_after_send = 1;
396                                 }
397                         }
398
399                         /* 
400                          * Now serialize it for transmission
401                          */
402                         serialize_message(&sermsg, msg);
403
404                         /* Now send it to every node */
405                         for (nptr = sc->ignet_push_shares; nptr != NULL;
406                             nptr = nptr->next) {
407
408                                 send = 1;
409
410                                 /* Check for valid node name */
411                                 if (is_valid_node(NULL,NULL,nptr->name) != 0) {
412                                         lprintf(3, "Invalid node <%s>\n",
413                                                 nptr->name);
414                                         send = 0;
415                                 }
416
417                                 /* Check for split horizon */
418                                 lprintf(9, "Path is %s\n", msg->cm_fields['P']);
419                                 bang = num_tokens(msg->cm_fields['P'], '!');
420                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
421                                         extract_token(buf, msg->cm_fields['P'],
422                                                 i, '!');
423                                         if (!strcasecmp(buf, nptr->name)) {
424                                                 send = 0;
425                                         }
426                                 }
427
428                                 /* Send the message */
429                                 if (send == 1) {
430                                         sprintf(filename,
431                                                 "./network/spoolout/%s",
432                                                 nptr->name);
433                                         fp = fopen(filename, "ab");
434                                         if (fp != NULL) {
435                                                 fwrite(sermsg.ser,
436                                                         sermsg.len, 1, fp);
437                                                 fclose(fp);
438                                         }
439                                 }
440                         }
441                         phree(sermsg.ser);
442                         CtdlFreeMessage(msg);
443                 }
444         }
445
446         /* update lastsent */
447         sc->lastsent = msgnum;
448
449         /* Delete this message if delete-after-send is set */
450         if (delete_after_send) {
451                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
452         }
453
454 }
455         
456
457
458
459 /*
460  * Batch up and send all outbound traffic from the current room
461  */
462 void network_spoolout_room(char *room_to_spool) {
463         char filename[SIZ];
464         char buf[SIZ];
465         char instr[SIZ];
466         FILE *fp;
467         struct SpoolControl sc;
468         /* struct namelist *digestrecps = NULL; */
469         struct namelist *nptr;
470
471         lprintf(7, "Spooling <%s>\n", room_to_spool);
472         if (getroom(&CC->quickroom, room_to_spool) != 0) {
473                 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
474                 return;
475         }
476
477         memset(&sc, 0, sizeof(struct SpoolControl));
478         assoc_file_name(filename, &CC->quickroom, "netconfigs");
479
480         fp = fopen(filename, "r");
481         if (fp == NULL) {
482                 lprintf(7, "Outbound batch processing skipped for <%s>\n",
483                         CC->quickroom.QRname);
484                 return;
485         }
486
487         lprintf(5, "Outbound batch processing started for <%s>\n",
488                 CC->quickroom.QRname);
489
490         while (fgets(buf, sizeof buf, fp) != NULL) {
491                 buf[strlen(buf)-1] = 0;
492
493                 extract(instr, buf, 0);
494                 if (!strcasecmp(instr, "lastsent")) {
495                         sc.lastsent = extract_long(buf, 1);
496                 }
497                 else if (!strcasecmp(instr, "listrecp")) {
498                         nptr = (struct namelist *)
499                                 mallok(sizeof(struct namelist));
500                         nptr->next = sc.listrecps;
501                         extract(nptr->name, buf, 1);
502                         sc.listrecps = nptr;
503                 }
504                 else if (!strcasecmp(instr, "ignet_push_share")) {
505                         nptr = (struct namelist *)
506                                 mallok(sizeof(struct namelist));
507                         nptr->next = sc.ignet_push_shares;
508                         extract(nptr->name, buf, 1);
509                         sc.ignet_push_shares = nptr;
510                 }
511
512
513         }
514         fclose(fp);
515
516
517         /* Do something useful */
518         CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
519                 network_spool_msg, &sc);
520
521
522         /* Now rewrite the config file */
523         fp = fopen(filename, "w");
524         if (fp == NULL) {
525                 lprintf(1, "ERROR: cannot open %s: %s\n",
526                         filename, strerror(errno));
527         }
528         else {
529                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
530
531                 /* Write out the listrecps while freeing from memory at the
532                  * same time.  Am I clever or what?  :)
533                  */
534                 while (sc.listrecps != NULL) {
535                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
536                         nptr = sc.listrecps->next;
537                         phree(sc.listrecps);
538                         sc.listrecps = nptr;
539                 }
540                 while (sc.ignet_push_shares != NULL) {
541                         fprintf(fp, "ignet_push_share|%s\n",
542                                 sc.ignet_push_shares->name);
543                         nptr = sc.ignet_push_shares->next;
544                         phree(sc.ignet_push_shares);
545                         sc.ignet_push_shares = nptr;
546                 }
547
548                 fclose(fp);
549         }
550
551         lprintf(5, "Outbound batch processing finished for <%s>\n",
552                 CC->quickroom.QRname);
553 }
554
555
556 /*
557  * Batch up and send all outbound traffic from the current room
558  */
559 void network_queue_room(struct quickroom *qrbuf, void *data) {
560         struct RoomProcList *ptr;
561
562         ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
563         if (ptr == NULL) return;
564
565         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
566         ptr->next = rplist;
567         rplist = ptr;
568 }
569
570
571 /*
572  * Learn topology from path fields
573  */
574 void network_learn_topology(char *node, char *path) {
575         char nexthop[SIZ];
576         struct NetMap *nmptr;
577
578         strcpy(nexthop, "");
579
580         if (num_tokens(path, '!') < 3) return;
581         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
582                 if (!strcasecmp(nmptr->nodename, node)) {
583                         extract_token(nmptr->nexthop, path, 0, '!');
584                         nmptr->lastcontact = time(NULL);
585                         return;
586                 }
587         }
588
589         /* If we got here then it's not in the map, so add it. */
590         nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
591         strcpy(nmptr->nodename, node);
592         nmptr->lastcontact = time(NULL);
593         extract_token(nmptr->nexthop, path, 0, '!');
594         nmptr->next = the_netmap;
595         the_netmap = nmptr;
596 }
597
598
599
600 /*
601  * Process a buffer containing a single message from a single file
602  * from the inbound queue 
603  */
604 void network_process_buffer(char *buffer, long size) {
605         struct CtdlMessage *msg;
606         long pos;
607         int field;
608         int a;
609         int e = MES_LOCAL;
610         struct usersupp tempUS;
611         char recp[SIZ];
612         char target_room[ROOMNAMELEN];
613         struct ser_ret sermsg;
614         char *oldpath = NULL;
615         char filename[SIZ];
616         FILE *fp;
617
618         /* Set default target room to trash */
619         strcpy(target_room, TWITROOM);
620
621         /* Load the message into memory */
622         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
623         memset(msg, 0, sizeof(struct CtdlMessage));
624         msg->cm_magic = CTDLMESSAGE_MAGIC;
625         msg->cm_anon_type = buffer[1];
626         msg->cm_format_type = buffer[2];
627
628         for (pos = 3; pos < size; ++pos) {
629                 field = buffer[pos];
630                 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
631                 pos = pos + strlen(&buffer[(int)pos]);
632         }
633
634         /* Check for message routing */
635         if (msg->cm_fields['D'] != NULL) {
636                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
637
638                         /* route the message */
639                         if (is_valid_node(NULL, NULL,
640                            msg->cm_fields['D']) == 0) {
641
642                                 /* prepend our node to the path */
643                                 if (msg->cm_fields['P'] != NULL) {
644                                         oldpath = msg->cm_fields['P'];
645                                         msg->cm_fields['P'] = NULL;
646                                 }
647                                 else {
648                                         oldpath = strdoop("unknown_user");
649                                 }
650                                 msg->cm_fields['P'] =
651                                         mallok(strlen(oldpath) + SIZ);
652                                 sprintf(msg->cm_fields['P'], "%s!%s",
653                                         config.c_nodename, oldpath);
654                                 phree(oldpath);
655
656                                 /* serialize the message */
657                                 serialize_message(&sermsg, msg);
658
659                                 /* now send it */
660                                 sprintf(filename,
661                                         "./network/spoolout/%s",
662                                         msg->cm_fields['D']);
663                                 fp = fopen(filename, "ab");
664                                 if (fp != NULL) {
665                                         fwrite(sermsg.ser,
666                                                 sermsg.len, 1, fp);
667                                         fclose(fp);
668                                 }
669                                 phree(sermsg.ser);
670                                 CtdlFreeMessage(msg);
671                                 return;
672                         }
673                         
674                         else {  /* invalid destination node name */
675
676                                 /* FIXME bounce the msg */
677
678                         }
679                 }
680         }
681
682         /* FIXME check to see if we already have this message */
683
684         /* Learn network topology from the path */
685         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
686                 network_learn_topology(msg->cm_fields['N'], 
687                                         msg->cm_fields['P']);
688         }
689
690         /* Does it have a recipient?  If so, validate it... */
691         if (msg->cm_fields['R'] != NULL) {
692
693                 safestrncpy(recp, msg->cm_fields['R'], sizeof(recp));
694
695                 e = alias(recp);        /* alias and mail type */
696                 if ((recp[0] == 0) || (e == MES_ERROR)) {
697
698                         /* FIXME bounce the msg */
699
700                 }
701                 else if (e == MES_LOCAL) {
702                         a = getuser(&tempUS, recp);
703                         if (a != 0) {
704                                 /* FIXME bounce the msg */
705                         }
706                         else {
707                                 MailboxName(target_room, &tempUS, MAILROOM);
708                         }
709                 }
710         }
711
712         else if (msg->cm_fields['C'] != NULL) {
713                 safestrncpy(target_room,
714                         msg->cm_fields['C'],
715                         sizeof target_room);
716         }
717
718         else if (msg->cm_fields['O'] != NULL) {
719                 safestrncpy(target_room,
720                         msg->cm_fields['O'],
721                         sizeof target_room);
722         }
723
724         /* save the message into a room */
725         msg->cm_flags = CM_SKIP_HOOKS;
726         CtdlSaveMsg(msg, "", target_room, 0);
727         CtdlFreeMessage(msg);
728 }
729
730
731 /*
732  * Process a single message from a single file from the inbound queue 
733  */
734 void network_process_message(FILE *fp, long msgstart, long msgend) {
735         long hold_pos;
736         long size;
737         char *buffer;
738
739         hold_pos = ftell(fp);
740         size = msgend - msgstart + 1;
741         buffer = mallok(size);
742         if (buffer != NULL) {
743                 fseek(fp, msgstart, SEEK_SET);
744                 fread(buffer, size, 1, fp);
745                 network_process_buffer(buffer, size);
746                 phree(buffer);
747         }
748
749         fseek(fp, hold_pos, SEEK_SET);
750 }
751
752
753 /*
754  * Process a single file from the inbound queue 
755  */
756 void network_process_file(char *filename) {
757         FILE *fp;
758         long msgstart = (-1L);
759         long msgend = (-1L);
760         long msgcur = 0L;
761         int ch;
762
763         lprintf(7, "network: processing <%s>\n", filename);
764
765         fp = fopen(filename, "rb");
766         if (fp == NULL) {
767                 lprintf(5, "Error opening %s: %s\n",
768                         filename, strerror(errno));
769                 return;
770         }
771
772         /* Look for messages in the data stream and break them out */
773         while (ch = getc(fp), ch >= 0) {
774         
775                 if (ch == 255) {
776                         if (msgstart >= 0L) {
777                                 msgend = msgcur - 1;
778                                 network_process_message(fp, msgstart, msgend);
779                         }
780                         msgstart = msgcur;
781                 }
782
783                 ++msgcur;
784         }
785
786         msgend = msgcur - 1;
787         if (msgstart >= 0L) {
788                 network_process_message(fp, msgstart, msgend);
789         }
790
791         fclose(fp);
792         unlink(filename);
793 }
794
795
796 /*
797  * Process anything in the inbound queue
798  */
799 void network_do_spoolin(void) {
800         DIR *dp;
801         struct dirent *d;
802         char filename[SIZ];
803
804         dp = opendir("./network/spoolin");
805         if (dp == NULL) return;
806
807         while (d = readdir(dp), d != NULL) {
808                 sprintf(filename, "./network/spoolin/%s", d->d_name);
809                 network_process_file(filename);
810         }
811
812
813         closedir(dp);
814 }
815
816
817 /*
818  * network_do_queue()
819  * 
820  * Run through the rooms doing various types of network stuff.
821  */
822 void network_do_queue(void) {
823         static int doing_queue = 0;
824         static time_t last_run = 0L;
825         struct RoomProcList *ptr;
826
827         /*
828          * Run no more frequently than once every n seconds
829          */
830         if ( (time(NULL) - last_run) < NETWORK_QUEUE_FREQUENCY ) return;
831
832         /*
833          * This is a simple concurrency check to make sure only one queue run
834          * is done at a time.  We could do this with a mutex, but since we
835          * don't really require extremely fine granularity here, we'll do it
836          * with a static variable instead.
837          */
838         if (doing_queue) return;
839         doing_queue = 1;
840         last_run = time(NULL);
841
842         read_network_map();
843
844         /* 
845          * Go ahead and run the queue
846          */
847         lprintf(7, "network: loading outbound queue\n");
848         ForEachRoom(network_queue_room, NULL);
849
850         lprintf(7, "network: running outbound queue\n");
851         while (rplist != NULL) {
852                 network_spoolout_room(rplist->name);
853                 ptr = rplist;
854                 rplist = rplist->next;
855                 phree(ptr);
856         }
857
858         lprintf(7, "network: processing inbound queue\n");
859         network_do_spoolin();
860
861         write_network_map();
862
863         lprintf(7, "network: queue run completed\n");
864         doing_queue = 0;
865 }
866
867
868
869 /*
870  * cmd_netp() - authenticate to the server as another Citadel node polling
871  *              for network traffic
872  */
873 void cmd_netp(char *cmdbuf)
874 {
875         char node[SIZ];
876         char pass[SIZ];
877
878         char secret[SIZ];
879         char nexthop[SIZ];
880
881         extract(node, cmdbuf, 0);
882         extract(pass, cmdbuf, 1);
883
884         if (is_valid_node(nexthop, secret, node) != 0) {
885                 cprintf("%d authentication failed\n", ERROR);
886                 return;
887         }
888
889         if (strcasecmp(pass, secret)) {
890                 cprintf("%d authentication failed\n", ERROR);
891                 return;
892         }
893
894         safestrncpy(CC->net_node, node, sizeof CC->net_node);
895         cprintf("%d authenticated as network node '%s'\n", OK,
896                 CC->net_node);
897 }
898
899
900
901 /*
902  * Module entry point
903  */
904 char *Dynamic_Module_Init(void)
905 {
906         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
907         CtdlRegisterProtoHook(cmd_snet, "SNET", "Get network config");
908         CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
909         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
910         return "$Id$";
911 }