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