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