7f0af1a8ecb0b98361c6413fa0a283aba519f85d
[citadel.git] / citadel / serv_network.c
1 /*
2  * $Id$ 
3  *
4  * This module will eventually replace netproc and some of its utilities.  In
5  * the meantime, it serves as a mailing list manager.
6  *
7  * Copyright (C) 2000-2001 by Art Cancro and others.
8  * This code is released under the terms of the GNU General Public License.
9  *
10  */
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <sys/wait.h>
34 #include <string.h>
35 #include <limits.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
40 #include "support.h"
41 #include "config.h"
42 #include "dynloader.h"
43 #include "room_ops.h"
44 #include "user_ops.h"
45 #include "policy.h"
46 #include "database.h"
47 #include "msgbase.h"
48 #include "tools.h"
49 #include "internet_addressing.h"
50 #include "serv_network.h"
51
52
53 /*
54  * When we do network processing, it's accomplished in two passes; one to
55  * gather a list of rooms and one to actually do them.  It's ok that rplist
56  * is global; this process *only* runs as part of the housekeeping loop and
57  * therefore only one will run at a time.
58  */
59 struct RoomProcList {
60         struct RoomProcList *next;
61         char name[ROOMNAMELEN];
62 };
63
64 struct RoomProcList *rplist = NULL;
65
66
67 /*
68  * We build a map of the Citadel network during network runs.
69  */
70 struct NetMap {
71         struct NetMap *next;
72         char nodename[SIZ];
73         time_t lastcontact;
74         char nexthop[SIZ];
75 };
76
77 struct NetMap *the_netmap = NULL;
78
79
80
81 /* 
82  * Read the network map from its configuration file into memory.
83  */
84 void read_network_map(void) {
85         char *serialized_map = NULL;
86         int i;
87         char buf[SIZ];
88         struct NetMap *nmptr;
89
90         serialized_map = CtdlGetSysConfig(IGNETMAP);
91         if (serialized_map == NULL) return;     /* if null, no entries */
92
93         /* Use the string tokenizer to grab one line at a time */
94         for (i=0; i<num_tokens(serialized_map, '\n'); ++i) {
95                 extract_token(buf, serialized_map, i, '\n');
96                 nmptr = (struct NetMap *) mallok(sizeof(struct NetMap));
97                 extract(nmptr->nodename, buf, 0);
98                 nmptr->lastcontact = extract_long(buf, 1);
99                 extract(nmptr->nexthop, buf, 2);
100                 nmptr->next = the_netmap;
101                 the_netmap = nmptr;
102         }
103
104         phree(serialized_map);
105 }
106
107
108 /*
109  * Write the network map from memory back to the configuration file.
110  */
111 void write_network_map(void) {
112         char *serialized_map = NULL;
113         struct NetMap *nmptr;
114
115         serialized_map = strdoop("");
116
117         if (the_netmap != NULL) {
118                 for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
119                         serialized_map = reallok(serialized_map,
120                                                 (strlen(serialized_map)+SIZ) );
121                         if (strlen(nmptr->nodename) > 0) {
122                                 sprintf(&serialized_map[strlen(serialized_map)],
123                                         "%s|%ld|%s\n",
124                                         nmptr->nodename,
125                                         nmptr->lastcontact,
126                                         nmptr->nexthop);
127                         }
128                 }
129         }
130
131         CtdlPutSysConfig(IGNETMAP, serialized_map);
132         phree(serialized_map);
133
134         /* Now free the list */
135         while (the_netmap != NULL) {
136                 nmptr = the_netmap->next;
137                 phree(the_netmap);
138                 the_netmap = nmptr;
139         }
140 }
141
142
143
144
145
146 void cmd_gnet(char *argbuf) {
147         char filename[SIZ];
148         char buf[SIZ];
149         FILE *fp;
150
151         if (CtdlAccessCheck(ac_room_aide)) return;
152         assoc_file_name(filename, &CC->quickroom, "netconfigs");
153         cprintf("%d Network settings for room #%ld <%s>\n",
154                 LISTING_FOLLOWS,
155                 CC->quickroom.QRnumber, CC->quickroom.QRname);
156
157         fp = fopen(filename, "r");
158         if (fp != NULL) {
159                 while (fgets(buf, sizeof buf, fp) != NULL) {
160                         buf[strlen(buf)-1] = 0;
161                         cprintf("%s\n", buf);
162                 }
163                 fclose(fp);
164         }
165
166         cprintf("000\n");
167 }
168
169
170 void cmd_snet(char *argbuf) {
171         char tempfilename[SIZ];
172         char filename[SIZ];
173         char buf[SIZ];
174         FILE *fp;
175
176         if (CtdlAccessCheck(ac_room_aide)) return;
177         safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
178         assoc_file_name(filename, &CC->quickroom, "netconfigs");
179
180         fp = fopen(tempfilename, "w");
181         if (fp == NULL) {
182                 cprintf("%d Cannot open %s: %s\n",
183                         ERROR+INTERNAL_ERROR,
184                         tempfilename,
185                         strerror(errno));
186         }
187
188         cprintf("%d %s\n", SEND_LISTING, tempfilename);
189         while (client_gets(buf), strcmp(buf, "000")) {
190                 fprintf(fp, "%s\n", buf);
191         }
192         fclose(fp);
193
194         /* Now copy the temp file to its permanent location
195          * (We use /bin/mv instead of link() because they may be on
196          * different filesystems)
197          */
198         unlink(filename);
199         snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
200         system(buf);
201 }
202
203
204
205 /*
206  * Spools out one message from the list.
207  */
208 void network_spool_msg(long msgnum, void *userdata) {
209         struct SpoolControl *sc;
210         struct namelist *nptr;
211         int err;
212         int i;
213         char *instr = NULL;
214         char *newpath = NULL;
215         size_t instr_len = SIZ;
216         struct CtdlMessage *msg;
217         struct CtdlMessage *imsg;
218         struct ser_ret sermsg;
219         FILE *fp;
220         char filename[SIZ];
221         char buf[SIZ];
222         int bang = 0;
223         int send = 1;
224         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
225
226         sc = (struct SpoolControl *)userdata;
227
228         /*
229          * Process mailing list recipients
230          */
231         if (sc->listrecps != NULL) {
232         
233                 /* First, copy it to the spoolout room */
234                 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
235                 if (err != 0) return;
236
237                 /* 
238                  * Figure out how big a buffer we need to allocate
239                  */
240                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
241                         instr_len = instr_len + strlen(nptr->name);
242                 }
243         
244                 /*
245                  * allocate...
246                  */
247                 lprintf(9, "Generating delivery instructions\n");
248                 instr = mallok(instr_len);
249                 if (instr == NULL) {
250                         lprintf(1, "Cannot allocate %d bytes for instr...\n",
251                                 instr_len);
252                         abort();
253                 }
254                 sprintf(instr,
255                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
256                         "bounceto|postmaster@%s\n" ,
257                         SPOOLMIME, msgnum, time(NULL), config.c_fqdn );
258         
259                 /* Generate delivery instructions for each recipient */
260                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
261                         sprintf(&instr[strlen(instr)], "remote|%s|0||\n",
262                                 nptr->name);
263                 }
264         
265                 /*
266                  * Generate a message from the instructions
267                  */
268                 imsg = mallok(sizeof(struct CtdlMessage));
269                 memset(imsg, 0, sizeof(struct CtdlMessage));
270                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
271                 imsg->cm_anon_type = MES_NORMAL;
272                 imsg->cm_format_type = FMT_RFC822;
273                 imsg->cm_fields['A'] = strdoop("Citadel");
274                 imsg->cm_fields['M'] = instr;
275         
276                 /* Save delivery instructions in spoolout room */
277                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
278                 CtdlFreeMessage(imsg);
279         }
280         
281         /*
282          * Process IGnet push shares
283          */
284         if (sc->ignet_push_shares != NULL) {
285         
286                 msg = CtdlFetchMessage(msgnum);
287                 if (msg != NULL) {
288
289                         /* Prepend our node name to the Path field whenever
290                          * sending a message to another IGnet node
291                          */
292                         if (msg->cm_fields['P'] == NULL) {
293                                 msg->cm_fields['P'] = strdoop("username");
294                         }
295                         newpath = mallok(strlen(msg->cm_fields['P']) + 
296                                         strlen(config.c_nodename) + 2);
297                         sprintf(newpath, "%s!%s", config.c_nodename,
298                                         msg->cm_fields['P']);
299                         phree(msg->cm_fields['P']);
300                         msg->cm_fields['P'] = newpath;
301
302                         /*
303                          * Force the message to appear in the correct room
304                          * on the far end by setting the C field correctly
305                          */
306                         if (msg->cm_fields['C'] != NULL) {
307                                 phree(msg->cm_fields['C']);
308                         }
309                         msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
310
311                         /*
312                          * Determine if this message is set to be deleted
313                          * after sending out on the network
314                          */
315                         if (msg->cm_fields['S'] != NULL) {
316                                 if (!strcasecmp(msg->cm_fields['S'],
317                                    "CANCEL")) {
318                                         delete_after_send = 1;
319                                 }
320                         }
321
322                         /* 
323                          * Now serialize it for transmission
324                          */
325                         serialize_message(&sermsg, msg);
326                         CtdlFreeMessage(msg);
327
328                         /* Now send it to every node */
329                         for (nptr = sc->ignet_push_shares; nptr != NULL;
330                             nptr = nptr->next) {
331
332                                 send = 1;
333
334                                 /* FIXME check for valid node name */
335
336                                 /* Check for split horizon */
337                                 bang = num_tokens(msg->cm_fields['P'], '!');
338                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
339                                         extract_token(buf, msg->cm_fields['P'],
340                                                 i, '!');
341                                         if (!strcasecmp(buf, nptr->name)) {
342                                                 send = 0;
343                                         }
344                                 }
345
346                                 /* Send the message */
347                                 if (send == 1) {
348                                         sprintf(filename,
349                                                 "./network/spoolout/%s",
350                                                 nptr->name);
351                                         fp = fopen(filename, "ab");
352                                         if (fp != NULL) {
353                                                 fwrite(sermsg.ser,
354                                                         sermsg.len, 1, fp);
355                                                 fclose(fp);
356                                         }
357                                 }
358                         }
359                 }
360         }
361
362         /* update lastsent */
363         sc->lastsent = msgnum;
364
365         /* Delete this message if delete-after-send is set */
366         if (delete_after_send) {
367                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
368         }
369
370 }
371         
372
373
374
375 /*
376  * Batch up and send all outbound traffic from the current room
377  */
378 void network_spoolout_room(char *room_to_spool) {
379         char filename[SIZ];
380         char buf[SIZ];
381         char instr[SIZ];
382         FILE *fp;
383         struct SpoolControl sc;
384         /* struct namelist *digestrecps = NULL; */
385         struct namelist *nptr;
386
387         lprintf(7, "Spooling <%s>\n", room_to_spool);
388         if (getroom(&CC->quickroom, room_to_spool) != 0) {
389                 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
390                 return;
391         }
392
393         memset(&sc, 0, sizeof(struct SpoolControl));
394         assoc_file_name(filename, &CC->quickroom, "netconfigs");
395
396         fp = fopen(filename, "r");
397         if (fp == NULL) {
398                 lprintf(7, "Outbound batch processing skipped for <%s>\n",
399                         CC->quickroom.QRname);
400                 return;
401         }
402
403         lprintf(5, "Outbound batch processing started for <%s>\n",
404                 CC->quickroom.QRname);
405
406         while (fgets(buf, sizeof buf, fp) != NULL) {
407                 buf[strlen(buf)-1] = 0;
408
409                 extract(instr, buf, 0);
410                 if (!strcasecmp(instr, "lastsent")) {
411                         sc.lastsent = extract_long(buf, 1);
412                 }
413                 else if (!strcasecmp(instr, "listrecp")) {
414                         nptr = (struct namelist *)
415                                 mallok(sizeof(struct namelist));
416                         nptr->next = sc.listrecps;
417                         extract(nptr->name, buf, 1);
418                         sc.listrecps = nptr;
419                 }
420                 else if (!strcasecmp(instr, "ignet_push_share")) {
421                         nptr = (struct namelist *)
422                                 mallok(sizeof(struct namelist));
423                         nptr->next = sc.ignet_push_shares;
424                         extract(nptr->name, buf, 1);
425                         sc.ignet_push_shares = nptr;
426                 }
427
428
429         }
430         fclose(fp);
431
432
433         /* Do something useful */
434         CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
435                 network_spool_msg, &sc);
436
437
438         /* Now rewrite the config file */
439         fp = fopen(filename, "w");
440         if (fp == NULL) {
441                 lprintf(1, "ERROR: cannot open %s: %s\n",
442                         filename, strerror(errno));
443         }
444         else {
445                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
446
447                 /* Write out the listrecps while freeing from memory at the
448                  * same time.  Am I clever or what?  :)
449                  */
450                 while (sc.listrecps != NULL) {
451                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
452                         nptr = sc.listrecps->next;
453                         phree(sc.listrecps);
454                         sc.listrecps = nptr;
455                 }
456                 while (sc.ignet_push_shares != NULL) {
457                         fprintf(fp, "ignet_push_share|%s\n",
458                                 sc.ignet_push_shares->name);
459                         nptr = sc.ignet_push_shares->next;
460                         phree(sc.ignet_push_shares);
461                         sc.ignet_push_shares = nptr;
462                 }
463
464                 fclose(fp);
465         }
466
467         lprintf(5, "Outbound batch processing finished for <%s>\n",
468                 CC->quickroom.QRname);
469 }
470
471
472 /*
473  * Batch up and send all outbound traffic from the current room
474  */
475 void network_queue_room(struct quickroom *qrbuf, void *data) {
476         struct RoomProcList *ptr;
477
478         ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
479         if (ptr == NULL) return;
480
481         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
482         ptr->next = rplist;
483         rplist = ptr;
484 }
485
486
487 /*
488  * Learn topology from path fields
489  */
490 void network_learn_topology(char *node, char *path) {
491         char nexthop[SIZ];
492         struct NetMap *nmptr;
493
494         strcpy(nexthop, "");
495
496         if (num_tokens(path, '!') < 3) return;
497         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
498                 if (!strcasecmp(nmptr->nodename, node)) {
499                         extract_token(nmptr->nexthop, path, 0, '!');
500                         nmptr->lastcontact = time(NULL);
501                         return;
502                 }
503         }
504
505         /* If we got here then it's not in the map, so add it. */
506         nmptr = (struct NetMap *) mallok(sizeof (struct NetMap));
507         strcpy(nmptr->nodename, node);
508         nmptr->lastcontact = time(NULL);
509         extract_token(nmptr->nexthop, path, 0, '!');
510         nmptr->next = the_netmap;
511         the_netmap = nmptr;
512 }
513
514
515
516 /*
517  * Process a buffer containing a single message from a single file
518  * from the inbound queue 
519  */
520 void network_process_buffer(char *buffer, long size) {
521         struct CtdlMessage *msg;
522         long pos;
523         int field;
524         int a, e;
525         struct usersupp tempUS;
526         char recp[SIZ];
527
528         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
529         memset(msg, 0, sizeof(struct CtdlMessage));
530         msg->cm_magic = CTDLMESSAGE_MAGIC;
531         msg->cm_anon_type = buffer[1];
532         msg->cm_format_type = buffer[2];
533
534         for (pos = 3; pos < size; ++pos) {
535                 field = buffer[pos];
536                 msg->cm_fields[field] = strdoop(&buffer[pos+1]);
537                 pos = pos + strlen(&buffer[(int)pos]);
538         }
539
540         /* Check for message routing */
541         if (msg->cm_fields['D'] != NULL) {
542                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
543
544                         /* FIXME route the message, stupid */
545
546                 }
547         }
548
549         /* Learn network topology from the path */
550         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
551                 network_learn_topology(msg->cm_fields['N'], 
552                                         msg->cm_fields['P']);
553         }
554
555         /* Does it have a recipient?  If so, validate it... */
556         if (msg->cm_fields['D'] != NULL) {
557
558                 safestrncpy(recp, msg->cm_fields['D'], sizeof(recp));
559
560                 e = alias(recp);        /* alias and mail type */
561                 if ((recp[0] == 0) || (e == MES_ERROR)) {
562
563                         /* FIXME bounce the msg */
564
565                 }
566                 else if (e == MES_LOCAL) {
567                         a = getuser(&tempUS, recp);
568                         if (a != 0) {
569
570                                 /* FIXME bounce the msg */
571
572                         }
573                 }
574         }
575
576         /* FIXME ... do something with it! */
577
578         CtdlFreeMessage(msg);
579 }
580
581
582 /*
583  * Process a single message from a single file from the inbound queue 
584  */
585 void network_process_message(FILE *fp, long msgstart, long msgend) {
586         long hold_pos;
587         long size;
588         char *buffer;
589
590         hold_pos = ftell(fp);
591         size = msgend - msgstart + 1;
592         buffer = mallok(size);
593         if (buffer != NULL) {
594                 fseek(fp, msgstart, SEEK_SET);
595                 fread(buffer, size, 1, fp);
596                 network_process_buffer(buffer, size);
597                 phree(buffer);
598         }
599
600         fseek(fp, hold_pos, SEEK_SET);
601 }
602
603
604 /*
605  * Process a single file from the inbound queue 
606  */
607 void network_process_file(char *filename) {
608         FILE *fp;
609         long msgstart = (-1L);
610         long msgend = (-1L);
611         long msgcur = 0L;
612         int ch;
613
614         lprintf(7, "network: processing <%s>\n", filename);
615
616         fp = fopen(filename, "rb");
617         if (fp == NULL) {
618                 lprintf(5, "Error opening %s: %s\n",
619                         filename, strerror(errno));
620                 return;
621         }
622
623         /* Look for messages in the data stream and break them out */
624         while (ch = getc(fp), ch >= 0) {
625         
626                 if (ch == 255) {
627                         if (msgstart >= 0L) {
628                                 msgend = msgcur - 1;
629                                 network_process_message(fp, msgstart, msgend);
630                         }
631                         msgstart = msgcur;
632                 }
633
634                 ++msgcur;
635         }
636
637         msgend = msgcur - 1;
638         if (msgstart >= 0L) {
639                 network_process_message(fp, msgstart, msgend);
640         }
641
642         fclose(fp);
643         /* unlink(filename); FIXME put back in */
644 }
645
646
647 /*
648  * Process anything in the inbound queue
649  */
650 void network_do_spoolin(void) {
651         DIR *dp;
652         struct dirent *d;
653         char filename[SIZ];
654
655         dp = opendir("./network/spoolin");
656         if (dp == NULL) return;
657
658         while (d = readdir(dp), d != NULL) {
659                 sprintf(filename, "./network/spoolin/%s", d->d_name);
660                 network_process_file(filename);
661         }
662
663
664         closedir(dp);
665 }
666
667
668 /*
669  * network_do_queue()
670  * 
671  * Run through the rooms doing various types of network stuff.
672  */
673 void network_do_queue(void) {
674         static int doing_queue = 0;
675         static time_t last_run = 0L;
676         struct RoomProcList *ptr;
677
678 #define NETWORK_QUEUE_FREQUENCY 3600    /* one hour ... FIXME put in config */
679         /*
680          * Run no more frequently than once every n seconds
681          */
682         if ( (time(NULL) - last_run) < NETWORK_QUEUE_FREQUENCY ) return;
683
684         /*
685          * This is a simple concurrency check to make sure only one queue run
686          * is done at a time.  We could do this with a mutex, but since we
687          * don't really require extremely fine granularity here, we'll do it
688          * with a static variable instead.
689          */
690         if (doing_queue) return;
691         doing_queue = 1;
692         last_run = time(NULL);
693
694         read_network_map();
695
696         /* 
697          * Go ahead and run the queue
698          */
699         lprintf(7, "network: loading outbound queue\n");
700         ForEachRoom(network_queue_room, NULL);
701
702         lprintf(7, "network: running outbound queue\n");
703         while (rplist != NULL) {
704                 network_spoolout_room(rplist->name);
705                 ptr = rplist;
706                 rplist = rplist->next;
707                 phree(ptr);
708         }
709
710         lprintf(7, "network: processing inbound queue\n");
711         network_do_spoolin();
712
713         write_network_map();
714
715         lprintf(7, "network: queue run completed\n");
716         doing_queue = 0;
717 }
718
719
720 /*
721  * Module entry point
722  */
723 char *Dynamic_Module_Init(void)
724 {
725         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
726         CtdlRegisterProtoHook(cmd_snet, "SNET", "Get network config");
727         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
728         return "$Id$";
729 }