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