4 * This module will eventually replace netproc and some of its utilities. In
5 * the meantime, it serves as a mailing list manager.
7 * Copyright (C) 2000-2001 by Art Cancro and others.
8 * This code is released under the terms of the GNU General Public License.
20 #include <sys/types.h>
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
27 # include <sys/time.h>
38 #include "sysdep_decls.h"
39 #include "citserver.h"
42 #include "dynloader.h"
49 #include "internet_addressing.h"
50 #include "serv_network.h"
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.
60 struct RoomProcList *next;
61 char name[ROOMNAMELEN];
64 struct RoomProcList *rplist = NULL;
69 void cmd_gnet(char *argbuf) {
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",
78 CC->quickroom.QRnumber, CC->quickroom.QRname);
80 fp = fopen(filename, "r");
82 while (fgets(buf, sizeof buf, fp) != NULL) {
83 buf[strlen(buf)-1] = 0;
93 void cmd_snet(char *argbuf) {
94 char tempfilename[SIZ];
99 if (CtdlAccessCheck(ac_room_aide)) return;
100 safestrncpy(tempfilename, tmpnam(NULL), sizeof tempfilename);
101 assoc_file_name(filename, &CC->quickroom, "netconfigs");
103 fp = fopen(tempfilename, "w");
105 cprintf("%d Cannot open %s: %s\n",
106 ERROR+INTERNAL_ERROR,
111 cprintf("%d %s\n", SEND_LISTING, tempfilename);
112 while (client_gets(buf), strcmp(buf, "000")) {
113 fprintf(fp, "%s\n", buf);
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)
122 snprintf(buf, sizeof buf, "/bin/mv %s %s", tempfilename, filename);
129 * Spools out one message from the list.
131 void network_spool_msg(long msgnum, void *userdata) {
132 struct SpoolControl *sc;
133 struct namelist *nptr;
137 char *newpath = NULL;
138 size_t instr_len = SIZ;
139 struct CtdlMessage *msg;
140 struct CtdlMessage *imsg;
141 struct ser_ret sermsg;
147 int delete_after_send = 0; /* Set to 1 to delete after spooling */
149 sc = (struct SpoolControl *)userdata;
152 * Process mailing list recipients
154 if (sc->listrecps != NULL) {
156 /* First, copy it to the spoolout room */
157 err = CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, msgnum, 0);
158 if (err != 0) return;
161 * Figure out how big a buffer we need to allocate
163 for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
164 instr_len = instr_len + strlen(nptr->name);
170 lprintf(9, "Generating delivery instructions\n");
171 instr = mallok(instr_len);
173 lprintf(1, "Cannot allocate %d bytes for instr...\n",
178 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
179 "bounceto|postmaster@%s\n" ,
180 SPOOLMIME, msgnum, time(NULL), config.c_fqdn );
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",
189 * Generate a message from the instructions
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;
199 /* Save delivery instructions in spoolout room */
200 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
201 CtdlFreeMessage(imsg);
205 * Process IGnet push shares
207 if (sc->ignet_push_shares != NULL) {
209 msg = CtdlFetchMessage(msgnum);
212 /* Prepend our node name to the Path field whenever
213 * sending a message to another IGnet node
215 if (msg->cm_fields['P'] == NULL) {
216 msg->cm_fields['P'] = strdoop("username");
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;
226 * Force the message to appear in the correct room
227 * on the far end by setting the C field correctly
229 if (msg->cm_fields['C'] != NULL) {
230 phree(msg->cm_fields['C']);
232 msg->cm_fields['C'] = strdoop(CC->quickroom.QRname);
235 * Determine if this message is set to be deleted
236 * after sending out on the network
238 if (msg->cm_fields['S'] != NULL) {
239 if (!strcasecmp(msg->cm_fields['S'],
241 delete_after_send = 1;
246 * Now serialize it for transmission
248 serialize_message(&sermsg, msg);
249 CtdlFreeMessage(msg);
251 /* Now send it to every node */
252 for (nptr = sc->ignet_push_shares; nptr != NULL;
257 /* FIXME check for valid node name */
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'],
264 if (!strcasecmp(buf, nptr->name)) {
269 /* Send the message */
272 "./network/spoolout/%s",
274 fp = fopen(filename, "ab");
285 /* update lastsent */
286 sc->lastsent = msgnum;
288 /* Delete this message if delete-after-send is set */
289 if (delete_after_send) {
290 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
299 * Batch up and send all outbound traffic from the current room
301 void network_spoolout_room(char *room_to_spool) {
306 struct SpoolControl sc;
307 /* struct namelist *digestrecps = NULL; */
308 struct namelist *nptr;
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);
316 memset(&sc, 0, sizeof(struct SpoolControl));
317 assoc_file_name(filename, &CC->quickroom, "netconfigs");
319 fp = fopen(filename, "r");
321 lprintf(7, "Outbound batch processing skipped for <%s>\n",
322 CC->quickroom.QRname);
326 lprintf(5, "Outbound batch processing started for <%s>\n",
327 CC->quickroom.QRname);
329 while (fgets(buf, sizeof buf, fp) != NULL) {
330 buf[strlen(buf)-1] = 0;
332 extract(instr, buf, 0);
333 if (!strcasecmp(instr, "lastsent")) {
334 sc.lastsent = extract_long(buf, 1);
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);
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;
356 /* Do something useful */
357 CtdlForEachMessage(MSGS_GT, sc.lastsent, (-63), NULL, NULL,
358 network_spool_msg, &sc);
361 /* Now rewrite the config file */
362 fp = fopen(filename, "w");
364 lprintf(1, "ERROR: cannot open %s: %s\n",
365 filename, strerror(errno));
368 fprintf(fp, "lastsent|%ld\n", sc.lastsent);
370 /* Write out the listrecps while freeing from memory at the
371 * same time. Am I clever or what? :)
373 while (sc.listrecps != NULL) {
374 fprintf(fp, "listrecp|%s\n", sc.listrecps->name);
375 nptr = sc.listrecps->next;
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;
390 lprintf(5, "Outbound batch processing finished for <%s>\n",
391 CC->quickroom.QRname);
396 * Batch up and send all outbound traffic from the current room
398 void network_queue_room(struct quickroom *qrbuf, void *data) {
399 struct RoomProcList *ptr;
401 ptr = (struct RoomProcList *) mallok(sizeof (struct RoomProcList));
402 if (ptr == NULL) return;
404 safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
413 * Run through the rooms doing various types of network stuff.
415 void network_do_queue(void) {
416 static int doing_queue = 0;
417 static time_t last_run = 0L;
418 struct RoomProcList *ptr;
420 #define NETWORK_QUEUE_FREQUENCY 3600 /* one hour ... FIXME put in config */
422 * Run no more frequently than once every n seconds
424 if ( (time(NULL) - last_run) < NETWORK_QUEUE_FREQUENCY ) return;
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.
432 if (doing_queue) return;
434 last_run = time(NULL);
437 * Go ahead and run the queue
439 lprintf(7, "network: loading outbound queue\n");
440 ForEachRoom(network_queue_room, NULL);
442 lprintf(7, "network: running outbound queue\n");
443 while (rplist != NULL) {
444 network_spoolout_room(rplist->name);
446 rplist = rplist->next;
450 lprintf(7, "network: queue run completed\n");
458 char *Dynamic_Module_Init(void)
460 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
461 CtdlRegisterProtoHook(cmd_snet, "SNET", "Get network config");
462 CtdlRegisterSessionHook(network_do_queue, EVT_TIMER);