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