b18738f7f2dd50b0f2005b7a576706aa220189d5
[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 /*
13  * FIXME do something about concurrency issues:
14  * 1. Don't allow the two nodes to poll each other at the same time
15  * 2. Don't allow polls during network processing
16  * 3. Kill Bill Gates using either a chainsaw or a wood chipper
17  */
18
19 #include "sysdep.h"
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <dirent.h>
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
31 # include <time.h>
32 #else
33 # if HAVE_SYS_TIME_H
34 #  include <sys/time.h>
35 # else
36 #  include <time.h>
37 # endif
38 #endif
39
40 #include <sys/wait.h>
41 #include <string.h>
42 #include <limits.h>
43 #include "citadel.h"
44 #include "server.h"
45 #include "sysdep_decls.h"
46 #include "citserver.h"
47 #include "support.h"
48 #include "config.h"
49 #include "dynloader.h"
50 #include "room_ops.h"
51 #include "user_ops.h"
52 #include "policy.h"
53 #include "database.h"
54 #include "msgbase.h"
55 #include "tools.h"
56 #include "internet_addressing.h"
57 #include "serv_network.h"
58 #include "clientsocket.h"
59
60
61 /*
62  * When we do network processing, it's accomplished in two passes; one to
63  * gather a list of rooms and one to actually do them.  It's ok that rplist
64  * is global; this process *only* runs as part of the housekeeping loop and
65  * therefore only one will run at a time.
66  */
67 struct RoomProcList {
68         struct RoomProcList *next;
69         char name[ROOMNAMELEN];
70 };
71
72 struct RoomProcList *rplist = NULL;
73
74
75 /*
76  * We build a map of the Citadel network during network runs.
77  */
78 struct NetMap {
79         struct NetMap *next;
80         char nodename[SIZ];
81         time_t lastcontact;
82         char nexthop[SIZ];
83 };
84
85 struct NetMap *the_netmap = NULL;
86
87
88
89 /* 
90  * Read the network map from its configuration file into memory.
91  */
92 void read_network_map(void) {
93         char *serialized_map = NULL;
94         int i;
95         char buf[SIZ];
96         struct NetMap *nmptr;
97
98         serialized_map = CtdlGetSysConfig(IGNETMAP);
99         if (serialized_map == NULL) return;     /* if null, no entries */
100
101         /* Use the string tokenizer to grab one line at a time */
102         for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
103                 extract_token(buf, serialized_map, i, '\n');
104                 nmptr = (struct NetMap *) mallok(sizeof(struct NetMap));
105                 extract(nmptr->nodename, buf, 0);
106                 nmptr->lastcontact = extract_long(buf, 1);
107                 extract(nmptr->nexthop, buf, 2);
108                 nmptr->next = the_netmap;
109                 the_netmap = nmptr;
110         }
111
112         phree(serialized_map);
113 }
114
115
116 /*
117  * Write the network map from memory back to the configuration file.
118  */
119 void write_network_map(void) {
120         char *serialized_map = NULL;
121         struct NetMap *nmptr;
122
123         serialized_map = strdoop("");
124
125         if (the_netmap != NULL) {
126                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
127                         serialized_map = reallok(serialized_map,
128                                                 (strlen(serialized_map)+SIZ) );
129                         if (strlen(nmptr->nodename) > 0) {
130                                 sprintf(&serialized_map[strlen(serialized_map)],
131                                         "%s|%ld|%s\n",
132                                         nmptr->nodename,
133                                         nmptr->lastcontact,
134                                         nmptr->nexthop);
135                         }
136                 }
137         }
138
139         CtdlPutSysConfig(IGNETMAP, serialized_map);
140         phree(serialized_map);
141
142         /* Now free the list */
143         while (the_netmap != NULL) {
144                 nmptr = the_netmap->next;
145                 phree(the_netmap);
146                 the_netmap = nmptr;
147         }
148 }
149
150
151
152 /* 
153  * Check the network map and determine whether the supplied node name is
154  * valid.  If it is not a neighbor node, supply the name of a neighbor node
155  * which is the next hop.  If it *is* a neighbor node, we also fill in the
156  * shared secret.
157  */
158 int is_valid_node(char *nexthop, char *secret, char *node) {
159         char *ignetcfg = NULL;
160         int i;
161         char linebuf[SIZ];
162         char buf[SIZ];
163         int retval;
164         struct NetMap *nmptr;
165
166         if (node == NULL) {
167                 return(-1);
168         }
169
170         /*
171          * First try the neighbor nodes
172          */
173         ignetcfg = CtdlGetSysConfig(IGNETCFG);
174         if (ignetcfg == NULL) {
175                 if (nexthop != NULL) {
176                         strcpy(nexthop, "");
177                 }
178                 return(-1);
179         }
180
181         retval = (-1);
182         if (nexthop != NULL) {
183                 strcpy(nexthop, "");
184         }
185
186         /* Use the string tokenizer to grab one line at a time */
187         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
188                 extract_token(linebuf, ignetcfg, i, '\n');
189                 extract(buf, linebuf, 0);
190                 if (!strcasecmp(buf, node)) {
191                         if (nexthop != NULL) {
192                                 strcpy(nexthop, "");
193                         }
194                         if (secret != NULL) {
195                                 extract(secret, linebuf, 1);
196                         }
197                         retval = 0;
198                 }
199         }
200
201         phree(ignetcfg);
202         if (retval == 0) {
203                 return(retval);         /* yup, it's a direct neighbor */
204         }
205
206         /*      
207          * If we get to this point we have to see if we know the next hop
208          */
209         if (the_netmap != NULL) {
210                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
211                         if (!strcasecmp(nmptr->nodename, node)) {
212                                 if (nexthop != NULL) {
213                                         strcpy(nexthop, nmptr->nexthop);
214                                 }
215                                 return(0);
216                         }
217                 }
218         }
219
220         /*
221          * If we get to this point, the supplied node name is bogus.
222          */
223         lprintf(5, "Invalid node name <%s>\n", node);
224         return(-1);
225 }
226
227
228
229
230
231 void cmd_gnet(char *argbuf) {
232         char filename[SIZ];
233         char buf[SIZ];
234         FILE *fp;
235
236         if (CtdlAccessCheck(ac_room_aide)) return;
237         assoc_file_name(filename, &CC->quickroom, "netconfigs");
238         cprintf("%d Network settings for room #%ld <%s>\n",
239                 LISTING_FOLLOWS,
240                 CC->quickroom.QRnumber, CC->quickroom.QRname);
241
242         fp = fopen(filename, "r");
243         if (fp != NULL) {
244                 while (fgets(buf, sizeof buf, fp) != NULL) {
245                         buf[strlen(buf)-1] = 0;
246                         cprintf("%s\n", buf);
247                 }
248                 fclose(fp);
249         }
250
251         cprintf("000\n");
252 }
253
254
255 void cmd_snet(char *argbuf) {
256         char tempfilename[SIZ];
257         char filename[SIZ];
258         char buf[SIZ];
259         FILE *fp;
260
261         if (CtdlAccessCheck(ac_room_aide)) return;
262         safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
263         assoc_file_name(filename, &CC->quickroom, "netconfigs");
264
265         fp = fopen(tempfilename, "w");
266         if (fp == NULL) {
267                 cprintf("%d Cannot open %s: %s\n",
268                         ERROR+INTERNAL_ERROR,
269                         tempfilename,
270                         strerror(errno));
271         }
272
273         cprintf("%d %s\n", SEND_LISTING, tempfilename);
274         while (client_gets(buf), strcmp(buf, "000")) {
275                 fprintf(fp, "%s\n", buf);
276         }
277         fclose(fp);
278
279         /* Now copy the temp file to its permanent location
280          * (We use /bin/mv instead of link() because they may be on
281          * different filesystems)
282          */
283         unlink(filename);
284         snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
285         system(buf);
286 }
287
288
289
290 /*
291  * Spools out one message from the list.
292  */
293 void network_spool_msg(long msgnum, void *userdata) {
294         struct SpoolControl *sc;
295         struct namelist *nptr;
296         int err;
297         int i;
298         char *instr = NULL;
299         char *newpath = NULL;
300         size_t instr_len = SIZ;
301         struct CtdlMessage *msg = NULL;
302         struct CtdlMessage *imsg;
303         struct ser_ret sermsg;
304         FILE *fp;
305         char filename[SIZ];
306         char buf[SIZ];
307         int bang = 0;
308         int send = 1;
309         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
310
311         sc = (struct SpoolControl *)userdata;
312
313         /*
314          * Process mailing list recipients
315          */
316         if (sc->listrecps != NULL) {
317         
318                 /* First, copy it to the spoolout room */
319                 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
320                 if (err != 0) return;
321
322                 /* 
323                  * Figure out how big a buffer we need to allocate
324                  */
325                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
326                         instr_len = instr_len + strlen(nptr->name);
327                 }
328         
329                 /*
330                  * allocate...
331                  */
332                 lprintf(9, "Generating delivery instructions\n");
333                 instr = mallok(instr_len);
334                 if (instr == NULL) {
335                         lprintf(1, "Cannot allocate %d bytes for instr...\n",
336                                 instr_len);
337                         abort();
338                 }
339                 sprintf(instr,
340                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
341                         "bounceto|postmaster@%s\n" ,
342                         SPOOLMIME, msgnum, time(NULL), config.c_fqdn );
343         
344                 /* Generate delivery instructions for each recipient */
345                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
346                         sprintf(&instr[strlen(instr)], "remote|%s|0||\n",
347                                 nptr->name);
348                 }
349         
350                 /*
351                  * Generate a message from the instructions
352                  */
353                 imsg = mallok(sizeof(struct CtdlMessage));
354                 memset(imsg, 0, sizeof(struct CtdlMessage));
355                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
356                 imsg->cm_anon_type = MES_NORMAL;
357                 imsg->cm_format_type = FMT_RFC822;
358                 imsg->cm_fields['A'] = strdoop("Citadel");
359                 imsg->cm_fields['M'] = instr;
360         
361                 /* Save delivery instructions in spoolout room */
362                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
363                 CtdlFreeMessage(imsg);
364         }
365         
366         /*
367          * Process IGnet push shares
368          */
369         if (sc->ignet_push_shares != NULL) {
370         
371                 msg = CtdlFetchMessage(msgnum);
372                 if (msg != NULL) {
373
374                         /* Prepend our node name to the Path field whenever
375                          * sending a message to another IGnet node
376                          */
377                         if (msg->cm_fields['P'] == NULL) {
378                                 msg->cm_fields['P'] = strdoop("username");
379                         }
380                         newpath = mallok(strlen(msg->cm_fields['P']) + 
381                                         strlen(config.c_nodename) + 2);
382                         sprintf(newpath, "%s!%s", config.c_nodename,
383                                         msg->cm_fields['P']);
384                         phree(msg->cm_fields['P']);
385                         msg->cm_fields['P'] = newpath;
386
387                         /*
388                          * Force the message to appear in the correct room
389                          * on the far end by setting the C field correctly
390                          */
391                         if (msg->cm_fields['C'] != NULL) {
392                                 phree(msg->cm_fields['C']);
393                         }
394                         msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
395
396                         /*
397                          * Determine if this message is set to be deleted
398                          * after sending out on the network
399                          */
400                         if (msg->cm_fields['S'] != NULL) {
401                                 if (!strcasecmp(msg->cm_fields['S'],
402                                    "CANCEL")) {
403                                         delete_after_send = 1;
404                                 }
405                         }
406
407                         /* 
408                          * Now serialize it for transmission
409                          */
410                         serialize_message(&sermsg, msg);
411
412                         /* Now send it to every node */
413                         for (nptr = sc->ignet_push_shares; nptr != NULL;
414                             nptr = nptr->next) {
415
416                                 send = 1;
417
418                                 /* Check for valid node name */
419                                 if (is_valid_node(NULL,NULL,nptr->name) != 0) {
420                                         lprintf(3, "Invalid node <%s>\n",
421                                                 nptr->name);
422                                         send = 0;
423                                 }
424
425                                 /* Check for split horizon */
426                                 lprintf(9, "Path is %s\n", msg->cm_fields['P']);
427                                 bang = num_tokens(msg->cm_fields['P'], '!');
428                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
429                                         extract_token(buf, msg->cm_fields['P'],
430                                                 i, '!');
431                                         if (!strcasecmp(buf, nptr->name)) {
432                                                 send = 0;
433                                         }
434                                 }
435
436                                 /* Send the message */
437                                 if (send == 1) {
438                                         sprintf(filename,
439                                                 "./network/spoolout/%s",
440                                                 nptr->name);
441                                         fp = fopen(filename, "ab");
442                                         if (fp != NULL) {
443                                                 fwrite(sermsg.ser,
444                                                         sermsg.len, 1, fp);
445                                                 fclose(fp);
446                                         }
447                                 }
448                         }
449                         phree(sermsg.ser);
450                         CtdlFreeMessage(msg);
451                 }
452         }
453
454         /* update lastsent */
455         sc->lastsent = msgnum;
456
457         /* Delete this message if delete-after-send is set */
458         if (delete_after_send) {
459                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
460         }
461
462 }
463         
464
465
466
467 /*
468  * Batch up and send all outbound traffic from the current room
469  */
470 void network_spoolout_room(char *room_to_spool) {
471         char filename[SIZ];
472         char buf[SIZ];
473         char instr[SIZ];
474         FILE *fp;
475         struct SpoolControl sc;
476         /* struct namelist *digestrecps = NULL; */
477         struct namelist *nptr;
478
479         lprintf(7, "Spooling <%s>\n", room_to_spool);
480         if (getroom(&CC->quickroom, room_to_spool) != 0) {
481                 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
482                 return;
483         }
484
485         memset(&sc, 0, sizeof(struct SpoolControl));
486         assoc_file_name(filename, &CC->quickroom, "netconfigs");
487
488         fp = fopen(filename, "r");
489         if (fp == NULL) {
490                 lprintf(7, "Outbound batch processing skipped for <%s>\n",
491                         CC->quickroom.QRname);
492                 return;
493         }
494
495         lprintf(5, "Outbound batch processing started for <%s>\n",
496                 CC->quickroom.QRname);
497
498         while (fgets(buf, sizeof buf, fp) != NULL) {
499                 buf[strlen(buf)-1] = 0;
500
501                 extract(instr, buf, 0);
502                 if (!strcasecmp(instr, "lastsent")) {
503                         sc.lastsent = extract_long(buf, 1);
504                 }
505                 else if (!strcasecmp(instr, "listrecp")) {
506                         nptr = (struct namelist *)
507                                 mallok(sizeof(struct namelist));
508                         nptr->next = sc.listrecps;
509                         extract(nptr->name, buf, 1);
510                         sc.listrecps = nptr;
511                 }
512                 else if (!strcasecmp(instr, "ignet_push_share")) {
513                         nptr = (struct namelist *)
514                                 mallok(sizeof(struct namelist));
515                         nptr->next = sc.ignet_push_shares;
516                         extract(nptr->name, buf, 1);
517                         sc.ignet_push_shares = nptr;
518                 }
519
520
521         }
522         fclose(fp);
523
524
525         /* Do something useful */
526         CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
527                 network_spool_msg, &sc);
528
529
530         /* Now rewrite the config file */
531         fp = fopen(filename, "w");
532         if (fp == NULL) {
533                 lprintf(1, "ERROR: cannot open %s: %s\n",
534                         filename, strerror(errno));
535         }
536         else {
537                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
538
539                 /* Write out the listrecps while freeing from memory at the
540                  * same time.  Am I clever or what?  :)
541                  */
542                 while (sc.listrecps != NULL) {
543                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
544                         nptr = sc.listrecps->next;
545                         phree(sc.listrecps);
546                         sc.listrecps = nptr;
547                 }
548                 while (sc.ignet_push_shares != NULL) {
549                         fprintf(fp, "ignet_push_share|%s\n",
550                                 sc.ignet_push_shares->name);
551                         nptr = sc.ignet_push_shares->next;
552                         phree(sc.ignet_push_shares);
553                         sc.ignet_push_shares = nptr;
554                 }
555
556                 fclose(fp);
557         }
558
559         lprintf(5, "Outbound batch processing finished for <%s>\n",
560                 CC->quickroom.QRname);
561 }
562
563
564 /*
565  * Batch up and send all outbound traffic from the current room
566  */
567 void network_queue_room(struct quickroom *qrbuf, void *data) {
568         struct RoomProcList *ptr;
569
570         ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
571         if (ptr == NULL) return;
572
573         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
574         ptr->next = rplist;
575         rplist = ptr;
576 }
577
578
579 /*
580  * Learn topology from path fields
581  */
582 void network_learn_topology(char *node, char *path) {
583         char nexthop[SIZ];
584         struct NetMap *nmptr;
585
586         strcpy(nexthop, "");
587
588         if (num_tokens(path, '!') < 3) return;
589         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
590                 if (!strcasecmp(nmptr->nodename, node)) {
591                         extract_token(nmptr->nexthop, path, 0, '!');
592                         nmptr->lastcontact = time(NULL);
593                         return;
594                 }
595         }
596
597         /* If we got here then it's not in the map, so add it. */
598         nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
599         strcpy(nmptr->nodename, node);
600         nmptr->lastcontact = time(NULL);
601         extract_token(nmptr->nexthop, path, 0, '!');
602         nmptr->next = the_netmap;
603         the_netmap = nmptr;
604 }
605
606
607
608
609 /*
610  * Bounce a message back to the sender
611  */
612 void network_bounce(struct CtdlMessage *msg, char *reason) {
613         static int serialnum = 0;
614         FILE *fp;
615         char filename[SIZ];
616         struct ser_ret sermsg;
617         char *oldpath = NULL;
618         char buf[SIZ];
619
620         lprintf(9, "entering network_bounce()\n");
621
622         if (msg == NULL) return;
623
624         /* 
625          * Give it a fresh message ID
626          */
627         if (msg->cm_fields['I'] != NULL) {
628                 phree(msg->cm_fields['I']);
629         }
630         sprintf(buf, "%ld.%04x.%04x@%s",
631                 time(NULL), getpid(), ++serialnum, config.c_fqdn);
632         msg->cm_fields['I'] = strdoop(buf);
633
634         /*
635          * FIXME ... right now we're just sending a bounce; we really want to
636          * include the text of the bounced message.
637          */
638         if (msg->cm_fields['M'] != NULL) {
639                 phree(msg->cm_fields['M']);
640         }
641         msg->cm_fields['M'] = strdoop(reason);
642         msg->cm_format_type = 0;
643
644         /*
645          * Turn the message around
646          */
647         if (msg->cm_fields['R'] == NULL) {
648                 phree(msg->cm_fields['R']);
649         }
650
651         if (msg->cm_fields['D'] == NULL) {
652                 phree(msg->cm_fields['D']);
653         }
654
655         msg->cm_fields['R'] = msg->cm_fields['A'];
656         msg->cm_fields['D'] = msg->cm_fields['N'];
657         msg->cm_fields['A'] = strdoop(BOUNCESOURCE);
658         msg->cm_fields['N'] = strdoop(config.c_nodename);
659         
660         if (!strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
661                 phree(msg->cm_fields['D']);
662         }
663
664         /*
665          * If this is a bounce of a bounce, send it to the Aide> room
666          * instead of looping around forever
667          */
668         if (msg->cm_fields['D'] == NULL) if (msg->cm_fields['R'] != NULL)
669            if (!strcasecmp(msg->cm_fields['R'], BOUNCESOURCE)) {
670                 phree(msg->cm_fields['R']);
671                 if (msg->cm_fields['C'] != NULL) {
672                         phree(msg->cm_fields['C']);
673                 }
674                 msg->cm_fields['C'] = strdoop(AIDEROOM);
675         }
676
677         /* prepend our node to the path */
678         if (msg->cm_fields['P'] != NULL) {
679                 oldpath = msg->cm_fields['P'];
680                 msg->cm_fields['P'] = NULL;
681         }
682         else {
683                 oldpath = strdoop("unknown_user");
684         }
685         msg->cm_fields['P'] = mallok(strlen(oldpath) + SIZ);
686         sprintf(msg->cm_fields['P'], "%s!%s", config.c_nodename, oldpath);
687         phree(oldpath);
688
689         /* serialize the message */
690         serialize_message(&sermsg, msg);
691
692         /* now send it */
693         sprintf(filename, "./network/spoolin/bounce.%04x.%04x",
694                 getpid(), serialnum);
695
696         fp = fopen(filename, "ab");
697         if (fp != NULL) {
698                 fwrite(sermsg.ser,
699                         sermsg.len, 1, fp);
700                 fclose(fp);
701         }
702         phree(sermsg.ser);
703         CtdlFreeMessage(msg);
704         lprintf(9, "leaving network_bounce()\n");
705 }
706
707
708
709
710 /*
711  * Process a buffer containing a single message from a single file
712  * from the inbound queue 
713  */
714 void network_process_buffer(char *buffer, long size) {
715         struct CtdlMessage *msg;
716         long pos;
717         int field;
718         int a;
719         int e = MES_LOCAL;
720         struct usersupp tempUS;
721         char recp[SIZ];
722         char target_room[ROOMNAMELEN];
723         struct ser_ret sermsg;
724         char *oldpath = NULL;
725         char filename[SIZ];
726         FILE *fp;
727
728         /* Set default target room to trash */
729         strcpy(target_room, TWITROOM);
730
731         /* Load the message into memory */
732         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
733         memset(msg, 0, sizeof(struct CtdlMessage));
734         msg->cm_magic = CTDLMESSAGE_MAGIC;
735         msg->cm_anon_type = buffer[1];
736         msg->cm_format_type = buffer[2];
737
738         for (pos = 3; pos < size; ++pos) {
739                 field = buffer[pos];
740                 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
741                 pos = pos + strlen(&buffer[(int)pos]);
742         }
743
744         /* Check for message routing */
745         if (msg->cm_fields['D'] != NULL) {
746                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
747
748                         /* route the message */
749                         if (is_valid_node(NULL, NULL,
750                            msg->cm_fields['D']) == 0) {
751
752                                 /* prepend our node to the path */
753                                 if (msg->cm_fields['P'] != NULL) {
754                                         oldpath = msg->cm_fields['P'];
755                                         msg->cm_fields['P'] = NULL;
756                                 }
757                                 else {
758                                         oldpath = strdoop("unknown_user");
759                                 }
760                                 msg->cm_fields['P'] =
761                                         mallok(strlen(oldpath) + SIZ);
762                                 sprintf(msg->cm_fields['P'], "%s!%s",
763                                         config.c_nodename, oldpath);
764                                 phree(oldpath);
765
766                                 /* serialize the message */
767                                 serialize_message(&sermsg, msg);
768
769                                 /* now send it */
770                                 sprintf(filename,
771                                         "./network/spoolout/%s",
772                                         msg->cm_fields['D']);
773                                 fp = fopen(filename, "ab");
774                                 if (fp != NULL) {
775                                         fwrite(sermsg.ser,
776                                                 sermsg.len, 1, fp);
777                                         fclose(fp);
778                                 }
779                                 phree(sermsg.ser);
780                                 CtdlFreeMessage(msg);
781                                 return;
782                         }
783                         
784                         else {  /* invalid destination node name */
785
786                                 network_bounce(msg,
787 "A message you sent could not be delivered due to an invalid destination node"
788 " name.  Please check the address and try sending the message again.\n");
789                                 msg = NULL;
790                                 return;
791
792                         }
793                 }
794         }
795
796         /* FIXME check to see if we already have this message */
797
798         /* Learn network topology from the path */
799         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
800                 network_learn_topology(msg->cm_fields['N'], 
801                                         msg->cm_fields['P']);
802         }
803
804         /* Does it have a recipient?  If so, validate it... */
805         if (msg->cm_fields['R'] != NULL) {
806
807                 safestrncpy(recp, msg->cm_fields['R'], sizeof(recp));
808
809                 e = alias(recp);        /* alias and mail type */
810                 if ((recp[0] == 0) || (e == MES_ERROR)) {
811
812                         network_bounce(msg,
813 "A message you sent could not be delivered due to an invalid address.\n"
814 "Please check the address and try sending the message again.\n");
815                         msg = NULL;
816                         return;
817
818                 }
819                 else if (e == MES_LOCAL) {
820                         a = getuser(&tempUS, recp);
821                         if (a != 0) {
822
823                                 network_bounce(msg,
824 "A message you sent could not be delivered because the user does not exist\n"
825 "on this system.  Please check the address and try again.\n");
826                                 msg = NULL;
827                                 return;
828
829                         }
830                         else {
831                                 MailboxName(target_room, &tempUS, MAILROOM);
832                         }
833                 }
834         }
835
836         else if (msg->cm_fields['C'] != NULL) {
837                 safestrncpy(target_room,
838                         msg->cm_fields['C'],
839                         sizeof target_room);
840         }
841
842         else if (msg->cm_fields['O'] != NULL) {
843                 safestrncpy(target_room,
844                         msg->cm_fields['O'],
845                         sizeof target_room);
846         }
847
848         /* save the message into a room */
849         msg->cm_flags = CM_SKIP_HOOKS;
850         CtdlSaveMsg(msg, "", target_room, 0);
851         CtdlFreeMessage(msg);
852 }
853
854
855 /*
856  * Process a single message from a single file from the inbound queue 
857  */
858 void network_process_message(FILE *fp, long msgstart, long msgend) {
859         long hold_pos;
860         long size;
861         char *buffer;
862
863         hold_pos = ftell(fp);
864         size = msgend - msgstart + 1;
865         buffer = mallok(size);
866         if (buffer != NULL) {
867                 fseek(fp, msgstart, SEEK_SET);
868                 fread(buffer, size, 1, fp);
869                 network_process_buffer(buffer, size);
870                 phree(buffer);
871         }
872
873         fseek(fp, hold_pos, SEEK_SET);
874 }
875
876
877 /*
878  * Process a single file from the inbound queue 
879  */
880 void network_process_file(char *filename) {
881         FILE *fp;
882         long msgstart = (-1L);
883         long msgend = (-1L);
884         long msgcur = 0L;
885         int ch;
886
887         lprintf(7, "network: processing <%s>\n", filename);
888
889         fp = fopen(filename, "rb");
890         if (fp == NULL) {
891                 lprintf(5, "Error opening %s: %s\n",
892                         filename, strerror(errno));
893                 return;
894         }
895
896         /* Look for messages in the data stream and break them out */
897         while (ch = getc(fp), ch >= 0) {
898         
899                 if (ch == 255) {
900                         if (msgstart >= 0L) {
901                                 msgend = msgcur - 1;
902                                 network_process_message(fp, msgstart, msgend);
903                         }
904                         msgstart = msgcur;
905                 }
906
907                 ++msgcur;
908         }
909
910         msgend = msgcur - 1;
911         if (msgstart >= 0L) {
912                 network_process_message(fp, msgstart, msgend);
913         }
914
915         fclose(fp);
916         unlink(filename);
917 }
918
919
920 /*
921  * Process anything in the inbound queue
922  */
923 void network_do_spoolin(void) {
924         DIR *dp;
925         struct dirent *d;
926         char filename[SIZ];
927
928         dp = opendir("./network/spoolin");
929         if (dp == NULL) return;
930
931         while (d = readdir(dp), d != NULL) {
932                 sprintf(filename, "./network/spoolin/%s", d->d_name);
933                 network_process_file(filename);
934         }
935
936
937         closedir(dp);
938 }
939
940
941
942
943
944 /*
945  * receive network spool from the remote system
946  */
947 void receive_spool(int sock, char *remote_nodename) {
948         long download_len;
949         long bytes_received;
950         char buf[SIZ];
951         static char pbuf[IGNET_PACKET_SIZE];
952         char tempfilename[PATH_MAX];
953         long plen;
954         FILE *fp;
955
956         strcpy(tempfilename, tmpnam(NULL));
957         if (sock_puts(sock, "NDOP") < 0) return;
958         if (sock_gets(sock, buf) < 0) return;
959         lprintf(9, "<%s\n", buf);
960         if (buf[0] != '2') {
961                 return;
962         }
963         download_len = extract_long(&buf[4], 0);
964
965         bytes_received = 0L;
966         fp = fopen(tempfilename, "w");
967         if (fp == NULL) {
968                 lprintf(9, "cannot open download file locally: %s\n",
969                         strerror(errno));
970                 return;
971         }
972
973         while (bytes_received < download_len) {
974                 sprintf(buf, "READ %ld|%ld",
975                         bytes_received,
976                      ((download_len - bytes_received > IGNET_PACKET_SIZE)
977                  ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
978                 if (sock_puts(sock, buf) < 0) {
979                         fclose(fp);
980                         unlink(tempfilename);
981                         return;
982                 }
983                 if (sock_gets(sock, buf) < 0) {
984                         fclose(fp);
985                         unlink(tempfilename);
986                         return;
987                 }
988                 if (buf[0] == '6') {
989                         plen = extract_long(&buf[4], 0);
990                         if (sock_read(sock, pbuf, plen) < 0) {
991                                 fclose(fp);
992                                 unlink(tempfilename);
993                                 return;
994                         }
995                         fwrite((char *) pbuf, plen, 1, fp);
996                         bytes_received = bytes_received + plen;
997                 }
998         }
999
1000         fclose(fp);
1001         if (sock_puts(sock, "CLOS") < 0) {
1002                 unlink(tempfilename);
1003                 return;
1004         }
1005         if (sock_gets(sock, buf) < 0) {
1006                 unlink(tempfilename);
1007                 return;
1008         }
1009         lprintf(9, "%s\n", buf);
1010         sprintf(buf, "mv %s ./network/spoolin/%s.%ld",
1011                 tempfilename, remote_nodename, (long) getpid());
1012         system(buf);
1013 }
1014
1015
1016
1017 /*
1018  * transmit network spool to the remote system
1019  */
1020 void transmit_spool(int sock, char *remote_nodename)
1021 {
1022         char buf[SIZ];
1023         char pbuf[4096];
1024         long plen;
1025         long bytes_to_write, thisblock;
1026         int fd;
1027         char sfname[128];
1028
1029         if (sock_puts(sock, "NUOP") < 0) return;
1030         if (sock_gets(sock, buf) < 0) return;
1031         lprintf(9, "<%s\n", buf);
1032         if (buf[0] != '2') {
1033                 return;
1034         }
1035
1036         sprintf(sfname, "./network/spoolout/%s", remote_nodename);
1037         fd = open(sfname, O_RDONLY);
1038         if (fd < 0) {
1039                 if (errno == ENOENT) {
1040                         lprintf(9, "Nothing to send.\n");
1041                 } else {
1042                         lprintf(5, "cannot open upload file locally: %s\n",
1043                                 strerror(errno));
1044                 }
1045                 return;
1046         }
1047         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1048                 bytes_to_write = plen;
1049                 while (bytes_to_write > 0L) {
1050                         sprintf(buf, "WRIT %ld", bytes_to_write);
1051                         if (sock_puts(sock, buf) < 0) {
1052                                 close(fd);
1053                                 return;
1054                         }
1055                         if (sock_gets(sock, buf) < 0) {
1056                                 close(fd);
1057                                 return;
1058                         }
1059                         thisblock = atol(&buf[4]);
1060                         if (buf[0] == '7') {
1061                                 if (sock_write(sock, pbuf,
1062                                    (int) thisblock) < 0) {
1063                                         close(fd);
1064                                         return;
1065                                 }
1066                                 bytes_to_write = bytes_to_write - thisblock;
1067                         } else {
1068                                 goto ABORTUPL;
1069                         }
1070                 }
1071         }
1072
1073 ABORTUPL:
1074         close(fd);
1075         if (sock_puts(sock, "UCLS 1") < 0) return;
1076         if (sock_gets(sock, buf) < 0) return;
1077         lprintf(9, "<%s\n", buf);
1078         if (buf[0] == '2') {
1079                 unlink(sfname);
1080         }
1081 }
1082
1083
1084
1085 /*
1086  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1087  */
1088 void network_poll_node(char *node, char *secret, char *host, char *port) {
1089         int sock;
1090         char buf[SIZ];
1091
1092         lprintf(5, "Polling node <%s> at %s:%s\n", node, host, port);
1093
1094         sock = sock_connect(host, port, "tcp");
1095         if (sock < 0) {
1096                 lprintf(7, "Could not connect: %s\n", strerror(errno));
1097                 return;
1098         }
1099         
1100         lprintf(9, "Connected!\n");
1101
1102         /* Read the server greeting */
1103         if (sock_gets(sock, buf) < 0) goto bail;
1104         lprintf(9, ">%s\n", buf);
1105
1106         /* Identify ourselves */
1107         sprintf(buf, "NETP %s|%s", config.c_nodename, secret);
1108         lprintf(9, "<%s\n", buf);
1109         if (sock_puts(sock, buf) <0) goto bail;
1110         if (sock_gets(sock, buf) < 0) goto bail;
1111         lprintf(9, ">%s\n", buf);
1112         if (buf[0] != '2') goto bail;
1113
1114         /* At this point we are authenticated. */
1115         receive_spool(sock, node);
1116         transmit_spool(sock, node);
1117
1118         sock_puts(sock, "QUIT");
1119 bail:   sock_close(sock);
1120 }
1121
1122
1123
1124 /*
1125  * Poll other Citadel nodes and transfer inbound/outbound network data.
1126  */
1127 void network_poll_other_citadel_nodes(void) {
1128         char *ignetcfg = NULL;
1129         int i;
1130         char linebuf[SIZ];
1131         char node[SIZ];
1132         char host[SIZ];
1133         char port[SIZ];
1134         char secret[SIZ];
1135
1136         ignetcfg = CtdlGetSysConfig(IGNETCFG);
1137         if (ignetcfg == NULL) return;   /* no nodes defined */
1138
1139         /* Use the string tokenizer to grab one line at a time */
1140         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
1141                 extract_token(linebuf, ignetcfg, i, '\n');
1142                 extract(node, linebuf, 0);
1143                 extract(secret, linebuf, 1);
1144                 extract(host, linebuf, 2);
1145                 extract(port, linebuf, 3);
1146                 if ( (strlen(node) > 0) && (strlen(secret) > 0) 
1147                    && (strlen(host) > 0) && strlen(port) > 0) {
1148                         network_poll_node(node, secret, host, port);
1149                 }
1150         }
1151
1152         phree(ignetcfg);
1153 }
1154
1155
1156
1157
1158
1159
1160
1161 /*
1162  * network_do_queue()
1163  * 
1164  * Run through the rooms doing various types of network stuff.
1165  */
1166 void network_do_queue(void) {
1167         static int doing_queue = 0;
1168         static time_t last_run = 0L;
1169         struct RoomProcList *ptr;
1170
1171         /*
1172          * Run no more frequently than once every n seconds
1173          */
1174         if ( (time(NULL) - last_run) < NETWORK_QUEUE_FREQUENCY ) return;
1175
1176         /*
1177          * This is a simple concurrency check to make sure only one queue run
1178          * is done at a time.  We could do this with a mutex, but since we
1179          * don't really require extremely fine granularity here, we'll do it
1180          * with a static variable instead.
1181          */
1182         if (doing_queue) return;
1183         doing_queue = 1;
1184         last_run = time(NULL);
1185
1186         /*
1187          * Poll other Citadel nodes.
1188          */
1189         network_poll_other_citadel_nodes();
1190
1191         /*
1192          * Load the network map into memory.
1193          */
1194         read_network_map();
1195
1196         /* 
1197          * Go ahead and run the queue
1198          */
1199         lprintf(7, "network: loading outbound queue\n");
1200         ForEachRoom(network_queue_room, NULL);
1201
1202         lprintf(7, "network: running outbound queue\n");
1203         while (rplist != NULL) {
1204                 network_spoolout_room(rplist->name);
1205                 ptr = rplist;
1206                 rplist = rplist->next;
1207                 phree(ptr);
1208         }
1209
1210         lprintf(7, "network: processing inbound queue\n");
1211         network_do_spoolin();
1212
1213         write_network_map();
1214
1215         lprintf(7, "network: queue run completed\n");
1216         doing_queue = 0;
1217 }
1218
1219
1220
1221 /*
1222  * cmd_netp() - authenticate to the server as another Citadel node polling
1223  *              for network traffic
1224  */
1225 void cmd_netp(char *cmdbuf)
1226 {
1227         char node[SIZ];
1228         char pass[SIZ];
1229
1230         char secret[SIZ];
1231         char nexthop[SIZ];
1232
1233         extract(node, cmdbuf, 0);
1234         extract(pass, cmdbuf, 1);
1235
1236         if (is_valid_node(nexthop, secret, node) != 0) {
1237                 cprintf("%d authentication failed\n", ERROR);
1238                 return;
1239         }
1240
1241         if (strcasecmp(pass, secret)) {
1242                 cprintf("%d authentication failed\n", ERROR);
1243                 return;
1244         }
1245
1246         safestrncpy(CC->net_node, node, sizeof CC->net_node);
1247         cprintf("%d authenticated as network node '%s'\n", OK,
1248                 CC->net_node);
1249 }
1250
1251
1252
1253 /*
1254  * Module entry point
1255  */
1256 char *Dynamic_Module_Init(void)
1257 {
1258         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1259         CtdlRegisterProtoHook(cmd_snet, "SNET", "Get network config");
1260         CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1261         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
1262         return "$Id$";
1263 }