]> code.citadel.org Git - citadel.git/blob - citadel/serv_network.c
* Began implementing "digest mode" for listserving. (Not complete)
[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 digest recipients
458          */
459         if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
460                 fprintf(sc->digestfp,   " -----------------------------------"
461                                         "------------------------------------"
462                                         "-------\r\n");
463                 CtdlRedirectOutput(sc->digestfp, -1);
464                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
465                 CtdlRedirectOutput(NULL, -1);
466         }
467         
468         /*
469          * Process IGnet push shares
470          */
471         if (sc->ignet_push_shares != NULL) {
472         
473                 msg = CtdlFetchMessage(msgnum);
474                 if (msg != NULL) {
475                         size_t newpath_len;
476
477                         /* Prepend our node name to the Path field whenever
478                          * sending a message to another IGnet node
479                          */
480                         if (msg->cm_fields['P'] == NULL) {
481                                 msg->cm_fields['P'] = strdoop("username");
482                         }
483                         newpath_len = strlen(msg->cm_fields['P']) +
484                                  strlen(config.c_nodename) + 2;
485                         newpath = mallok(newpath_len);
486                         snprintf(newpath, newpath_len, "%s!%s",
487                                  config.c_nodename, msg->cm_fields['P']);
488                         phree(msg->cm_fields['P']);
489                         msg->cm_fields['P'] = newpath;
490
491                         /*
492                          * Force the message to appear in the correct room
493                          * on the far end by setting the C field correctly
494                          */
495                         if (msg->cm_fields['C'] != NULL) {
496                                 phree(msg->cm_fields['C']);
497                         }
498                         msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
499
500                         /*
501                          * Determine if this message is set to be deleted
502                          * after sending out on the network
503                          */
504                         if (msg->cm_fields['S'] != NULL) {
505                                 if (!strcasecmp(msg->cm_fields['S'],
506                                    "CANCEL")) {
507                                         delete_after_send = 1;
508                                 }
509                         }
510
511                         /* 
512                          * Now serialize it for transmission
513                          */
514                         serialize_message(&sermsg, msg);
515
516                         /* Now send it to every node */
517                         for (nptr = sc->ignet_push_shares; nptr != NULL;
518                             nptr = nptr->next) {
519
520                                 send = 1;
521
522                                 /* Check for valid node name */
523                                 if (is_valid_node(NULL,NULL,nptr->name) != 0) {
524                                         lprintf(3, "Invalid node <%s>\n",
525                                                 nptr->name);
526                                         send = 0;
527                                 }
528
529                                 /* Check for split horizon */
530                                 lprintf(9, "Path is %s\n", msg->cm_fields['P']);
531                                 bang = num_tokens(msg->cm_fields['P'], '!');
532                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
533                                         extract_token(buf, msg->cm_fields['P'],
534                                                 i, '!');
535                                         if (!strcasecmp(buf, nptr->name)) {
536                                                 send = 0;
537                                         }
538                                 }
539
540                                 /* Send the message */
541                                 if (send == 1) {
542                                         snprintf(filename, sizeof filename,
543                                                 "./network/spoolout/%s",
544                                                 nptr->name);
545                                         fp = fopen(filename, "ab");
546                                         if (fp != NULL) {
547                                                 fwrite(sermsg.ser,
548                                                         sermsg.len, 1, fp);
549                                                 fclose(fp);
550                                         }
551                                 }
552                         }
553                         phree(sermsg.ser);
554                         CtdlFreeMessage(msg);
555                 }
556         }
557
558         /* update lastsent */
559         sc->lastsent = msgnum;
560
561         /* Delete this message if delete-after-send is set */
562         if (delete_after_send) {
563                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
564         }
565
566 }
567         
568
569
570
571 /*
572  * Batch up and send all outbound traffic from the current room
573  */
574 void network_spoolout_room(char *room_to_spool) {
575         char filename[SIZ];
576         char buf[SIZ];
577         char instr[SIZ];
578         FILE *fp;
579         struct SpoolControl sc;
580         struct namelist *nptr;
581
582         lprintf(7, "Spooling <%s>\n", room_to_spool);
583         if (getroom(&CC->quickroom, room_to_spool) != 0) {
584                 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
585                 return;
586         }
587
588         memset(&sc, 0, sizeof(struct SpoolControl));
589         assoc_file_name(filename, sizeof filename, &CC->quickroom, "netconfigs");
590
591         fp = fopen(filename, "r");
592         if (fp == NULL) {
593                 lprintf(7, "Outbound batch processing skipped for <%s>\n",
594                         CC->quickroom.QRname);
595                 return;
596         }
597
598         lprintf(5, "Outbound batch processing started for <%s>\n",
599                 CC->quickroom.QRname);
600
601         while (fgets(buf, sizeof buf, fp) != NULL) {
602                 buf[strlen(buf)-1] = 0;
603
604                 extract(instr, buf, 0);
605                 if (!strcasecmp(instr, "lastsent")) {
606                         sc.lastsent = extract_long(buf, 1);
607                 }
608                 else if (!strcasecmp(instr, "listrecp")) {
609                         nptr = (struct namelist *)
610                                 mallok(sizeof(struct namelist));
611                         nptr->next = sc.listrecps;
612                         extract(nptr->name, buf, 1);
613                         sc.listrecps = nptr;
614                 }
615                 else if (!strcasecmp(instr, "digestrecp")) {
616                         nptr = (struct namelist *)
617                                 mallok(sizeof(struct namelist));
618                         nptr->next = sc.digestrecps;
619                         extract(nptr->name, buf, 1);
620                         sc.digestrecps = nptr;
621                 }
622                 else if (!strcasecmp(instr, "ignet_push_share")) {
623                         nptr = (struct namelist *)
624                                 mallok(sizeof(struct namelist));
625                         nptr->next = sc.ignet_push_shares;
626                         extract(nptr->name, buf, 1);
627                         sc.ignet_push_shares = nptr;
628                 }
629
630
631         }
632         fclose(fp);
633
634         /* If there are digest recipients, we have to build a digest */
635         if (sc.digestrecps != NULL) {
636                 sc.digestfp = tmpfile();
637         }
638
639         /* Do something useful */
640         CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL,
641                 network_spool_msg, &sc);
642
643         /* If we wrote a digest, deliver it and then close it */
644         if (sc.digestfp != NULL) {
645                 fprintf(sc->digestfp,   " -----------------------------------"
646                                         "------------------------------------"
647                                         "-------\r\n");
648                 /* FIXME deliver it! */
649                 fclose(sc.digestfp);
650         }
651
652         /* Now rewrite the config file */
653         fp = fopen(filename, "w");
654         if (fp == NULL) {
655                 lprintf(1, "ERROR: cannot open %s: %s\n",
656                         filename, strerror(errno));
657         }
658         else {
659                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
660
661                 /* Write out the listrecps while freeing from memory at the
662                  * same time.  Am I clever or what?  :)
663                  */
664                 while (sc.listrecps != NULL) {
665                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
666                         nptr = sc.listrecps->next;
667                         phree(sc.listrecps);
668                         sc.listrecps = nptr;
669                 }
670                 /* Do the same for digestrecps */
671                 while (sc.digestrecps != NULL) {
672                         fprintf(fp, "digestrecp|%s\n", sc.digestrecps->name);
673                         nptr = sc.digestrecps->next;
674                         phree(sc.digestrecps);
675                         sc.digestrecps = nptr;
676                 }
677                 while (sc.ignet_push_shares != NULL) {
678                         fprintf(fp, "ignet_push_share|%s\n",
679                                 sc.ignet_push_shares->name);
680                         nptr = sc.ignet_push_shares->next;
681                         phree(sc.ignet_push_shares);
682                         sc.ignet_push_shares = nptr;
683                 }
684
685                 fclose(fp);
686         }
687
688         lprintf(5, "Outbound batch processing finished for <%s>\n",
689                 CC->quickroom.QRname);
690 }
691
692
693 /*
694  * Batch up and send all outbound traffic from the current room
695  */
696 void network_queue_room(struct quickroom *qrbuf, void *data) {
697         struct RoomProcList *ptr;
698
699         ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
700         if (ptr == NULL) return;
701
702         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
703         ptr->next = rplist;
704         rplist = ptr;
705 }
706
707
708 /*
709  * Learn topology from path fields
710  */
711 void network_learn_topology(char *node, char *path) {
712         char nexthop[SIZ];
713         struct NetMap *nmptr;
714
715         strcpy(nexthop, "");
716
717         if (num_tokens(path, '!') < 3) return;
718         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
719                 if (!strcasecmp(nmptr->nodename, node)) {
720                         extract_token(nmptr->nexthop, path, 0, '!');
721                         nmptr->lastcontact = time(NULL);
722                         return;
723                 }
724         }
725
726         /* If we got here then it's not in the map, so add it. */
727         nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
728         strcpy(nmptr->nodename, node);
729         nmptr->lastcontact = time(NULL);
730         extract_token(nmptr->nexthop, path, 0, '!');
731         nmptr->next = the_netmap;
732         the_netmap = nmptr;
733 }
734
735
736
737
738 /*
739  * Bounce a message back to the sender
740  */
741 void network_bounce(struct CtdlMessage *msg, char *reason) {
742         char *oldpath = NULL;
743         char buf[SIZ];
744         char bouncesource[SIZ];
745         char recipient[SIZ];
746         struct recptypes *valid = NULL;
747         char force_room[ROOMNAMELEN];
748         static int serialnum = 0;
749         size_t size;
750
751         lprintf(9, "entering network_bounce()\n");
752
753         if (msg == NULL) return;
754
755         snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
756
757         /* 
758          * Give it a fresh message ID
759          */
760         if (msg->cm_fields['I'] != NULL) {
761                 phree(msg->cm_fields['I']);
762         }
763         snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s",
764                 (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn);
765         msg->cm_fields['I'] = strdoop(buf);
766
767         /*
768          * FIXME ... right now we're just sending a bounce; we really want to
769          * include the text of the bounced message.
770          */
771         if (msg->cm_fields['M'] != NULL) {
772                 phree(msg->cm_fields['M']);
773         }
774         msg->cm_fields['M'] = strdoop(reason);
775         msg->cm_format_type = 0;
776
777         /*
778          * Turn the message around
779          */
780         if (msg->cm_fields['R'] == NULL) {
781                 phree(msg->cm_fields['R']);
782         }
783
784         if (msg->cm_fields['D'] == NULL) {
785                 phree(msg->cm_fields['D']);
786         }
787
788         snprintf(recipient, sizeof recipient, "%s@%s",
789                 msg->cm_fields['A'], msg->cm_fields['N']);
790
791         if (msg->cm_fields['A'] == NULL) {
792                 phree(msg->cm_fields['A']);
793         }
794
795         if (msg->cm_fields['N'] == NULL) {
796                 phree(msg->cm_fields['N']);
797         }
798
799         msg->cm_fields['A'] = strdoop(BOUNCESOURCE);
800         msg->cm_fields['N'] = strdoop(config.c_nodename);
801         
802
803         /* prepend our node to the path */
804         if (msg->cm_fields['P'] != NULL) {
805                 oldpath = msg->cm_fields['P'];
806                 msg->cm_fields['P'] = NULL;
807         }
808         else {
809                 oldpath = strdoop("unknown_user");
810         }
811         size = strlen(oldpath) + SIZ;
812         msg->cm_fields['P'] = mallok(size);
813         snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath);
814         phree(oldpath);
815
816         /* Now submit the message */
817         valid = validate_recipients(recipient);
818         if (valid != NULL) if (valid->num_error > 0) {
819                 phree(valid);
820                 valid = NULL;
821         }
822         if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
823                 strcpy(force_room, config.c_aideroom);
824         }
825         else {
826                 strcpy(force_room, "");
827         }
828         if ( (valid == NULL) && (strlen(force_room) == 0) ) {
829                 strcpy(force_room, config.c_aideroom);
830         }
831         CtdlSubmitMsg(msg, valid, force_room);
832
833         /* Clean up */
834         if (valid != NULL) phree(valid);
835         CtdlFreeMessage(msg);
836         lprintf(9, "leaving network_bounce()\n");
837 }
838
839
840
841
842 /*
843  * Process a buffer containing a single message from a single file
844  * from the inbound queue 
845  */
846 void network_process_buffer(char *buffer, long size) {
847         struct CtdlMessage *msg;
848         long pos;
849         int field;
850         struct recptypes *recp = NULL;
851         char target_room[ROOMNAMELEN];
852         struct ser_ret sermsg;
853         char *oldpath = NULL;
854         char filename[SIZ];
855         FILE *fp;
856         char buf[SIZ];
857
858         /* Set default target room to trash */
859         strcpy(target_room, TWITROOM);
860
861         /* Load the message into memory */
862         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
863         memset(msg, 0, sizeof(struct CtdlMessage));
864         msg->cm_magic = CTDLMESSAGE_MAGIC;
865         msg->cm_anon_type = buffer[1];
866         msg->cm_format_type = buffer[2];
867
868         for (pos = 3; pos < size; ++pos) {
869                 field = buffer[pos];
870                 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
871                 pos = pos + strlen(&buffer[(int)pos]);
872         }
873
874         /* Check for message routing */
875         if (msg->cm_fields['D'] != NULL) {
876                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
877
878                         /* route the message */
879                         if (is_valid_node(NULL, NULL,
880                            msg->cm_fields['D']) == 0) {
881
882                                 /* prepend our node to the path */
883                                 if (msg->cm_fields['P'] != NULL) {
884                                         oldpath = msg->cm_fields['P'];
885                                         msg->cm_fields['P'] = NULL;
886                                 }
887                                 else {
888                                         oldpath = strdoop("unknown_user");
889                                 }
890                                 size = strlen(oldpath) + SIZ;
891                                 msg->cm_fields['P'] = mallok(size);
892                                 snprintf(msg->cm_fields['P'], size, "%s!%s",
893                                         config.c_nodename, oldpath);
894                                 phree(oldpath);
895
896                                 /* serialize the message */
897                                 serialize_message(&sermsg, msg);
898
899                                 /* now send it */
900                                 snprintf(filename, sizeof filename,
901                                         "./network/spoolout/%s",
902                                         msg->cm_fields['D']);
903                                 fp = fopen(filename, "ab");
904                                 if (fp != NULL) {
905                                         fwrite(sermsg.ser,
906                                                 sermsg.len, 1, fp);
907                                         fclose(fp);
908                                 }
909                                 phree(sermsg.ser);
910                                 CtdlFreeMessage(msg);
911                                 return;
912                         }
913                         
914                         else {  /* invalid destination node name */
915
916                                 network_bounce(msg,
917 "A message you sent could not be delivered due to an invalid destination node"
918 " name.  Please check the address and try sending the message again.\n");
919                                 msg = NULL;
920                                 return;
921
922                         }
923                 }
924         }
925
926         /*
927          * Check to see if we already have a copy of this message
928          */
929         if (network_usetable(msg) != 0) {
930                 snprintf(buf, sizeof buf,
931                         "Loopzapper rejected message <%s> "
932                         "from <%s> in <%s> @ <%s>\n",
933                         ((msg->cm_fields['I']!=NULL)?(msg->cm_fields['I']):""),
934                         ((msg->cm_fields['A']!=NULL)?(msg->cm_fields['A']):""),
935                         ((msg->cm_fields['O']!=NULL)?(msg->cm_fields['O']):""),
936                         ((msg->cm_fields['N']!=NULL)?(msg->cm_fields['N']):"")
937                 );
938                 aide_message(buf);
939                 CtdlFreeMessage(msg);
940                 msg = NULL;
941                 return;
942         }
943
944         /* Learn network topology from the path */
945         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
946                 network_learn_topology(msg->cm_fields['N'], 
947                                         msg->cm_fields['P']);
948         }
949
950         /* Does it have a recipient?  If so, validate it... */
951         if (msg->cm_fields['R'] != NULL) {
952                 recp = validate_recipients(msg->cm_fields['R']);
953                 if (recp != NULL) if (recp->num_error > 0) {
954                         network_bounce(msg,
955 "A message you sent could not be delivered due to an invalid address.\n"
956 "Please check the address and try sending the message again.\n");
957                         msg = NULL;
958                         phree(recp);
959                         return;
960                 }
961                 strcpy(target_room, "");        /* no target room if mail */
962         }
963
964         else if (msg->cm_fields['C'] != NULL) {
965                 safestrncpy(target_room,
966                         msg->cm_fields['C'],
967                         sizeof target_room);
968         }
969
970         else if (msg->cm_fields['O'] != NULL) {
971                 safestrncpy(target_room,
972                         msg->cm_fields['O'],
973                         sizeof target_room);
974         }
975
976         /* Strip out fields that are only relevant during transit */
977         if (msg->cm_fields['D'] != NULL) {
978                 phree(msg->cm_fields['D']);
979                 msg->cm_fields['D'] = NULL;
980         }
981         if (msg->cm_fields['C'] != NULL) {
982                 phree(msg->cm_fields['C']);
983                 msg->cm_fields['C'] = NULL;
984         }
985
986         /* save the message into a room */
987         if (PerformNetprocHooks(msg, target_room) == 0) {
988                 msg->cm_flags = CM_SKIP_HOOKS;
989                 CtdlSubmitMsg(msg, recp, target_room);
990         }
991         CtdlFreeMessage(msg);
992         phree(recp);
993 }
994
995
996 /*
997  * Process a single message from a single file from the inbound queue 
998  */
999 void network_process_message(FILE *fp, long msgstart, long msgend) {
1000         long hold_pos;
1001         long size;
1002         char *buffer;
1003
1004         hold_pos = ftell(fp);
1005         size = msgend - msgstart + 1;
1006         buffer = mallok(size);
1007         if (buffer != NULL) {
1008                 fseek(fp, msgstart, SEEK_SET);
1009                 fread(buffer, size, 1, fp);
1010                 network_process_buffer(buffer, size);
1011                 phree(buffer);
1012         }
1013
1014         fseek(fp, hold_pos, SEEK_SET);
1015 }
1016
1017
1018 /*
1019  * Process a single file from the inbound queue 
1020  */
1021 void network_process_file(char *filename) {
1022         FILE *fp;
1023         long msgstart = (-1L);
1024         long msgend = (-1L);
1025         long msgcur = 0L;
1026         int ch;
1027
1028         lprintf(7, "network: processing <%s>\n", filename);
1029
1030         fp = fopen(filename, "rb");
1031         if (fp == NULL) {
1032                 lprintf(5, "Error opening %s: %s\n",
1033                         filename, strerror(errno));
1034                 return;
1035         }
1036
1037         /* Look for messages in the data stream and break them out */
1038         while (ch = getc(fp), ch >= 0) {
1039         
1040                 if (ch == 255) {
1041                         if (msgstart >= 0L) {
1042                                 msgend = msgcur - 1;
1043                                 network_process_message(fp, msgstart, msgend);
1044                         }
1045                         msgstart = msgcur;
1046                 }
1047
1048                 ++msgcur;
1049         }
1050
1051         msgend = msgcur - 1;
1052         if (msgstart >= 0L) {
1053                 network_process_message(fp, msgstart, msgend);
1054         }
1055
1056         fclose(fp);
1057         unlink(filename);
1058 }
1059
1060
1061 /*
1062  * Process anything in the inbound queue
1063  */
1064 void network_do_spoolin(void) {
1065         DIR *dp;
1066         struct dirent *d;
1067         char filename[SIZ];
1068
1069         dp = opendir("./network/spoolin");
1070         if (dp == NULL) return;
1071
1072         while (d = readdir(dp), d != NULL) {
1073                 snprintf(filename, sizeof filename, "./network/spoolin/%s", d->d_name);
1074                 network_process_file(filename);
1075         }
1076
1077
1078         closedir(dp);
1079 }
1080
1081
1082
1083
1084
1085 /*
1086  * receive network spool from the remote system
1087  */
1088 void receive_spool(int sock, char *remote_nodename) {
1089         long download_len;
1090         long bytes_received;
1091         char buf[SIZ];
1092         static char pbuf[IGNET_PACKET_SIZE];
1093         char tempfilename[PATH_MAX];
1094         long plen;
1095         FILE *fp;
1096
1097         strcpy(tempfilename, tmpnam(NULL));
1098         if (sock_puts(sock, "NDOP") < 0) return;
1099         if (sock_gets(sock, buf) < 0) return;
1100         lprintf(9, "<%s\n", buf);
1101         if (buf[0] != '2') {
1102                 return;
1103         }
1104         download_len = extract_long(&buf[4], 0);
1105
1106         bytes_received = 0L;
1107         fp = fopen(tempfilename, "w");
1108         if (fp == NULL) {
1109                 lprintf(9, "cannot open download file locally: %s\n",
1110                         strerror(errno));
1111                 return;
1112         }
1113
1114         while (bytes_received < download_len) {
1115                 snprintf(buf, sizeof buf, "READ %ld|%ld",
1116                         bytes_received,
1117                      ((download_len - bytes_received > IGNET_PACKET_SIZE)
1118                  ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
1119                 if (sock_puts(sock, buf) < 0) {
1120                         fclose(fp);
1121                         unlink(tempfilename);
1122                         return;
1123                 }
1124                 if (sock_gets(sock, buf) < 0) {
1125                         fclose(fp);
1126                         unlink(tempfilename);
1127                         return;
1128                 }
1129                 if (buf[0] == '6') {
1130                         plen = extract_long(&buf[4], 0);
1131                         if (sock_read(sock, pbuf, plen) < 0) {
1132                                 fclose(fp);
1133                                 unlink(tempfilename);
1134                                 return;
1135                         }
1136                         fwrite((char *) pbuf, plen, 1, fp);
1137                         bytes_received = bytes_received + plen;
1138                 }
1139         }
1140
1141         fclose(fp);
1142         if (sock_puts(sock, "CLOS") < 0) {
1143                 unlink(tempfilename);
1144                 return;
1145         }
1146         if (sock_gets(sock, buf) < 0) {
1147                 unlink(tempfilename);
1148                 return;
1149         }
1150         lprintf(9, "%s\n", buf);
1151         snprintf(buf, sizeof buf, "mv %s ./network/spoolin/%s.%ld",
1152                 tempfilename, remote_nodename, (long) getpid());
1153         system(buf);
1154 }
1155
1156
1157
1158 /*
1159  * transmit network spool to the remote system
1160  */
1161 void transmit_spool(int sock, char *remote_nodename)
1162 {
1163         char buf[SIZ];
1164         char pbuf[4096];
1165         long plen;
1166         long bytes_to_write, thisblock;
1167         int fd;
1168         char sfname[128];
1169
1170         if (sock_puts(sock, "NUOP") < 0) return;
1171         if (sock_gets(sock, buf) < 0) return;
1172         lprintf(9, "<%s\n", buf);
1173         if (buf[0] != '2') {
1174                 return;
1175         }
1176
1177         snprintf(sfname, sizeof sfname, "./network/spoolout/%s", remote_nodename);
1178         fd = open(sfname, O_RDONLY);
1179         if (fd < 0) {
1180                 if (errno == ENOENT) {
1181                         lprintf(9, "Nothing to send.\n");
1182                 } else {
1183                         lprintf(5, "cannot open upload file locally: %s\n",
1184                                 strerror(errno));
1185                 }
1186                 return;
1187         }
1188         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1189                 bytes_to_write = plen;
1190                 while (bytes_to_write > 0L) {
1191                         snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
1192                         if (sock_puts(sock, buf) < 0) {
1193                                 close(fd);
1194                                 return;
1195                         }
1196                         if (sock_gets(sock, buf) < 0) {
1197                                 close(fd);
1198                                 return;
1199                         }
1200                         thisblock = atol(&buf[4]);
1201                         if (buf[0] == '7') {
1202                                 if (sock_write(sock, pbuf,
1203                                    (int) thisblock) < 0) {
1204                                         close(fd);
1205                                         return;
1206                                 }
1207                                 bytes_to_write = bytes_to_write - thisblock;
1208                         } else {
1209                                 goto ABORTUPL;
1210                         }
1211                 }
1212         }
1213
1214 ABORTUPL:
1215         close(fd);
1216         if (sock_puts(sock, "UCLS 1") < 0) return;
1217         if (sock_gets(sock, buf) < 0) return;
1218         lprintf(9, "<%s\n", buf);
1219         if (buf[0] == '2') {
1220                 unlink(sfname);
1221         }
1222 }
1223
1224
1225
1226 /*
1227  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1228  */
1229 void network_poll_node(char *node, char *secret, char *host, char *port) {
1230         int sock;
1231         char buf[SIZ];
1232
1233         if (network_talking_to(node, NTT_CHECK)) return;
1234         network_talking_to(node, NTT_ADD);
1235         lprintf(5, "Polling node <%s> at %s:%s\n", node, host, port);
1236
1237         sock = sock_connect(host, port, "tcp");
1238         if (sock < 0) {
1239                 lprintf(7, "Could not connect: %s\n", strerror(errno));
1240                 network_talking_to(node, NTT_REMOVE);
1241                 return;
1242         }
1243         
1244         lprintf(9, "Connected!\n");
1245
1246         /* Read the server greeting */
1247         if (sock_gets(sock, buf) < 0) goto bail;
1248         lprintf(9, ">%s\n", buf);
1249
1250         /* Identify ourselves */
1251         snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
1252         lprintf(9, "<%s\n", buf);
1253         if (sock_puts(sock, buf) <0) goto bail;
1254         if (sock_gets(sock, buf) < 0) goto bail;
1255         lprintf(9, ">%s\n", buf);
1256         if (buf[0] != '2') goto bail;
1257
1258         /* At this point we are authenticated. */
1259         receive_spool(sock, node);
1260         transmit_spool(sock, node);
1261
1262         sock_puts(sock, "QUIT");
1263 bail:   sock_close(sock);
1264         network_talking_to(node, NTT_REMOVE);
1265 }
1266
1267
1268
1269 /*
1270  * Poll other Citadel nodes and transfer inbound/outbound network data.
1271  */
1272 void network_poll_other_citadel_nodes(void) {
1273         char *ignetcfg = NULL;
1274         int i;
1275         char linebuf[SIZ];
1276         char node[SIZ];
1277         char host[SIZ];
1278         char port[SIZ];
1279         char secret[SIZ];
1280
1281         ignetcfg = CtdlGetSysConfig(IGNETCFG);
1282         if (ignetcfg == NULL) return;   /* no nodes defined */
1283
1284         /* Use the string tokenizer to grab one line at a time */
1285         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
1286                 extract_token(linebuf, ignetcfg, i, '\n');
1287                 extract(node, linebuf, 0);
1288                 extract(secret, linebuf, 1);
1289                 extract(host, linebuf, 2);
1290                 extract(port, linebuf, 3);
1291                 if ( (strlen(node) > 0) && (strlen(secret) > 0) 
1292                    && (strlen(host) > 0) && strlen(port) > 0) {
1293                         network_poll_node(node, secret, host, port);
1294                 }
1295         }
1296
1297         phree(ignetcfg);
1298 }
1299
1300
1301
1302
1303
1304
1305
1306 /*
1307  * network_do_queue()
1308  * 
1309  * Run through the rooms doing various types of network stuff.
1310  */
1311 void network_do_queue(void) {
1312         static int doing_queue = 0;
1313         static time_t last_run = 0L;
1314         struct RoomProcList *ptr;
1315
1316         /*
1317          * Run no more frequently than once every n seconds
1318          */
1319         if ( (time(NULL) - last_run) < config.c_net_freq ) return;
1320
1321         /*
1322          * This is a simple concurrency check to make sure only one queue run
1323          * is done at a time.  We could do this with a mutex, but since we
1324          * don't really require extremely fine granularity here, we'll do it
1325          * with a static variable instead.
1326          */
1327         if (doing_queue) return;
1328         doing_queue = 1;
1329         last_run = time(NULL);
1330
1331         /*
1332          * Poll other Citadel nodes.
1333          */
1334         network_poll_other_citadel_nodes();
1335
1336         /*
1337          * Load the network map and filter list into memory.
1338          */
1339         read_network_map();
1340         filterlist = load_filter_list();
1341
1342         /* 
1343          * Go ahead and run the queue
1344          */
1345         lprintf(7, "network: loading outbound queue\n");
1346         ForEachRoom(network_queue_room, NULL);
1347
1348         lprintf(7, "network: running outbound queue\n");
1349         while (rplist != NULL) {
1350                 network_spoolout_room(rplist->name);
1351                 ptr = rplist;
1352                 rplist = rplist->next;
1353                 phree(ptr);
1354         }
1355
1356         lprintf(7, "network: processing inbound queue\n");
1357         network_do_spoolin();
1358
1359         /* Save the network map back to disk */
1360         write_network_map();
1361
1362         /* Free the filter list in memory */
1363         free_filter_list(filterlist);
1364         filterlist = NULL;
1365
1366         lprintf(7, "network: queue run completed\n");
1367         doing_queue = 0;
1368 }
1369
1370
1371 /*
1372  * cmd_netp() - authenticate to the server as another Citadel node polling
1373  *              for network traffic
1374  */
1375 void cmd_netp(char *cmdbuf)
1376 {
1377         char node[SIZ];
1378         char pass[SIZ];
1379
1380         char secret[SIZ];
1381         char nexthop[SIZ];
1382
1383         extract(node, cmdbuf, 0);
1384         extract(pass, cmdbuf, 1);
1385
1386         if (is_valid_node(nexthop, secret, node) != 0) {
1387                 cprintf("%d authentication failed\n", ERROR);
1388                 return;
1389         }
1390
1391         if (strcasecmp(pass, secret)) {
1392                 cprintf("%d authentication failed\n", ERROR);
1393                 return;
1394         }
1395
1396         if (network_talking_to(node, NTT_CHECK)) {
1397                 cprintf("%d Already talking to %s right now\n", ERROR, node);
1398                 return;
1399         }
1400
1401         safestrncpy(CC->net_node, node, sizeof CC->net_node);
1402         network_talking_to(node, NTT_ADD);
1403         cprintf("%d authenticated as network node '%s'\n", CIT_OK,
1404                 CC->net_node);
1405 }
1406
1407
1408
1409
1410
1411 /*
1412  * Module entry point
1413  */
1414 char *Dynamic_Module_Init(void)
1415 {
1416         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1417         CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1418         CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1419         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
1420         return "$Id$";
1421 }