]> code.citadel.org Git - citadel.git/blob - citadel/serv_network.c
* Initial support for having a room participate in a remote mailing list
[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  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
11  * This is a fairly high-level type of critical section.  It ensures that no
12  * two threads work on the netconfigs files at the same time.  Since we do
13  * so many things inside these, here are the rules:
14  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
15  *  2. Do *not* perform any I/O with the client during these sections.
16  *
17  */
18
19 /*
20  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
21  * requests that have not been confirmed will be deleted.
22  */
23 #define EXP     259200  /* three days */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <signal.h>
32 #include <pwd.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <dirent.h>
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
38 # include <time.h>
39 #else
40 # if HAVE_SYS_TIME_H
41 #  include <sys/time.h>
42 # else
43 #  include <time.h>
44 # endif
45 #endif
46
47 #include <sys/wait.h>
48 #include <string.h>
49 #include <limits.h>
50 #include "citadel.h"
51 #include "server.h"
52 #include "sysdep_decls.h"
53 #include "citserver.h"
54 #include "support.h"
55 #include "config.h"
56 #include "serv_extensions.h"
57 #include "room_ops.h"
58 #include "user_ops.h"
59 #include "policy.h"
60 #include "database.h"
61 #include "msgbase.h"
62 #include "tools.h"
63 #include "internet_addressing.h"
64 #include "serv_network.h"
65 #include "clientsocket.h"
66 #include "file_ops.h"
67
68 #ifndef HAVE_SNPRINTF
69 #include "snprintf.h"
70 #endif
71
72 /* Nonzero while we are doing outbound network processing */
73 static int doing_queue = 0;
74
75 /*
76  * When we do network processing, it's accomplished in two passes; one to
77  * gather a list of rooms and one to actually do them.  It's ok that rplist
78  * is global; this process *only* runs as part of the housekeeping loop and
79  * therefore only one will run at a time.
80  */
81 struct RoomProcList *rplist = NULL;
82
83 /*
84  * We build a map of network nodes during processing.
85  */
86 struct NetMap *the_netmap = NULL;
87 char *working_ignetcfg = NULL;
88
89 /*
90  * Load or refresh the Citadel network (IGnet) configuration for this node.
91  */
92 void load_working_ignetcfg(void) {
93         char *cfg;
94
95         cfg = CtdlGetSysConfig(IGNETCFG);
96         if (cfg == NULL) {
97                 cfg = strdup("");
98         }
99
100         working_ignetcfg = realloc(working_ignetcfg, strlen(cfg) + 1 );
101         strcpy(working_ignetcfg, cfg);
102 }
103
104
105
106
107
108 /*
109  * Keep track of what messages to reject
110  */
111 struct FilterList *load_filter_list(void) {
112         char *serialized_list = NULL;
113         int i;
114         char buf[SIZ];
115         struct FilterList *newlist = NULL;
116         struct FilterList *nptr;
117
118         serialized_list = CtdlGetSysConfig(FILTERLIST);
119         if (serialized_list == NULL) return(NULL); /* if null, no entries */
120
121         /* Use the string tokenizer to grab one line at a time */
122         for (i=0; i<num_tokens(serialized_list, '\n'); ++i) {
123                 extract_token(buf, serialized_list, i, '\n');
124                 nptr = (struct FilterList *) malloc(sizeof(struct FilterList));
125                 extract(nptr->fl_user, buf, 0);
126                 striplt(nptr->fl_user);
127                 extract(nptr->fl_room, buf, 1);
128                 striplt(nptr->fl_room);
129                 extract(nptr->fl_node, buf, 2);
130                 striplt(nptr->fl_node);
131
132                 /* Cowardly refuse to add an any/any/any entry that would
133                  * end up filtering every single message.
134                  */
135                 if (strlen(nptr->fl_user) + strlen(nptr->fl_room)
136                    + strlen(nptr->fl_node) == 0) {
137                         free(nptr);
138                 }
139                 else {
140                         nptr->next = newlist;
141                         newlist = nptr;
142                 }
143         }
144
145         free(serialized_list);
146         return newlist;
147 }
148
149
150 void free_filter_list(struct FilterList *fl) {
151         if (fl == NULL) return;
152         free_filter_list(fl->next);
153         free(fl);
154 }
155
156
157
158 /*
159  * Check the use table.  This is a list of messages which have recently
160  * arrived on the system.  It is maintained and queried to prevent the same
161  * message from being entered into the database multiple times if it happens
162  * to arrive multiple times by accident.
163  */
164 int network_usetable(struct CtdlMessage *msg) {
165
166         char msgid[SIZ];
167         struct cdbdata *cdbut;
168         struct UseTable ut;
169
170         /* Bail out if we can't generate a message ID */
171         if (msg == NULL) {
172                 return(0);
173         }
174         if (msg->cm_fields['I'] == NULL) {
175                 return(0);
176         }
177         if (strlen(msg->cm_fields['I']) == 0) {
178                 return(0);
179         }
180
181         /* Generate the message ID */
182         strcpy(msgid, msg->cm_fields['I']);
183         if (haschar(msgid, '@') == 0) {
184                 strcat(msgid, "@");
185                 if (msg->cm_fields['N'] != NULL) {
186                         strcat(msgid, msg->cm_fields['N']);
187                 }
188                 else {
189                         return(0);
190                 }
191         }
192
193         cdbut = cdb_fetch(CDB_USETABLE, msgid, strlen(msgid));
194         if (cdbut != NULL) {
195                 cdb_free(cdbut);
196                 return(1);
197         }
198
199         /* If we got to this point, it's unique: add it. */
200         strcpy(ut.ut_msgid, msgid);
201         ut.ut_timestamp = time(NULL);
202         cdb_store(CDB_USETABLE, msgid, strlen(msgid),
203                 &ut, sizeof(struct UseTable) );
204         return(0);
205 }
206
207
208 /* 
209  * Read the network map from its configuration file into memory.
210  */
211 void read_network_map(void) {
212         char *serialized_map = NULL;
213         int i;
214         char buf[SIZ];
215         struct NetMap *nmptr;
216
217         serialized_map = CtdlGetSysConfig(IGNETMAP);
218         if (serialized_map == NULL) return;     /* if null, no entries */
219
220         /* Use the string tokenizer to grab one line at a time */
221         for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
222                 extract_token(buf, serialized_map, i, '\n');
223                 nmptr = (struct NetMap *) malloc(sizeof(struct NetMap));
224                 extract(nmptr->nodename, buf, 0);
225                 nmptr->lastcontact = extract_long(buf, 1);
226                 extract(nmptr->nexthop, buf, 2);
227                 nmptr->next = the_netmap;
228                 the_netmap = nmptr;
229         }
230
231         free(serialized_map);
232 }
233
234
235 /*
236  * Write the network map from memory back to the configuration file.
237  */
238 void write_network_map(void) {
239         char *serialized_map = NULL;
240         struct NetMap *nmptr;
241
242         serialized_map = strdup("");
243
244         if (the_netmap != NULL) {
245                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
246                         serialized_map = realloc(serialized_map,
247                                                 (strlen(serialized_map)+SIZ) );
248                         if (strlen(nmptr->nodename) > 0) {
249                                 snprintf(&serialized_map[strlen(serialized_map)],
250                                         SIZ,
251                                         "%s|%ld|%s\n",
252                                         nmptr->nodename,
253                                         (long)nmptr->lastcontact,
254                                         nmptr->nexthop);
255                         }
256                 }
257         }
258
259         CtdlPutSysConfig(IGNETMAP, serialized_map);
260         free(serialized_map);
261
262         /* Now free the list */
263         while (the_netmap != NULL) {
264                 nmptr = the_netmap->next;
265                 free(the_netmap);
266                 the_netmap = nmptr;
267         }
268 }
269
270
271
272 /* 
273  * Check the network map and determine whether the supplied node name is
274  * valid.  If it is not a neighbor node, supply the name of a neighbor node
275  * which is the next hop.  If it *is* a neighbor node, we also fill in the
276  * shared secret.
277  */
278 int is_valid_node(char *nexthop, char *secret, char *node) {
279         int i;
280         char linebuf[SIZ];
281         char buf[SIZ];
282         int retval;
283         struct NetMap *nmptr;
284
285         if (node == NULL) {
286                 return(-1);
287         }
288
289         /*
290          * First try the neighbor nodes
291          */
292         if (working_ignetcfg == NULL) {
293                 lprintf(CTDL_ERR, "working_ignetcfg is NULL!\n");
294                 if (nexthop != NULL) {
295                         strcpy(nexthop, "");
296                 }
297                 return(-1);
298         }
299
300         retval = (-1);
301         if (nexthop != NULL) {
302                 strcpy(nexthop, "");
303         }
304
305         /* Use the string tokenizer to grab one line at a time */
306         for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
307                 extract_token(linebuf, working_ignetcfg, i, '\n');
308                 extract(buf, linebuf, 0);
309                 if (!strcasecmp(buf, node)) {
310                         if (nexthop != NULL) {
311                                 strcpy(nexthop, "");
312                         }
313                         if (secret != NULL) {
314                                 extract(secret, linebuf, 1);
315                         }
316                         retval = 0;
317                 }
318         }
319
320         if (retval == 0) {
321                 return(retval);         /* yup, it's a direct neighbor */
322         }
323
324         /*      
325          * If we get to this point we have to see if we know the next hop
326          */
327         if (the_netmap != NULL) {
328                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
329                         if (!strcasecmp(nmptr->nodename, node)) {
330                                 if (nexthop != NULL) {
331                                         strcpy(nexthop, nmptr->nexthop);
332                                 }
333                                 return(0);
334                         }
335                 }
336         }
337
338         /*
339          * If we get to this point, the supplied node name is bogus.
340          */
341         lprintf(CTDL_ERR, "Invalid node name <%s>\n", node);
342         return(-1);
343 }
344
345
346
347
348
349 void cmd_gnet(char *argbuf) {
350         char filename[SIZ];
351         char buf[SIZ];
352         FILE *fp;
353
354         if (CtdlAccessCheck(ac_room_aide)) return;
355         assoc_file_name(filename, sizeof filename, &CC->room, "netconfigs");
356         cprintf("%d Network settings for room #%ld <%s>\n",
357                 LISTING_FOLLOWS,
358                 CC->room.QRnumber, CC->room.QRname);
359
360         fp = fopen(filename, "r");
361         if (fp != NULL) {
362                 while (fgets(buf, sizeof buf, fp) != NULL) {
363                         buf[strlen(buf)-1] = 0;
364                         cprintf("%s\n", buf);
365                 }
366                 fclose(fp);
367         }
368
369         cprintf("000\n");
370 }
371
372
373 void cmd_snet(char *argbuf) {
374         char tempfilename[SIZ];
375         char filename[SIZ];
376         char buf[SIZ];
377         FILE *fp;
378
379         unbuffer_output();
380
381         if (CtdlAccessCheck(ac_room_aide)) return;
382         safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
383         assoc_file_name(filename, sizeof filename, &CC->room, "netconfigs");
384
385         fp = fopen(tempfilename, "w");
386         if (fp == NULL) {
387                 cprintf("%d Cannot open %s: %s\n",
388                         ERROR + INTERNAL_ERROR,
389                         tempfilename,
390                         strerror(errno));
391         }
392
393         cprintf("%d %s\n", SEND_LISTING, tempfilename);
394         while (client_gets(buf), strcmp(buf, "000")) {
395                 fprintf(fp, "%s\n", buf);
396         }
397         fclose(fp);
398
399         /* Now copy the temp file to its permanent location
400          * (We use /bin/mv instead of link() because they may be on
401          * different filesystems)
402          */
403         unlink(filename);
404         snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
405         begin_critical_section(S_NETCONFIGS);
406         system(buf);
407         end_critical_section(S_NETCONFIGS);
408 }
409
410
411 /*
412  * Spools out one message from the list.
413  */
414 void network_spool_msg(long msgnum, void *userdata) {
415         struct SpoolControl *sc;
416         int err;
417         int i;
418         char *newpath = NULL;
419         char *instr = NULL;
420         size_t instr_len = SIZ;
421         struct CtdlMessage *msg = NULL;
422         struct CtdlMessage *imsg;
423         struct namelist *nptr;
424         struct maplist *mptr;
425         struct ser_ret sermsg;
426         FILE *fp;
427         char filename[SIZ];
428         char buf[SIZ];
429         int bang = 0;
430         int send = 1;
431         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
432         long list_msgnum = 0L;
433         int ok_to_participate = 0;
434
435         sc = (struct SpoolControl *)userdata;
436
437         /*
438          * Process mailing list recipients
439          */
440         instr_len = SIZ;
441         if (sc->listrecps != NULL) {
442         
443                 /* First, copy it to the spoolout room */
444                 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
445                 if (err != 0) return;
446
447                 /* 
448                  * Figure out how big a buffer we need to allocate
449                  */
450                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
451                         instr_len = instr_len + strlen(nptr->name);
452                 }
453         
454                 /*
455                  * allocate...
456                  */
457                 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
458                 instr = malloc(instr_len);
459                 if (instr == NULL) {
460                         lprintf(CTDL_EMERG,
461                                 "Cannot allocate %ld bytes for instr...\n",
462                                 (long)instr_len);
463                         abort();
464                 }
465                 snprintf(instr, instr_len,
466                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
467                         "bounceto|postmaster@%s\n" ,
468                         SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn );
469         
470                 /* Generate delivery instructions for each recipient */
471                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
472                         size_t tmp = strlen(instr);
473                         snprintf(&instr[tmp], instr_len - tmp,
474                                  "remote|%s|0||\n", nptr->name);
475                 }
476         
477                 /*
478                  * Generate a message from the instructions
479                  */
480                 imsg = malloc(sizeof(struct CtdlMessage));
481                 memset(imsg, 0, sizeof(struct CtdlMessage));
482                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
483                 imsg->cm_anon_type = MES_NORMAL;
484                 imsg->cm_format_type = FMT_RFC822;
485                 imsg->cm_fields['A'] = strdup("Citadel");
486                 imsg->cm_fields['M'] = instr;
487         
488                 /* Save delivery instructions in spoolout room */
489                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
490                 CtdlFreeMessage(imsg);
491         }
492
493         /*
494          * Process digest recipients
495          */
496         if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
497                 fprintf(sc->digestfp,   " -----------------------------------"
498                                         "------------------------------------"
499                                         "-------\n");
500                 CtdlRedirectOutput(sc->digestfp, -1);
501                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 0);
502                 CtdlRedirectOutput(NULL, -1);
503                 sc->num_msgs_spooled += 1;
504         }
505
506         /*
507          * Process client-side list participations for this room
508          */
509         instr_len = SIZ;
510         if (sc->participates != NULL) {
511                 msg = CtdlFetchMessage(msgnum, 1);
512                 if (msg != NULL) {
513
514                         /* Only send messages which originated on our own Citadel
515                          * network, otherwise we'll end up sending the remote
516                          * mailing list's messages back to it, which is rude...
517                          */
518                         ok_to_participate = 0;
519                         if (msg->cm_fields['N'] != NULL) {
520                                 if (!strcasecmp(msg->cm_fields['N'], config.c_nodename)) {
521                                         ok_to_participate = 1;
522                                 }
523                                 if (is_valid_node(NULL, NULL, msg->cm_fields['N']) == 0) {
524                                         ok_to_participate = 1;
525                                 }
526                         }
527                         if (ok_to_participate) {
528                                 if (msg->cm_fields['F'] != NULL) {
529                                         free(msg->cm_fields['F']);
530                                 }
531                                 msg->cm_fields['F'] = malloc(SIZ);
532                                 /* Replace the Internet email address of the actual
533                                 * author with the email address of the room itself,
534                                 * so the remote listserv doesn't reject us.
535                                 * FIXME ... I want to be able to pick any address
536                                 */
537                                 snprintf(msg->cm_fields['F'], SIZ,
538                                         "room_%s@%s", CC->room.QRname,
539                                         config.c_fqdn);
540                                 for (i=0; i<strlen(msg->cm_fields['F']); ++i) {
541                                         if (isspace(msg->cm_fields['F'][i])) {
542                                                 msg->cm_fields['F'][i] = '_';
543                                         }
544                                 }
545
546                                 /* Now save it and generate delivery instructions */
547                                 list_msgnum = CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
548
549                                 /* 
550                                  * Figure out how big a buffer we need to allocate
551                                  */
552                                 for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
553                                         instr_len = instr_len + strlen(nptr->name);
554                                 }
555                         
556                                 /*
557                                  * allocate...
558                                  */
559                                 instr = malloc(instr_len);
560                                 if (instr == NULL) {
561                                         lprintf(CTDL_EMERG,
562                                                 "Cannot allocate %ld bytes for instr...\n",
563                                                 (long)instr_len);
564                                         abort();
565                                 }
566                                 snprintf(instr, instr_len,
567                                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
568                                         "bounceto|postmaster@%s\n" ,
569                                         SPOOLMIME, list_msgnum, (long)time(NULL), config.c_fqdn );
570                         
571                                 /* Generate delivery instructions for each recipient */
572                                 for (nptr = sc->participates; nptr != NULL; nptr = nptr->next) {
573                                         size_t tmp = strlen(instr);
574                                         snprintf(&instr[tmp], instr_len - tmp,
575                                                  "remote|%s|0||\n", nptr->name);
576                                 }
577                         
578                                 /*
579                                  * Generate a message from the instructions
580                                  */
581                                 imsg = malloc(sizeof(struct CtdlMessage));
582                                 memset(imsg, 0, sizeof(struct CtdlMessage));
583                                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
584                                 imsg->cm_anon_type = MES_NORMAL;
585                                 imsg->cm_format_type = FMT_RFC822;
586                                 imsg->cm_fields['A'] = strdup("Citadel");
587                                 imsg->cm_fields['M'] = instr;
588                         
589                                 /* Save delivery instructions in spoolout room */
590                                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
591                                 CtdlFreeMessage(imsg);
592                         }
593                         CtdlFreeMessage(msg);
594                 }
595         }
596         
597         /*
598          * Process IGnet push shares
599          */
600         if (sc->ignet_push_shares != NULL) {
601         
602                 msg = CtdlFetchMessage(msgnum, 1);
603                 if (msg != NULL) {
604                         size_t newpath_len;
605
606                         /* Prepend our node name to the Path field whenever
607                          * sending a message to another IGnet node
608                          */
609                         if (msg->cm_fields['P'] == NULL) {
610                                 msg->cm_fields['P'] = strdup("username");
611                         }
612                         newpath_len = strlen(msg->cm_fields['P']) +
613                                  strlen(config.c_nodename) + 2;
614                         newpath = malloc(newpath_len);
615                         snprintf(newpath, newpath_len, "%s!%s",
616                                  config.c_nodename, msg->cm_fields['P']);
617                         free(msg->cm_fields['P']);
618                         msg->cm_fields['P'] = newpath;
619
620                         /*
621                          * Determine if this message is set to be deleted
622                          * after sending out on the network
623                          */
624                         if (msg->cm_fields['S'] != NULL) {
625                                 if (!strcasecmp(msg->cm_fields['S'],
626                                    "CANCEL")) {
627                                         delete_after_send = 1;
628                                 }
629                         }
630
631                         /* Now send it to every node */
632                         for (mptr = sc->ignet_push_shares; mptr != NULL;
633                             mptr = mptr->next) {
634
635                                 send = 1;
636
637                                 /* Check for valid node name */
638                                 if (is_valid_node(NULL, NULL, mptr->remote_nodename) != 0) {
639                                         lprintf(CTDL_ERR, "Invalid node <%s>\n",
640                                                 mptr->remote_nodename);
641                                         send = 0;
642                                 }
643
644                                 /* Check for split horizon */
645                                 lprintf(CTDL_DEBUG, "Path is %s\n", msg->cm_fields['P']);
646                                 bang = num_tokens(msg->cm_fields['P'], '!');
647                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
648                                         extract_token(buf, msg->cm_fields['P'],
649                                                 i, '!');
650                                         if (!strcasecmp(buf, mptr->remote_nodename)) {
651                                                 send = 0;
652                                         }
653                                 }
654
655                                 /* Send the message */
656                                 if (send == 1) {
657
658                                         /*
659                                          * Force the message to appear in the correct room
660                                          * on the far end by setting the C field correctly
661                                          */
662                                         if (msg->cm_fields['C'] != NULL) {
663                                                 free(msg->cm_fields['C']);
664                                         }
665                                         if (strlen(mptr->remote_roomname) > 0) {
666                                                 msg->cm_fields['C'] = strdup(mptr->remote_roomname);
667                                         }
668                                         else {
669                                                 msg->cm_fields['C'] = strdup(CC->room.QRname);
670                                         }
671
672                                         /* serialize it for transmission */
673                                         serialize_message(&sermsg, msg);
674
675                                         /* write it to the spool file */
676                                         snprintf(filename, sizeof filename,
677                                                 "./network/spoolout/%s",
678                                                 mptr->remote_nodename);
679                                         fp = fopen(filename, "ab");
680                                         if (fp != NULL) {
681                                                 fwrite(sermsg.ser,
682                                                         sermsg.len, 1, fp);
683                                                 fclose(fp);
684                                         }
685
686                                         /* free the serialized version */
687                                         free(sermsg.ser);
688                                 }
689                         }
690                         CtdlFreeMessage(msg);
691                 }
692         }
693
694         /* update lastsent */
695         sc->lastsent = msgnum;
696
697         /* Delete this message if delete-after-send is set */
698         if (delete_after_send) {
699                 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
700         }
701
702 }
703         
704
705 /*
706  * Deliver digest messages
707  */
708 void network_deliver_digest(struct SpoolControl *sc) {
709         char buf[SIZ];
710         int i;
711         struct CtdlMessage *msg;
712         long msglen;
713         long msgnum;
714         char *instr = NULL;
715         size_t instr_len = SIZ;
716         struct CtdlMessage *imsg;
717         struct namelist *nptr;
718
719         if (sc->num_msgs_spooled < 1) {
720                 fclose(sc->digestfp);
721                 sc->digestfp = NULL;
722                 return;
723         }
724
725         msg = malloc(sizeof(struct CtdlMessage));
726         memset(msg, 0, sizeof(struct CtdlMessage));
727         msg->cm_magic = CTDLMESSAGE_MAGIC;
728         msg->cm_format_type = FMT_RFC822;
729         msg->cm_anon_type = MES_NORMAL;
730
731         sprintf(buf, "%ld", time(NULL));
732         msg->cm_fields['T'] = strdup(buf);
733         msg->cm_fields['A'] = strdup(CC->room.QRname);
734         msg->cm_fields['U'] = strdup(CC->room.QRname);
735         sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
736         for (i=0; i<strlen(buf); ++i) {
737                 if (isspace(buf[i])) buf[i]='_';
738                 buf[i] = tolower(buf[i]);
739         }
740         msg->cm_fields['F'] = strdup(buf);
741
742         fseek(sc->digestfp, 0L, SEEK_END);
743         msglen = ftell(sc->digestfp);
744
745         msg->cm_fields['M'] = malloc(msglen + 1);
746         fseek(sc->digestfp, 0L, SEEK_SET);
747         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
748         msg->cm_fields['M'][msglen] = 0;
749
750         fclose(sc->digestfp);
751         sc->digestfp = NULL;
752
753         msgnum = CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
754         CtdlFreeMessage(msg);
755
756         /* Now generate the delivery instructions */
757
758         /* 
759          * Figure out how big a buffer we need to allocate
760          */
761         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
762                 instr_len = instr_len + strlen(nptr->name);
763         }
764         
765         /*
766          * allocate...
767          */
768         lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
769         instr = malloc(instr_len);
770         if (instr == NULL) {
771                 lprintf(CTDL_EMERG, "Cannot allocate %ld bytes for instr...\n",
772                         (long)instr_len);
773                 abort();
774         }
775         snprintf(instr, instr_len,
776                 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
777                 "bounceto|postmaster@%s\n" ,
778                 SPOOLMIME, msgnum, (long)time(NULL), config.c_fqdn );
779
780         /* Generate delivery instructions for each recipient */
781         for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
782                 size_t tmp = strlen(instr);
783                 snprintf(&instr[tmp], instr_len - tmp,
784                          "remote|%s|0||\n", nptr->name);
785         }
786
787         /*
788          * Generate a message from the instructions
789          */
790         imsg = malloc(sizeof(struct CtdlMessage));
791         memset(imsg, 0, sizeof(struct CtdlMessage));
792         imsg->cm_magic = CTDLMESSAGE_MAGIC;
793         imsg->cm_anon_type = MES_NORMAL;
794         imsg->cm_format_type = FMT_RFC822;
795         imsg->cm_fields['A'] = strdup("Citadel");
796         imsg->cm_fields['M'] = instr;
797
798         /* Save delivery instructions in spoolout room */
799         CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
800         CtdlFreeMessage(imsg);
801 }
802
803
804 /*
805  * Batch up and send all outbound traffic from the current room
806  */
807 void network_spoolout_room(char *room_to_spool) {
808         char filename[SIZ];
809         char buf[SIZ];
810         char instr[SIZ];
811         char nodename[SIZ];
812         char roomname[SIZ];
813         char nexthop[SIZ];
814         FILE *fp;
815         struct SpoolControl sc;
816         struct namelist *nptr = NULL;
817         struct maplist *mptr = NULL;
818         size_t miscsize = 0;
819         size_t linesize = 0;
820         int skipthisline = 0;
821         int i;
822
823         if (getroom(&CC->room, room_to_spool) != 0) {
824                 lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", room_to_spool);
825                 return;
826         }
827
828         memset(&sc, 0, sizeof(struct SpoolControl));
829         assoc_file_name(filename, sizeof filename, &CC->room, "netconfigs");
830
831         begin_critical_section(S_NETCONFIGS);
832
833         fp = fopen(filename, "r");
834         if (fp == NULL) {
835                 end_critical_section(S_NETCONFIGS);
836                 return;
837         }
838
839         lprintf(CTDL_INFO, "Networking started for <%s>\n", CC->room.QRname);
840
841         while (fgets(buf, sizeof buf, fp) != NULL) {
842                 buf[strlen(buf)-1] = 0;
843
844                 extract(instr, buf, 0);
845                 if (!strcasecmp(instr, "lastsent")) {
846                         sc.lastsent = extract_long(buf, 1);
847                 }
848                 else if (!strcasecmp(instr, "listrecp")) {
849                         nptr = (struct namelist *)
850                                 malloc(sizeof(struct namelist));
851                         nptr->next = sc.listrecps;
852                         extract(nptr->name, buf, 1);
853                         sc.listrecps = nptr;
854                 }
855                 else if (!strcasecmp(instr, "participate")) {
856                         nptr = (struct namelist *)
857                                 malloc(sizeof(struct namelist));
858                         nptr->next = sc.participates;
859                         extract(nptr->name, buf, 1);
860                         sc.participates = nptr;
861                 }
862                 else if (!strcasecmp(instr, "digestrecp")) {
863                         nptr = (struct namelist *)
864                                 malloc(sizeof(struct namelist));
865                         nptr->next = sc.digestrecps;
866                         extract(nptr->name, buf, 1);
867                         sc.digestrecps = nptr;
868                 }
869                 else if (!strcasecmp(instr, "ignet_push_share")) {
870                         /* by checking each node's validity, we automatically
871                          * purge nodes which do not exist from room network
872                          * configurations at this time.
873                          */
874                         extract(nodename, buf, 1);
875                         extract(roomname, buf, 2);
876                         strcpy(nexthop, "xxx");
877                         if (is_valid_node(nexthop, NULL, nodename) == 0) {
878                                 if (strlen(nexthop) == 0) {
879                                         mptr = (struct maplist *)
880                                                 malloc(sizeof(struct maplist));
881                                         mptr->next = sc.ignet_push_shares;
882                                         strcpy(mptr->remote_nodename, nodename);
883                                         strcpy(mptr->remote_roomname, roomname);
884                                         sc.ignet_push_shares = mptr;
885                                 }
886                         }
887                 }
888                 else {
889                         /* Preserve 'other' lines ... *unless* they happen to
890                          * be subscribe/unsubscribe pendings with expired
891                          * timestamps.
892                          */
893                         skipthisline = 0;
894                         if (!strncasecmp(buf, "subpending|", 11)) {
895                                 if (time(NULL) - extract_long(buf, 4) > EXP) {
896                                         skipthisline = 1;
897                                 }
898                         }
899                         if (!strncasecmp(buf, "unsubpending|", 13)) {
900                                 if (time(NULL) - extract_long(buf, 3) > EXP) {
901                                         skipthisline = 1;
902                                 }
903                         }
904
905                         if (skipthisline == 0) {
906                                 linesize = strlen(buf);
907                                 sc.misc = realloc(sc.misc,
908                                         (miscsize + linesize + 2) );
909                                 sprintf(&sc.misc[miscsize], "%s\n", buf);
910                                 miscsize = miscsize + linesize + 1;
911                         }
912                 }
913
914
915         }
916         fclose(fp);
917
918         /* If there are digest recipients, we have to build a digest */
919         if (sc.digestrecps != NULL) {
920                 sc.digestfp = tmpfile();
921                 fprintf(sc.digestfp, "Content-type: text/plain\n\n");
922         }
923
924         /* Do something useful */
925         CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL,
926                 network_spool_msg, &sc);
927
928         /* If we wrote a digest, deliver it and then close it */
929         snprintf(buf, sizeof buf, "room_%s@%s",
930                 CC->room.QRname, config.c_fqdn);
931         for (i=0; i<strlen(buf); ++i) {
932                 buf[i] = tolower(buf[i]);
933                 if (isspace(buf[i])) buf[i] = '_';
934         }
935         if (sc.digestfp != NULL) {
936                 fprintf(sc.digestfp,    " -----------------------------------"
937                                         "------------------------------------"
938                                         "-------\n"
939                                         "You are subscribed to the '%s' "
940                                         "list.\n"
941                                         "To post to the list: %s\n",
942                                         CC->room.QRname, buf
943                 );
944                 network_deliver_digest(&sc);    /* deliver and close */
945         }
946
947         /* Now rewrite the config file */
948         fp = fopen(filename, "w");
949         if (fp == NULL) {
950                 lprintf(CTDL_CRIT, "ERROR: cannot open %s: %s\n",
951                         filename, strerror(errno));
952         }
953         else {
954                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
955
956                 /* Write out the listrecps while freeing from memory at the
957                  * same time.  Am I clever or what?  :)
958                  */
959                 while (sc.listrecps != NULL) {
960                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
961                         nptr = sc.listrecps->next;
962                         free(sc.listrecps);
963                         sc.listrecps = nptr;
964                 }
965                 /* Do the same for digestrecps */
966                 while (sc.digestrecps != NULL) {
967                         fprintf(fp, "digestrecp|%s\n", sc.digestrecps->name);
968                         nptr = sc.digestrecps->next;
969                         free(sc.digestrecps);
970                         sc.digestrecps = nptr;
971                 }
972                 /* Do the same for participates */
973                 while (sc.participates != NULL) {
974                         fprintf(fp, "participate|%s\n", sc.participates->name);
975                         nptr = sc.participates->next;
976                         free(sc.participates);
977                         sc.participates = nptr;
978                 }
979                 while (sc.ignet_push_shares != NULL) {
980                         /* by checking each node's validity, we automatically
981                          * purge nodes which do not exist from room network
982                          * configurations at this time.
983                          */
984                         if (is_valid_node(NULL, NULL, sc.ignet_push_shares->remote_nodename) == 0) {
985                         }
986                         fprintf(fp, "ignet_push_share|%s",
987                                 sc.ignet_push_shares->remote_nodename);
988                         if (strlen(sc.ignet_push_shares->remote_roomname) > 0) {
989                                 fprintf(fp, "|%s", sc.ignet_push_shares->remote_roomname);
990                         }
991                         fprintf(fp, "\n");
992                         mptr = sc.ignet_push_shares->next;
993                         free(sc.ignet_push_shares);
994                         sc.ignet_push_shares = mptr;
995                 }
996                 if (sc.misc != NULL) {
997                         fwrite(sc.misc, strlen(sc.misc), 1, fp);
998                 }
999                 free(sc.misc);
1000
1001                 fclose(fp);
1002         }
1003         end_critical_section(S_NETCONFIGS);
1004 }
1005
1006
1007
1008 /*
1009  * Send the *entire* contents of the current room to one specific network node,
1010  * ignoring anything we know about which messages have already undergone
1011  * network processing.  This can be used to bring a new node into sync.
1012  */
1013 int network_sync_to(char *target_node) {
1014         struct SpoolControl sc;
1015         int num_spooled = 0;
1016         int found_node = 0;
1017         char buf[SIZ];
1018         char sc_type[SIZ];
1019         char sc_node[SIZ];
1020         char sc_room[SIZ];
1021         char filename[SIZ];
1022         FILE *fp;
1023
1024         /* Grab the configuration line we're looking for */
1025         assoc_file_name(filename, sizeof filename, &CC->room, "netconfigs");
1026         begin_critical_section(S_NETCONFIGS);
1027         fp = fopen(filename, "r");
1028         if (fp == NULL) {
1029                 end_critical_section(S_NETCONFIGS);
1030                 return(-1);
1031         }
1032         while (fgets(buf, sizeof buf, fp) != NULL) {
1033                 buf[strlen(buf)-1] = 0;
1034                 extract(sc_type, buf, 0);
1035                 extract(sc_node, buf, 1);
1036                 extract(sc_room, buf, 2);
1037                 if ( (!strcasecmp(sc_type, "ignet_push_share"))
1038                    && (!strcasecmp(sc_node, target_node)) ) {
1039                         found_node = 1;
1040                         
1041                         /* Concise syntax because we don't need a full linked-list */
1042                         memset(&sc, 0, sizeof(struct SpoolControl));
1043                         sc.ignet_push_shares = (struct maplist *)
1044                                 malloc(sizeof(struct maplist));
1045                         sc.ignet_push_shares->next = NULL;
1046                         safestrncpy(sc.ignet_push_shares->remote_nodename,
1047                                 sc_node,
1048                                 sizeof sc.ignet_push_shares->remote_nodename);
1049                         safestrncpy(sc.ignet_push_shares->remote_roomname,
1050                                 sc_room,
1051                                 sizeof sc.ignet_push_shares->remote_roomname);
1052                 }
1053         }
1054         fclose(fp);
1055         end_critical_section(S_NETCONFIGS);
1056
1057         if (!found_node) return(-1);
1058
1059         /* Send ALL messages */
1060         num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL,
1061                 network_spool_msg, &sc);
1062
1063         /* Concise cleanup because we know there's only one node in the sc */
1064         free(sc.ignet_push_shares);
1065
1066         lprintf(CTDL_NOTICE, "Synchronized %d messages to <%s>\n",
1067                 num_spooled, target_node);
1068         return(num_spooled);
1069 }
1070
1071
1072 /*
1073  * Implements the NSYN command
1074  */
1075 void cmd_nsyn(char *argbuf) {
1076         int num_spooled;
1077         char target_node[SIZ];
1078
1079         if (CtdlAccessCheck(ac_aide)) return;
1080
1081         extract(target_node, argbuf, 0);
1082         num_spooled = network_sync_to(target_node);
1083         if (num_spooled >= 0) {
1084                 cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled);
1085         }
1086         else {
1087                 cprintf("%d No such room/node share exists.\n",
1088                         ERROR + ROOM_NOT_FOUND);
1089         }
1090 }
1091
1092
1093
1094 /*
1095  * Batch up and send all outbound traffic from the current room
1096  */
1097 void network_queue_room(struct ctdlroom *qrbuf, void *data) {
1098         struct RoomProcList *ptr;
1099
1100         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
1101         if (ptr == NULL) return;
1102
1103         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
1104         ptr->next = rplist;
1105         rplist = ptr;
1106 }
1107
1108
1109 /*
1110  * Learn topology from path fields
1111  */
1112 void network_learn_topology(char *node, char *path) {
1113         char nexthop[SIZ];
1114         struct NetMap *nmptr;
1115
1116         strcpy(nexthop, "");
1117
1118         if (num_tokens(path, '!') < 3) return;
1119         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
1120                 if (!strcasecmp(nmptr->nodename, node)) {
1121                         extract_token(nmptr->nexthop, path, 0, '!');
1122                         nmptr->lastcontact = time(NULL);
1123                         return;
1124                 }
1125         }
1126
1127         /* If we got here then it's not in the map, so add it. */
1128         nmptr = (struct NetMap *) malloc(sizeof (struct NetMap));
1129         strcpy(nmptr->nodename, node);
1130         nmptr->lastcontact = time(NULL);
1131         extract_token(nmptr->nexthop, path, 0, '!');
1132         nmptr->next = the_netmap;
1133         the_netmap = nmptr;
1134 }
1135
1136
1137
1138
1139 /*
1140  * Bounce a message back to the sender
1141  */
1142 void network_bounce(struct CtdlMessage *msg, char *reason) {
1143         char *oldpath = NULL;
1144         char buf[SIZ];
1145         char bouncesource[SIZ];
1146         char recipient[SIZ];
1147         struct recptypes *valid = NULL;
1148         char force_room[ROOMNAMELEN];
1149         static int serialnum = 0;
1150         size_t size;
1151
1152         lprintf(CTDL_DEBUG, "entering network_bounce()\n");
1153
1154         if (msg == NULL) return;
1155
1156         snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename);
1157
1158         /* 
1159          * Give it a fresh message ID
1160          */
1161         if (msg->cm_fields['I'] != NULL) {
1162                 free(msg->cm_fields['I']);
1163         }
1164         snprintf(buf, sizeof buf, "%ld.%04lx.%04x@%s",
1165                 (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn);
1166         msg->cm_fields['I'] = strdup(buf);
1167
1168         /*
1169          * FIXME ... right now we're just sending a bounce; we really want to
1170          * include the text of the bounced message.
1171          */
1172         if (msg->cm_fields['M'] != NULL) {
1173                 free(msg->cm_fields['M']);
1174         }
1175         msg->cm_fields['M'] = strdup(reason);
1176         msg->cm_format_type = 0;
1177
1178         /*
1179          * Turn the message around
1180          */
1181         if (msg->cm_fields['R'] == NULL) {
1182                 free(msg->cm_fields['R']);
1183         }
1184
1185         if (msg->cm_fields['D'] == NULL) {
1186                 free(msg->cm_fields['D']);
1187         }
1188
1189         snprintf(recipient, sizeof recipient, "%s@%s",
1190                 msg->cm_fields['A'], msg->cm_fields['N']);
1191
1192         if (msg->cm_fields['A'] == NULL) {
1193                 free(msg->cm_fields['A']);
1194         }
1195
1196         if (msg->cm_fields['N'] == NULL) {
1197                 free(msg->cm_fields['N']);
1198         }
1199
1200         msg->cm_fields['A'] = strdup(BOUNCESOURCE);
1201         msg->cm_fields['N'] = strdup(config.c_nodename);
1202         
1203
1204         /* prepend our node to the path */
1205         if (msg->cm_fields['P'] != NULL) {
1206                 oldpath = msg->cm_fields['P'];
1207                 msg->cm_fields['P'] = NULL;
1208         }
1209         else {
1210                 oldpath = strdup("unknown_user");
1211         }
1212         size = strlen(oldpath) + SIZ;
1213         msg->cm_fields['P'] = malloc(size);
1214         snprintf(msg->cm_fields['P'], size, "%s!%s", config.c_nodename, oldpath);
1215         free(oldpath);
1216
1217         /* Now submit the message */
1218         valid = validate_recipients(recipient);
1219         if (valid != NULL) if (valid->num_error > 0) {
1220                 free(valid);
1221                 valid = NULL;
1222         }
1223         if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) {
1224                 strcpy(force_room, config.c_aideroom);
1225         }
1226         else {
1227                 strcpy(force_room, "");
1228         }
1229         if ( (valid == NULL) && (strlen(force_room) == 0) ) {
1230                 strcpy(force_room, config.c_aideroom);
1231         }
1232         CtdlSubmitMsg(msg, valid, force_room);
1233
1234         /* Clean up */
1235         if (valid != NULL) free(valid);
1236         CtdlFreeMessage(msg);
1237         lprintf(CTDL_DEBUG, "leaving network_bounce()\n");
1238 }
1239
1240
1241
1242
1243 /*
1244  * Process a buffer containing a single message from a single file
1245  * from the inbound queue 
1246  */
1247 void network_process_buffer(char *buffer, long size) {
1248         struct CtdlMessage *msg;
1249         long pos;
1250         int field;
1251         struct recptypes *recp = NULL;
1252         char target_room[ROOMNAMELEN];
1253         struct ser_ret sermsg;
1254         char *oldpath = NULL;
1255         char filename[SIZ];
1256         FILE *fp;
1257         char nexthop[SIZ];
1258         unsigned char firstbyte;
1259         unsigned char lastbyte;
1260
1261         /* Validate just a little bit.  First byte should be FF and
1262          * last byte should be 00.
1263          */
1264         memcpy(&firstbyte, &buffer[0], 1);
1265         memcpy(&lastbyte, &buffer[size-1], 1);
1266         if ( (firstbyte != 255) || (lastbyte != 0) ) {
1267                 lprintf(CTDL_ERR, "Corrupt message!  Ignoring.\n");
1268                 return;
1269         }
1270
1271         /* Set default target room to trash */
1272         strcpy(target_room, TWITROOM);
1273
1274         /* Load the message into memory */
1275         msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1276         memset(msg, 0, sizeof(struct CtdlMessage));
1277         msg->cm_magic = CTDLMESSAGE_MAGIC;
1278         msg->cm_anon_type = buffer[1];
1279         msg->cm_format_type = buffer[2];
1280
1281         for (pos = 3; pos < size; ++pos) {
1282                 field = buffer[pos];
1283                 msg->cm_fields[field] = strdup(&buffer[pos+1]);
1284                 pos = pos + strlen(&buffer[(int)pos]);
1285         }
1286
1287         /* Check for message routing */
1288         if (msg->cm_fields['D'] != NULL) {
1289                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
1290
1291                         /* route the message */
1292                         strcpy(nexthop, "");
1293                         if (is_valid_node(nexthop, NULL,
1294                            msg->cm_fields['D']) == 0) {
1295
1296                                 /* prepend our node to the path */
1297                                 if (msg->cm_fields['P'] != NULL) {
1298                                         oldpath = msg->cm_fields['P'];
1299                                         msg->cm_fields['P'] = NULL;
1300                                 }
1301                                 else {
1302                                         oldpath = strdup("unknown_user");
1303                                 }
1304                                 size = strlen(oldpath) + SIZ;
1305                                 msg->cm_fields['P'] = malloc(size);
1306                                 snprintf(msg->cm_fields['P'], size, "%s!%s",
1307                                         config.c_nodename, oldpath);
1308                                 free(oldpath);
1309
1310                                 /* serialize the message */
1311                                 serialize_message(&sermsg, msg);
1312
1313                                 /* now send it */
1314                                 if (strlen(nexthop) == 0) {
1315                                         strcpy(nexthop, msg->cm_fields['D']);
1316                                 }
1317                                 snprintf(filename, sizeof filename,
1318                                         "./network/spoolout/%s", nexthop);
1319                                 fp = fopen(filename, "ab");
1320                                 if (fp != NULL) {
1321                                         fwrite(sermsg.ser,
1322                                                 sermsg.len, 1, fp);
1323                                         fclose(fp);
1324                                 }
1325                                 free(sermsg.ser);
1326                                 CtdlFreeMessage(msg);
1327                                 return;
1328                         }
1329                         
1330                         else {  /* invalid destination node name */
1331
1332                                 network_bounce(msg,
1333 "A message you sent could not be delivered due to an invalid destination node"
1334 " name.  Please check the address and try sending the message again.\n");
1335                                 msg = NULL;
1336                                 return;
1337
1338                         }
1339                 }
1340         }
1341
1342         /*
1343          * Check to see if we already have a copy of this message, and
1344          * abort its processing if so.  (We used to post a warning to Aide>
1345          * every time this happened, but the network is now so densely
1346          * connected that it's inevitable.)
1347          */
1348         if (network_usetable(msg) != 0) {
1349                 return;
1350         }
1351
1352         /* Learn network topology from the path */
1353         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
1354                 network_learn_topology(msg->cm_fields['N'], 
1355                                         msg->cm_fields['P']);
1356         }
1357
1358         /* Is the sending node giving us a very persuasive suggestion about
1359          * which room this message should be saved in?  If so, go with that.
1360          */
1361         if (msg->cm_fields['C'] != NULL) {
1362                 safestrncpy(target_room,
1363                         msg->cm_fields['C'],
1364                         sizeof target_room);
1365         }
1366
1367         /* Otherwise, does it have a recipient?  If so, validate it... */
1368         else if (msg->cm_fields['R'] != NULL) {
1369                 recp = validate_recipients(msg->cm_fields['R']);
1370                 if (recp != NULL) if (recp->num_error > 0) {
1371                         network_bounce(msg,
1372 "A message you sent could not be delivered due to an invalid address.\n"
1373 "Please check the address and try sending the message again.\n");
1374                         msg = NULL;
1375                         free(recp);
1376                         return;
1377                 }
1378                 strcpy(target_room, "");        /* no target room if mail */
1379         }
1380
1381         /* Our last shot at finding a home for this message is to see if
1382          * it has the O field (Originating room) set.
1383          */
1384         else if (msg->cm_fields['O'] != NULL) {
1385                 safestrncpy(target_room,
1386                         msg->cm_fields['O'],
1387                         sizeof target_room);
1388         }
1389
1390         /* Strip out fields that are only relevant during transit */
1391         if (msg->cm_fields['D'] != NULL) {
1392                 free(msg->cm_fields['D']);
1393                 msg->cm_fields['D'] = NULL;
1394         }
1395         if (msg->cm_fields['C'] != NULL) {
1396                 free(msg->cm_fields['C']);
1397                 msg->cm_fields['C'] = NULL;
1398         }
1399
1400         /* save the message into a room */
1401         if (PerformNetprocHooks(msg, target_room) == 0) {
1402                 msg->cm_flags = CM_SKIP_HOOKS;
1403                 CtdlSubmitMsg(msg, recp, target_room);
1404         }
1405         CtdlFreeMessage(msg);
1406         free(recp);
1407 }
1408
1409
1410 /*
1411  * Process a single message from a single file from the inbound queue 
1412  */
1413 void network_process_message(FILE *fp, long msgstart, long msgend) {
1414         long hold_pos;
1415         long size;
1416         char *buffer;
1417
1418         hold_pos = ftell(fp);
1419         size = msgend - msgstart + 1;
1420         buffer = malloc(size);
1421         if (buffer != NULL) {
1422                 fseek(fp, msgstart, SEEK_SET);
1423                 fread(buffer, size, 1, fp);
1424                 network_process_buffer(buffer, size);
1425                 free(buffer);
1426         }
1427
1428         fseek(fp, hold_pos, SEEK_SET);
1429 }
1430
1431
1432 /*
1433  * Process a single file from the inbound queue 
1434  */
1435 void network_process_file(char *filename) {
1436         FILE *fp;
1437         long msgstart = (-1L);
1438         long msgend = (-1L);
1439         long msgcur = 0L;
1440         int ch;
1441
1442
1443         fp = fopen(filename, "rb");
1444         if (fp == NULL) {
1445                 lprintf(CTDL_CRIT, "Error opening %s: %s\n",
1446                         filename, strerror(errno));
1447                 return;
1448         }
1449
1450         lprintf(CTDL_INFO, "network: processing <%s>\n", filename);
1451
1452         /* Look for messages in the data stream and break them out */
1453         while (ch = getc(fp), ch >= 0) {
1454         
1455                 if (ch == 255) {
1456                         if (msgstart >= 0L) {
1457                                 msgend = msgcur - 1;
1458                                 network_process_message(fp, msgstart, msgend);
1459                         }
1460                         msgstart = msgcur;
1461                 }
1462
1463                 ++msgcur;
1464         }
1465
1466         msgend = msgcur - 1;
1467         if (msgstart >= 0L) {
1468                 network_process_message(fp, msgstart, msgend);
1469         }
1470
1471         fclose(fp);
1472         unlink(filename);
1473 }
1474
1475
1476 /*
1477  * Process anything in the inbound queue
1478  */
1479 void network_do_spoolin(void) {
1480         DIR *dp;
1481         struct dirent *d;
1482         char filename[SIZ];
1483
1484         dp = opendir("./network/spoolin");
1485         if (dp == NULL) return;
1486
1487         while (d = readdir(dp), d != NULL) {
1488                 snprintf(filename, sizeof filename,
1489                         "./network/spoolin/%s", d->d_name);
1490                 network_process_file(filename);
1491         }
1492
1493
1494         closedir(dp);
1495 }
1496
1497
1498 /*
1499  * Delete any files in the outbound queue that were intended
1500  * to be sent to nodes which no longer exist.
1501  */
1502 void network_purge_spoolout(void) {
1503         DIR *dp;
1504         struct dirent *d;
1505         char filename[SIZ];
1506         char nexthop[SIZ];
1507         int i;
1508
1509         dp = opendir("./network/spoolout");
1510         if (dp == NULL) return;
1511
1512         while (d = readdir(dp), d != NULL) {
1513                 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
1514                         continue;
1515                 snprintf(filename, sizeof filename,
1516                         "./network/spoolout/%s", d->d_name);
1517
1518                 strcpy(nexthop, "");
1519                 i = is_valid_node(nexthop, NULL, d->d_name);
1520         
1521                 if ( (i != 0) || (strlen(nexthop) > 0) ) {
1522                         unlink(filename);
1523                 }
1524         }
1525
1526
1527         closedir(dp);
1528 }
1529
1530
1531
1532 /*
1533  * receive network spool from the remote system
1534  */
1535 void receive_spool(int sock, char *remote_nodename) {
1536         long download_len;
1537         long bytes_received;
1538         char buf[SIZ];
1539         static char pbuf[IGNET_PACKET_SIZE];
1540         char tempfilename[PATH_MAX];
1541         long plen;
1542         FILE *fp;
1543
1544         strcpy(tempfilename, tmpnam(NULL));
1545         if (sock_puts(sock, "NDOP") < 0) return;
1546         if (sock_gets(sock, buf) < 0) return;
1547         lprintf(CTDL_DEBUG, "<%s\n", buf);
1548         if (buf[0] != '2') {
1549                 return;
1550         }
1551         download_len = extract_long(&buf[4], 0);
1552
1553         bytes_received = 0L;
1554         fp = fopen(tempfilename, "w");
1555         if (fp == NULL) {
1556                 lprintf(CTDL_CRIT, "cannot open download file locally: %s\n",
1557                         strerror(errno));
1558                 return;
1559         }
1560
1561         while (bytes_received < download_len) {
1562                 snprintf(buf, sizeof buf, "READ %ld|%ld",
1563                         bytes_received,
1564                      ((download_len - bytes_received > IGNET_PACKET_SIZE)
1565                  ? IGNET_PACKET_SIZE : (download_len - bytes_received)));
1566                 if (sock_puts(sock, buf) < 0) {
1567                         fclose(fp);
1568                         unlink(tempfilename);
1569                         return;
1570                 }
1571                 if (sock_gets(sock, buf) < 0) {
1572                         fclose(fp);
1573                         unlink(tempfilename);
1574                         return;
1575                 }
1576                 if (buf[0] == '6') {
1577                         plen = extract_long(&buf[4], 0);
1578                         if (sock_read(sock, pbuf, plen) < 0) {
1579                                 fclose(fp);
1580                                 unlink(tempfilename);
1581                                 return;
1582                         }
1583                         fwrite((char *) pbuf, plen, 1, fp);
1584                         bytes_received = bytes_received + plen;
1585                 }
1586         }
1587
1588         fclose(fp);
1589         if (sock_puts(sock, "CLOS") < 0) {
1590                 unlink(tempfilename);
1591                 return;
1592         }
1593         if (sock_gets(sock, buf) < 0) {
1594                 unlink(tempfilename);
1595                 return;
1596         }
1597         if (download_len > 0)
1598                 lprintf(CTDL_NOTICE, "Received %ld octets from <%s>",
1599                                 download_len, remote_nodename);
1600         lprintf(CTDL_DEBUG, "%s", buf);
1601         snprintf(buf, sizeof buf, "mv %s ./network/spoolin/%s.%ld",
1602                 tempfilename, remote_nodename, (long) getpid());
1603         system(buf);
1604 }
1605
1606
1607
1608 /*
1609  * transmit network spool to the remote system
1610  */
1611 void transmit_spool(int sock, char *remote_nodename)
1612 {
1613         char buf[SIZ];
1614         char pbuf[4096];
1615         long plen;
1616         long bytes_to_write, thisblock, bytes_written;
1617         int fd;
1618         char sfname[128];
1619
1620         if (sock_puts(sock, "NUOP") < 0) return;
1621         if (sock_gets(sock, buf) < 0) return;
1622         lprintf(CTDL_DEBUG, "<%s\n", buf);
1623         if (buf[0] != '2') {
1624                 return;
1625         }
1626
1627         snprintf(sfname, sizeof sfname, "./network/spoolout/%s", remote_nodename);
1628         fd = open(sfname, O_RDONLY);
1629         if (fd < 0) {
1630                 if (errno != ENOENT) {
1631                         lprintf(CTDL_CRIT, "cannot open upload file locally: %s\n",
1632                                 strerror(errno));
1633                 }
1634                 return;
1635         }
1636         bytes_written = 0;
1637         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
1638                 bytes_to_write = plen;
1639                 while (bytes_to_write > 0L) {
1640                         snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
1641                         if (sock_puts(sock, buf) < 0) {
1642                                 close(fd);
1643                                 return;
1644                         }
1645                         if (sock_gets(sock, buf) < 0) {
1646                                 close(fd);
1647                                 return;
1648                         }
1649                         thisblock = atol(&buf[4]);
1650                         if (buf[0] == '7') {
1651                                 if (sock_write(sock, pbuf,
1652                                    (int) thisblock) < 0) {
1653                                         close(fd);
1654                                         return;
1655                                 }
1656                                 bytes_to_write -= thisblock;
1657                                 bytes_written += thisblock;
1658                         } else {
1659                                 goto ABORTUPL;
1660                         }
1661                 }
1662         }
1663
1664 ABORTUPL:
1665         close(fd);
1666         if (sock_puts(sock, "UCLS 1") < 0) return;
1667         if (sock_gets(sock, buf) < 0) return;
1668         lprintf(CTDL_NOTICE, "Sent %ld octets to <%s>",
1669                         bytes_written, remote_nodename);
1670         lprintf(CTDL_DEBUG, "<%s\n", buf);
1671         if (buf[0] == '2') {
1672                 unlink(sfname);
1673         }
1674 }
1675
1676
1677
1678 /*
1679  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
1680  */
1681 void network_poll_node(char *node, char *secret, char *host, char *port) {
1682         int sock;
1683         char buf[SIZ];
1684
1685         if (network_talking_to(node, NTT_CHECK)) return;
1686         network_talking_to(node, NTT_ADD);
1687         lprintf(CTDL_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port);
1688
1689         sock = sock_connect(host, port, "tcp");
1690         if (sock < 0) {
1691                 lprintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno));
1692                 network_talking_to(node, NTT_REMOVE);
1693                 return;
1694         }
1695         
1696         lprintf(CTDL_DEBUG, "Connected!\n");
1697
1698         /* Read the server greeting */
1699         if (sock_gets(sock, buf) < 0) goto bail;
1700         lprintf(CTDL_DEBUG, ">%s\n", buf);
1701
1702         /* Identify ourselves */
1703         snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
1704         lprintf(CTDL_DEBUG, "<%s\n", buf);
1705         if (sock_puts(sock, buf) <0) goto bail;
1706         if (sock_gets(sock, buf) < 0) goto bail;
1707         lprintf(CTDL_DEBUG, ">%s\n", buf);
1708         if (buf[0] != '2') goto bail;
1709
1710         /* At this point we are authenticated. */
1711         receive_spool(sock, node);
1712         transmit_spool(sock, node);
1713
1714         sock_puts(sock, "QUIT");
1715 bail:   sock_close(sock);
1716         network_talking_to(node, NTT_REMOVE);
1717 }
1718
1719
1720
1721 /*
1722  * Poll other Citadel nodes and transfer inbound/outbound network data.
1723  * Set "full" to nonzero to force a poll of every node, or to zero to poll
1724  * only nodes to which we have data to send.
1725  */
1726 void network_poll_other_citadel_nodes(int full_poll) {
1727         int i;
1728         char linebuf[SIZ];
1729         char node[SIZ];
1730         char host[SIZ];
1731         char port[SIZ];
1732         char secret[SIZ];
1733         int poll = 0;
1734         char spoolfile[SIZ];
1735
1736         if (working_ignetcfg == NULL) {
1737                 lprintf(CTDL_DEBUG, "No nodes defined - not polling\n");
1738                 return;
1739         }
1740
1741         /* Use the string tokenizer to grab one line at a time */
1742         for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
1743                 extract_token(linebuf, working_ignetcfg, i, '\n');
1744                 extract(node, linebuf, 0);
1745                 extract(secret, linebuf, 1);
1746                 extract(host, linebuf, 2);
1747                 extract(port, linebuf, 3);
1748                 if ( (strlen(node) > 0) && (strlen(secret) > 0) 
1749                    && (strlen(host) > 0) && strlen(port) > 0) {
1750                         poll = full_poll;
1751                         if (poll == 0) {
1752                                 sprintf(spoolfile, "./network/spoolout/%s",
1753                                         node);
1754                                 if (access(spoolfile, R_OK) == 0) {
1755                                         poll = 1;
1756                                 }
1757                         }
1758                         if (poll) {
1759                                 network_poll_node(node, secret, host, port);
1760                         }
1761                 }
1762         }
1763
1764 }
1765
1766
1767
1768
1769
1770
1771
1772 /*
1773  * network_do_queue()
1774  * 
1775  * Run through the rooms doing various types of network stuff.
1776  */
1777 void network_do_queue(void) {
1778         static time_t last_run = 0L;
1779         struct RoomProcList *ptr;
1780         int full_processing = 1;
1781
1782         /*
1783          * Run the full set of processing tasks no more frequently
1784          * than once every n seconds
1785          */
1786         if ( (time(NULL) - last_run) < config.c_net_freq ) {
1787                 full_processing = 0;
1788         }
1789
1790         /*
1791          * This is a simple concurrency check to make sure only one queue run
1792          * is done at a time.  We could do this with a mutex, but since we
1793          * don't really require extremely fine granularity here, we'll do it
1794          * with a static variable instead.
1795          */
1796         if (doing_queue) return;
1797         doing_queue = 1;
1798
1799         /* Load the IGnet Configuration into memory */
1800         load_working_ignetcfg();
1801
1802         /*
1803          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
1804          * then we poll everyone.  Otherwise we only poll nodes we have stuff
1805          * to send to.
1806          */
1807         network_poll_other_citadel_nodes(full_processing);
1808
1809         /*
1810          * Load the network map and filter list into memory.
1811          */
1812         read_network_map();
1813         filterlist = load_filter_list();
1814
1815         /* 
1816          * Go ahead and run the queue
1817          */
1818         if (full_processing) {
1819                 lprintf(CTDL_DEBUG, "network: loading outbound queue\n");
1820                 ForEachRoom(network_queue_room, NULL);
1821
1822                 lprintf(CTDL_DEBUG, "network: running outbound queue\n");
1823                 while (rplist != NULL) {
1824                         network_spoolout_room(rplist->name);
1825                         ptr = rplist;
1826                         rplist = rplist->next;
1827                         free(ptr);
1828                 }
1829         }
1830
1831         lprintf(CTDL_DEBUG, "network: processing inbound queue\n");
1832         network_do_spoolin();
1833
1834         /* Save the network map back to disk */
1835         write_network_map();
1836
1837         /* Free the filter list in memory */
1838         free_filter_list(filterlist);
1839         filterlist = NULL;
1840
1841         network_purge_spoolout();
1842
1843         lprintf(CTDL_DEBUG, "network: queue run completed\n");
1844
1845         if (full_processing) {
1846                 last_run = time(NULL);
1847         }
1848
1849         doing_queue = 0;
1850 }
1851
1852
1853 /*
1854  * cmd_netp() - authenticate to the server as another Citadel node polling
1855  *              for network traffic
1856  */
1857 void cmd_netp(char *cmdbuf)
1858 {
1859         char node[SIZ];
1860         char pass[SIZ];
1861         int v;
1862
1863         char secret[SIZ];
1864         char nexthop[SIZ];
1865
1866         /* Authenticate */
1867         extract(node, cmdbuf, 0);
1868         extract(pass, cmdbuf, 1);
1869
1870         if (doing_queue) {
1871                 lprintf(CTDL_WARNING, "Network node <%s> refused - spooling", node);
1872                 cprintf("%d spooling - try again in a few minutes\n",
1873                         ERROR + RESOURCE_BUSY);
1874                 return;
1875         }
1876
1877         /* load the IGnet Configuration to check node validity */
1878         load_working_ignetcfg();
1879         v = is_valid_node(nexthop, secret, node);
1880
1881         if (v != 0) {
1882                 lprintf(CTDL_WARNING, "Unknown node <%s>", node);
1883                 cprintf("%d authentication failed\n",
1884                         ERROR + PASSWORD_REQUIRED);
1885                 return;
1886         }
1887
1888         if (strcasecmp(pass, secret)) {
1889                 lprintf(CTDL_WARNING, "Bad password for network node <%s>", node);
1890                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
1891                 return;
1892         }
1893
1894         if (network_talking_to(node, NTT_CHECK)) {
1895                 lprintf(CTDL_WARNING, "Duplicate session for network node <%s>", node);
1896                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
1897                 return;
1898         }
1899
1900         safestrncpy(CC->net_node, node, sizeof CC->net_node);
1901         network_talking_to(node, NTT_ADD);
1902         lprintf(CTDL_NOTICE, "Network node <%s> logged in\n", CC->net_node);
1903         cprintf("%d authenticated as network node '%s'\n", CIT_OK,
1904                 CC->net_node);
1905 }
1906
1907
1908
1909 /*
1910  * Module entry point
1911  */
1912 char *serv_network_init(void)
1913 {
1914         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1915         CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1916         CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1917         CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node");
1918         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
1919         return "$Id$";
1920 }