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