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