]> code.citadel.org Git - citadel.git/blob - citadel/serv_network.c
* Added split horizon and delete-after-spool to the new networker
[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
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
69 void cmd_gnet(char *argbuf) {
70         char filename[SIZ];
71         char buf[SIZ];
72         FILE *fp;
73
74         if (CtdlAccessCheck(ac_room_aide)) return;
75         assoc_file_name(filename, &CC->quickroom, "netconfigs");
76         cprintf("%d Network settings for room #%ld <%s>\n",
77                 LISTING_FOLLOWS,
78                 CC->quickroom.QRnumber, CC->quickroom.QRname);
79
80         fp = fopen(filename, "r");
81         if (fp != NULL) {
82                 while (fgets(buf, sizeof buf, fp) != NULL) {
83                         buf[strlen(buf)-1] = 0;
84                         cprintf("%s\n", buf);
85                 }
86                 fclose(fp);
87         }
88
89         cprintf("000\n");
90 }
91
92
93 void cmd_snet(char *argbuf) {
94         char tempfilename[SIZ];
95         char filename[SIZ];
96         char buf[SIZ];
97         FILE *fp;
98
99         if (CtdlAccessCheck(ac_room_aide)) return;
100         safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
101         assoc_file_name(filename, &CC->quickroom, "netconfigs");
102
103         fp = fopen(tempfilename, "w");
104         if (fp == NULL) {
105                 cprintf("%d Cannot open %s: %s\n",
106                         ERROR+INTERNAL_ERROR,
107                         tempfilename,
108                         strerror(errno));
109         }
110
111         cprintf("%d %s\n", SEND_LISTING, tempfilename);
112         while (client_gets(buf), strcmp(buf, "000")) {
113                 fprintf(fp, "%s\n", buf);
114         }
115         fclose(fp);
116
117         /* Now copy the temp file to its permanent location
118          * (We use /bin/mv instead of link() because they may be on
119          * different filesystems)
120          */
121         unlink(filename);
122         snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
123         system(buf);
124 }
125
126
127
128 /*
129  * Spools out one message from the list.
130  */
131 void network_spool_msg(long msgnum, void *userdata) {
132         struct SpoolControl *sc;
133         struct namelist *nptr;
134         int err;
135         int i;
136         char *instr = NULL;
137         char *newpath = NULL;
138         size_t instr_len = SIZ;
139         struct CtdlMessage *msg;
140         struct CtdlMessage *imsg;
141         struct ser_ret sermsg;
142         FILE *fp;
143         char filename[SIZ];
144         char buf[SIZ];
145         int bang = 0;
146         int send = 1;
147         int delete_after_send = 0;      /* Set to 1 to delete after spooling */
148
149         sc = (struct SpoolControl *)userdata;
150
151         /*
152          * Process mailing list recipients
153          */
154         if (sc->listrecps != NULL) {
155         
156                 /* First, copy it to the spoolout room */
157                 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
158                 if (err != 0) return;
159
160                 /* 
161                  * Figure out how big a buffer we need to allocate
162                  */
163                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
164                         instr_len = instr_len + strlen(nptr->name);
165                 }
166         
167                 /*
168                  * allocate...
169                  */
170                 lprintf(9, "Generating delivery instructions\n");
171                 instr = mallok(instr_len);
172                 if (instr == NULL) {
173                         lprintf(1, "Cannot allocate %d bytes for instr...\n",
174                                 instr_len);
175                         abort();
176                 }
177                 sprintf(instr,
178                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
179                         "bounceto|postmaster@%s\n" ,
180                         SPOOLMIME, msgnum, time(NULL), config.c_fqdn );
181         
182                 /* Generate delivery instructions for each recipient */
183                 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
184                         sprintf(&instr[strlen(instr)], "remote|%s|0||\n",
185                                 nptr->name);
186                 }
187         
188                 /*
189                  * Generate a message from the instructions
190                  */
191                 imsg = mallok(sizeof(struct CtdlMessage));
192                 memset(imsg, 0, sizeof(struct CtdlMessage));
193                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
194                 imsg->cm_anon_type = MES_NORMAL;
195                 imsg->cm_format_type = FMT_RFC822;
196                 imsg->cm_fields['A'] = strdoop("Citadel");
197                 imsg->cm_fields['M'] = instr;
198         
199                 /* Save delivery instructions in spoolout room */
200                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
201                 CtdlFreeMessage(imsg);
202         }
203         
204         /*
205          * Process IGnet push shares
206          */
207         if (sc->ignet_push_shares != NULL) {
208         
209                 msg = CtdlFetchMessage(msgnum);
210                 if (msg != NULL) {
211
212                         /* Prepend our node name to the Path field whenever
213                          * sending a message to another IGnet node
214                          */
215                         if (msg->cm_fields['P'] == NULL) {
216                                 msg->cm_fields['P'] = strdoop("username");
217                         }
218                         newpath = mallok(strlen(msg->cm_fields['P']) + 
219                                         strlen(config.c_nodename) + 2);
220                         sprintf(newpath, "%s!%s", config.c_nodename,
221                                         msg->cm_fields['P']);
222                         phree(msg->cm_fields['P']);
223                         msg->cm_fields['P'] = newpath;
224
225                         /*
226                          * Force the message to appear in the correct room
227                          * on the far end by setting the C field correctly
228                          */
229                         if (msg->cm_fields['C'] != NULL) {
230                                 phree(msg->cm_fields['C']);
231                         }
232                         msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
233
234                         /*
235                          * Determine if this message is set to be deleted
236                          * after sending out on the network
237                          */
238                         if (msg->cm_fields['S'] != NULL) {
239                                 if (!strcasecmp(msg->cm_fields['S'],
240                                    "CANCEL")) {
241                                         delete_after_send = 1;
242                                 }
243                         }
244
245                         /* 
246                          * Now serialize it for transmission
247                          */
248                         serialize_message(&sermsg, msg);
249                         CtdlFreeMessage(msg);
250
251                         /* Now send it to every node */
252                         for (nptr = sc->ignet_push_shares; nptr != NULL;
253                             nptr = nptr->next) {
254
255                                 send = 1;
256
257                                 /* FIXME check for valid node name */
258
259                                 /* Check for split horizon */
260                                 bang = num_tokens(msg->cm_fields['P'], '!');
261                                 if (bang > 1) for (i=0; i<(bang-1); ++i) {
262                                         extract_token(buf, msg->cm_fields['P'],
263                                                 i, '!');
264                                         if (!strcasecmp(buf, nptr->name)) {
265                                                 send = 0;
266                                         }
267                                 }
268
269                                 /* Send the message */
270                                 if (send == 1) {
271                                         sprintf(filename,
272                                                 "./network/spoolout/%s",
273                                                 nptr->name);
274                                         fp = fopen(filename, "ab");
275                                         if (fp != NULL) {
276                                                 fwrite(sermsg.ser,
277                                                         sermsg.len, 1, fp);
278                                                 fclose(fp);
279                                         }
280                                 }
281                         }
282                 }
283         }
284
285         /* update lastsent */
286         sc->lastsent = msgnum;
287
288         /* Delete this message if delete-after-send is set */
289         if (delete_after_send) {
290                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
291         }
292
293 }
294         
295
296
297
298 /*
299  * Batch up and send all outbound traffic from the current room
300  */
301 void network_spoolout_room(char *room_to_spool) {
302         char filename[SIZ];
303         char buf[SIZ];
304         char instr[SIZ];
305         FILE *fp;
306         struct SpoolControl sc;
307         /* struct namelist *digestrecps = NULL; */
308         struct namelist *nptr;
309
310         lprintf(7, "Spooling <%s>\n", room_to_spool);
311         if (getroom(&CC->quickroom, room_to_spool) != 0) {
312                 lprintf(1, "ERROR: cannot load <%s>\n", room_to_spool);
313                 return;
314         }
315
316         memset(&sc, 0, sizeof(struct SpoolControl));
317         assoc_file_name(filename, &CC->quickroom, "netconfigs");
318
319         fp = fopen(filename, "r");
320         if (fp == NULL) {
321                 lprintf(7, "Outbound batch processing skipped for <%s>\n",
322                         CC->quickroom.QRname);
323                 return;
324         }
325
326         lprintf(5, "Outbound batch processing started for <%s>\n",
327                 CC->quickroom.QRname);
328
329         while (fgets(buf, sizeof buf, fp) != NULL) {
330                 buf[strlen(buf)-1] = 0;
331
332                 extract(instr, buf, 0);
333                 if (!strcasecmp(instr, "lastsent")) {
334                         sc.lastsent = extract_long(buf, 1);
335                 }
336                 else if (!strcasecmp(instr, "listrecp")) {
337                         nptr = (struct namelist *)
338                                 mallok(sizeof(struct namelist));
339                         nptr->next = sc.listrecps;
340                         extract(nptr->name, buf, 1);
341                         sc.listrecps = nptr;
342                 }
343                 else if (!strcasecmp(instr, "ignet_push_share")) {
344                         nptr = (struct namelist *)
345                                 mallok(sizeof(struct namelist));
346                         nptr->next = sc.ignet_push_shares;
347                         extract(nptr->name, buf, 1);
348                         sc.ignet_push_shares = nptr;
349                 }
350
351
352         }
353         fclose(fp);
354
355
356         /* Do something useful */
357         CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
358                 network_spool_msg, &sc);
359
360
361         /* Now rewrite the config file */
362         fp = fopen(filename, "w");
363         if (fp == NULL) {
364                 lprintf(1, "ERROR: cannot open %s: %s\n",
365                         filename, strerror(errno));
366         }
367         else {
368                 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
369
370                 /* Write out the listrecps while freeing from memory at the
371                  * same time.  Am I clever or what?  :)
372                  */
373                 while (sc.listrecps != NULL) {
374                         fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
375                         nptr = sc.listrecps->next;
376                         phree(sc.listrecps);
377                         sc.listrecps = nptr;
378                 }
379                 while (sc.ignet_push_shares != NULL) {
380                         fprintf(fp, "ignet_push_share|%s\n",
381                                 sc.ignet_push_shares->name);
382                         nptr = sc.ignet_push_shares->next;
383                         phree(sc.ignet_push_shares);
384                         sc.ignet_push_shares = nptr;
385                 }
386
387                 fclose(fp);
388         }
389
390         lprintf(5, "Outbound batch processing finished for <%s>\n",
391                 CC->quickroom.QRname);
392 }
393
394
395 /*
396  * Batch up and send all outbound traffic from the current room
397  */
398 void network_queue_room(struct quickroom *qrbuf, void *data) {
399         struct RoomProcList *ptr;
400
401         ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
402         if (ptr == NULL) return;
403
404         safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
405         ptr->next = rplist;
406         rplist = ptr;
407 }
408         
409
410 /*
411  * network_do_queue()
412  * 
413  * Run through the rooms doing various types of network stuff.
414  */
415 void network_do_queue(void) {
416         static int doing_queue = 0;
417         static time_t last_run = 0L;
418         struct RoomProcList *ptr;
419
420 #define NETWORK_QUEUE_FREQUENCY 3600    /* one hour ... FIXME put in config */
421         /*
422          * Run no more frequently than once every n seconds
423          */
424         if ( (time(NULL) - last_run) < NETWORK_QUEUE_FREQUENCY ) return;
425
426         /*
427          * This is a simple concurrency check to make sure only one queue run
428          * is done at a time.  We could do this with a mutex, but since we
429          * don't really require extremely fine granularity here, we'll do it
430          * with a static variable instead.
431          */
432         if (doing_queue) return;
433         doing_queue = 1;
434         last_run = time(NULL);
435
436         /* 
437          * Go ahead and run the queue
438          */
439         lprintf(7, "network: loading outbound queue\n");
440         ForEachRoom(network_queue_room, NULL);
441
442         lprintf(7, "network: running outbound queue\n");
443         while (rplist != NULL) {
444                 network_spoolout_room(rplist->name);
445                 ptr = rplist;
446                 rplist = rplist->next;
447                 phree(ptr);
448         }
449
450         lprintf(7, "network: queue run completed\n");
451         doing_queue = 0;
452 }
453
454
455 /*
456  * Module entry point
457  */
458 char *Dynamic_Module_Init(void)
459 {
460         CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
461         CtdlRegisterProtoHook(cmd_snet, "SNET", "Get network config");
462         CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);
463         return "$Id$";
464 }