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