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