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