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