Remove global variables; replace by stack passing.
[citadel.git] / citadel / modules / network / serv_netspool.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2011 by the citadel.org team
6  *
7  *  This program is open source software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
22  * This is a fairly high-level type of critical section.  It ensures that no
23  * two threads work on the netconfigs files at the same time.  Since we do
24  * so many things inside these, here are the rules:
25  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
26  *  2. Do *not* perform any I/O with the client during these sections.
27  *
28  */
29
30 /*
31  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
32  * requests that have not been confirmed will be deleted.
33  */
34 #define EXP     259200  /* three days */
35
36 #include "sysdep.h"
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <stdio.h>
40 #include <fcntl.h>
41 #include <ctype.h>
42 #include <signal.h>
43 #include <pwd.h>
44 #include <errno.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <dirent.h>
48 #if TIME_WITH_SYS_TIME
49 # include <sys/time.h>
50 # include <time.h>
51 #else
52 # if HAVE_SYS_TIME_H
53 #  include <sys/time.h>
54 # else
55 #  include <time.h>
56 # endif
57 #endif
58 #ifdef HAVE_SYSCALL_H
59 # include <syscall.h>
60 #else 
61 # if HAVE_SYS_SYSCALL_H
62 #  include <sys/syscall.h>
63 # endif
64 #endif
65
66 #include <sys/wait.h>
67 #include <string.h>
68 #include <limits.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "user_ops.h"
76 #include "database.h"
77 #include "msgbase.h"
78 #include "internet_addressing.h"
79 #include "serv_network.h"
80 #include "clientsocket.h"
81 #include "file_ops.h"
82 #include "citadel_dirs.h"
83 #include "threads.h"
84
85 #ifndef HAVE_SNPRINTF
86 #include "snprintf.h"
87 #endif
88
89 #include "context.h"
90 #include "netconfig.h"
91 #include "netspool.h"
92 #include "netmail.h"
93 #include "ctdl_module.h"
94
95 /*
96  * Learn topology from path fields
97  */
98 void network_learn_topology(char *node, char *path, NetMap *the_netmap, int *netmap_changed) {
99         char nexthop[256];
100         NetMap *nmptr;
101
102         strcpy(nexthop, "");
103
104         if (num_tokens(path, '!') < 3) return;
105         for (nmptr = the_netmap; nmptr != NULL; nmptr = nmptr->next) {
106                 if (!strcasecmp(nmptr->nodename, node)) {
107                         extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
108                         nmptr->lastcontact = time(NULL);
109                         (*netmap_changed) ++;
110                         return;
111                 }
112         }
113
114         /* If we got here then it's not in the map, so add it. */
115         nmptr = (NetMap *) malloc(sizeof (NetMap));
116         strcpy(nmptr->nodename, node);
117         nmptr->lastcontact = time(NULL);
118         extract_token(nmptr->nexthop, path, 0, '!', sizeof nmptr->nexthop);
119         nmptr->next = the_netmap;
120         the_netmap = nmptr;
121         (*netmap_changed) ++;
122 }
123
124
125         
126
127 int read_spoolcontrol_file(SpoolControl **scc, char *filename)
128 {
129         FILE *fp;
130         char instr[SIZ];
131         char buf[SIZ];
132         char nodename[256];
133         char roomname[ROOMNAMELEN];
134         size_t miscsize = 0;
135         size_t linesize = 0;
136         int skipthisline = 0;
137         namelist *nptr = NULL;
138         maplist *mptr = NULL;
139         SpoolControl *sc;
140
141         fp = fopen(filename, "r");
142         if (fp == NULL) {
143                 return 0;
144         }
145         sc = malloc(sizeof(SpoolControl));
146         memset(sc, 0, sizeof(SpoolControl));
147         *scc = sc;
148
149         while (fgets(buf, sizeof buf, fp) != NULL) {
150                 buf[strlen(buf)-1] = 0;
151
152                 extract_token(instr, buf, 0, '|', sizeof instr);
153                 if (!strcasecmp(instr, strof(lastsent))) {
154                         sc->lastsent = extract_long(buf, 1);
155                 }
156                 else if (!strcasecmp(instr, strof(listrecp))) {
157                         nptr = (namelist *)
158                                 malloc(sizeof(namelist));
159                         nptr->next = sc->listrecps;
160                         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
161                         sc->listrecps = nptr;
162                 }
163                 else if (!strcasecmp(instr, strof(participate))) {
164                         nptr = (namelist *)
165                                 malloc(sizeof(namelist));
166                         nptr->next = sc->participates;
167                         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
168                         sc->participates = nptr;
169                 }
170                 else if (!strcasecmp(instr, strof(digestrecp))) {
171                         nptr = (namelist *)
172                                 malloc(sizeof(namelist));
173                         nptr->next = sc->digestrecps;
174                         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
175                         sc->digestrecps = nptr;
176                 }
177                 else if (!strcasecmp(instr, strof(ignet_push_share))) {
178                         extract_token(nodename, buf, 1, '|', sizeof nodename);
179                         extract_token(roomname, buf, 2, '|', sizeof roomname);
180                         mptr = (maplist *) malloc(sizeof(maplist));
181                         mptr->next = sc->ignet_push_shares;
182                         strcpy(mptr->remote_nodename, nodename);
183                         strcpy(mptr->remote_roomname, roomname);
184                         sc->ignet_push_shares = mptr;
185                 }
186                 else {
187                         /* Preserve 'other' lines ... *unless* they happen to
188                          * be subscribe/unsubscribe pendings with expired
189                          * timestamps.
190                          */
191                         skipthisline = 0;
192                         if (!strncasecmp(buf, strof(subpending)"|", 11)) {
193                                 if (time(NULL) - extract_long(buf, 4) > EXP) {
194                                         skipthisline = 1;
195                                 }
196                         }
197                         if (!strncasecmp(buf, strof(unsubpending)"|", 13)) {
198                                 if (time(NULL) - extract_long(buf, 3) > EXP) {
199                                         skipthisline = 1;
200                                 }
201                         }
202
203                         if (skipthisline == 0) {
204                                 linesize = strlen(buf);
205                                 sc->misc = realloc(sc->misc,
206                                         (miscsize + linesize + 2) );
207                                 sprintf(&sc->misc[miscsize], "%s\n", buf);
208                                 miscsize = miscsize + linesize + 1;
209                         }
210                 }
211
212
213         }
214         fclose(fp);
215         return 1;
216 }
217
218 void free_spoolcontrol_struct(SpoolControl **scc)
219 {
220         SpoolControl *sc;
221         namelist *nptr = NULL;
222         maplist *mptr = NULL;
223
224         sc = *scc;
225         while (sc->listrecps != NULL) {
226                 nptr = sc->listrecps->next;
227                 free(sc->listrecps);
228                 sc->listrecps = nptr;
229         }
230         /* Do the same for digestrecps */
231         while (sc->digestrecps != NULL) {
232                 nptr = sc->digestrecps->next;
233                 free(sc->digestrecps);
234                 sc->digestrecps = nptr;
235         }
236         /* Do the same for participates */
237         while (sc->participates != NULL) {
238                 nptr = sc->participates->next;
239                 free(sc->participates);
240                 sc->participates = nptr;
241         }
242         while (sc->ignet_push_shares != NULL) {
243                 mptr = sc->ignet_push_shares->next;
244                 free(sc->ignet_push_shares);
245                 sc->ignet_push_shares = mptr;
246         }
247         free(sc->misc);
248         free(sc);
249         *scc=NULL;
250 }
251
252 int writenfree_spoolcontrol_file(SpoolControl **scc, char *filename)
253 {
254         char tempfilename[PATH_MAX];
255         int TmpFD;
256         SpoolControl *sc;
257         namelist *nptr = NULL;
258         maplist *mptr = NULL;
259         long len;
260         time_t unixtime;
261         struct timeval tv;
262         long reltid; /* if we don't have SYS_gettid, use "random" value */
263         StrBuf *Cfg;
264         int rc;
265
266         len = strlen(filename);
267         memcpy(tempfilename, filename, len + 1);
268
269
270 #if defined(HAVE_SYSCALL_H) && defined (SYS_gettid)
271         reltid = syscall(SYS_gettid);
272 #endif
273         gettimeofday(&tv, NULL);
274         /* Promote to time_t; types differ on some OSes (like darwin) */
275         unixtime = tv.tv_sec;
276
277         sprintf(tempfilename + len, ".%ld-%ld", reltid, unixtime);
278         sc = *scc;
279         errno = 0;
280         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
281         Cfg = NewStrBuf();
282         if ((TmpFD < 0) || (errno != 0)) {
283                 syslog(LOG_CRIT, "ERROR: cannot open %s: %s\n",
284                         filename, strerror(errno));
285                 free_spoolcontrol_struct(scc);
286                 unlink(tempfilename);
287         }
288         else {
289                 fchown(TmpFD, config.c_ctdluid, 0);
290                 StrBufAppendPrintf(Cfg, "lastsent|%ld\n", sc->lastsent);
291                 
292                 /* Write out the listrecps while freeing from memory at the
293                  * same time.  Am I clever or what?  :)
294                  */
295                 while (sc->listrecps != NULL) {
296                     StrBufAppendPrintf(Cfg, "listrecp|%s\n", sc->listrecps->name);
297                         nptr = sc->listrecps->next;
298                         free(sc->listrecps);
299                         sc->listrecps = nptr;
300                 }
301                 /* Do the same for digestrecps */
302                 while (sc->digestrecps != NULL) {
303                         StrBufAppendPrintf(Cfg, "digestrecp|%s\n", sc->digestrecps->name);
304                         nptr = sc->digestrecps->next;
305                         free(sc->digestrecps);
306                         sc->digestrecps = nptr;
307                 }
308                 /* Do the same for participates */
309                 while (sc->participates != NULL) {
310                         StrBufAppendPrintf(Cfg, "participate|%s\n", sc->participates->name);
311                         nptr = sc->participates->next;
312                         free(sc->participates);
313                         sc->participates = nptr;
314                 }
315                 while (sc->ignet_push_shares != NULL) {
316                         StrBufAppendPrintf(Cfg, "ignet_push_share|%s", sc->ignet_push_shares->remote_nodename);
317                         if (!IsEmptyStr(sc->ignet_push_shares->remote_roomname)) {
318                                 StrBufAppendPrintf(Cfg, "|%s", sc->ignet_push_shares->remote_roomname);
319                         }
320                         StrBufAppendPrintf(Cfg, "\n");
321                         mptr = sc->ignet_push_shares->next;
322                         free(sc->ignet_push_shares);
323                         sc->ignet_push_shares = mptr;
324                 }
325                 if (sc->misc != NULL) {
326                         StrBufAppendBufPlain(Cfg, sc->misc, -1, 0);
327                 }
328                 free(sc->misc);
329
330                 rc = write(TmpFD, ChrPtr(Cfg), StrLength(Cfg));
331                 if ((rc >=0 ) && (rc == StrLength(Cfg))) 
332                 {
333                         close(TmpFD);
334                         rename(tempfilename, filename);
335                 }
336                 else {
337                         syslog(LOG_EMERG, 
338                                       "unable to write %s; [%s]; not enough space on the disk?\n", 
339                                       tempfilename, 
340                                       strerror(errno));
341                         close(TmpFD);
342                         unlink(tempfilename);
343                 }
344                 FreeStrBuf(&Cfg);
345                 free(sc);
346                 *scc=NULL;
347         }
348         return 1;
349 }
350 int is_recipient(SpoolControl *sc, const char *Name)
351 {
352         namelist *nptr;
353         size_t len;
354
355         len = strlen(Name);
356         nptr = sc->listrecps;
357         while (nptr != NULL) {
358                 if (strncmp(Name, nptr->name, len)==0)
359                         return 1;
360                 nptr = nptr->next;
361         }
362         /* Do the same for digestrecps */
363         nptr = sc->digestrecps;
364         while (nptr != NULL) {
365                 if (strncmp(Name, nptr->name, len)==0)
366                         return 1;
367                 nptr = nptr->next;
368         }
369         /* Do the same for participates */
370         nptr = sc->participates;
371         while (nptr != NULL) {
372                 if (strncmp(Name, nptr->name, len)==0)
373                         return 1;
374                 nptr = nptr->next;
375         }
376         return 0;
377 }
378
379
380 /*
381  * Batch up and send all outbound traffic from the current room
382  */
383 void network_spoolout_room(char *room_to_spool) {
384         char buf[SIZ];
385         char filename[PATH_MAX];
386         SpoolControl *sc;
387         int i;
388
389         /*
390          * If the room doesn't exist, don't try to perform its networking tasks.
391          * Normally this should never happen, but once in a while maybe a room gets
392          * queued for networking and then deleted before it can happen.
393          */
394         if (CtdlGetRoom(&CC->room, room_to_spool) != 0) {
395                 syslog(LOG_CRIT, "ERROR: cannot load <%s>\n", room_to_spool);
396                 return;
397         }
398
399         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
400         begin_critical_section(S_NETCONFIGS);
401
402         /* Only do net processing for rooms that have netconfigs */
403         if (!read_spoolcontrol_file(&sc, filename))
404         {
405                 end_critical_section(S_NETCONFIGS);
406                 return;
407         }
408         syslog(LOG_INFO, "Networking started for <%s>\n", CC->room.QRname);
409
410         /* If there are digest recipients, we have to build a digest */
411         if (sc->digestrecps != NULL) {
412                 sc->digestfp = tmpfile();
413                 fprintf(sc->digestfp, "Content-type: text/plain\n\n");
414         }
415
416         /* Do something useful */
417         CtdlForEachMessage(MSGS_GT, sc->lastsent, NULL, NULL, NULL,
418                 network_spool_msg, sc);
419
420         /* If we wrote a digest, deliver it and then close it */
421         snprintf(buf, sizeof buf, "room_%s@%s",
422                 CC->room.QRname, config.c_fqdn);
423         for (i=0; buf[i]; ++i) {
424                 buf[i] = tolower(buf[i]);
425                 if (isspace(buf[i])) buf[i] = '_';
426         }
427         if (sc->digestfp != NULL) {
428                 fprintf(sc->digestfp,   " -----------------------------------"
429                                         "------------------------------------"
430                                         "-------\n"
431                                         "You are subscribed to the '%s' "
432                                         "list.\n"
433                                         "To post to the list: %s\n",
434                                         CC->room.QRname, buf
435                 );
436                 network_deliver_digest(sc);     /* deliver and close */
437         }
438
439         /* Now rewrite the config file */
440         writenfree_spoolcontrol_file(&sc, filename);
441         end_critical_section(S_NETCONFIGS);
442 }
443
444 /*
445  * Process a buffer containing a single message from a single file
446  * from the inbound queue 
447  */
448 void network_process_buffer(char *buffer, long size, char *working_ignetcfg, NetMap *the_netmap, int *netmap_changed)
449 {
450         struct CtdlMessage *msg = NULL;
451         long pos;
452         int field;
453         struct recptypes *recp = NULL;
454         char target_room[ROOMNAMELEN];
455         struct ser_ret sermsg;
456         char *oldpath = NULL;
457         char filename[PATH_MAX];
458         FILE *fp;
459         char nexthop[SIZ];
460         unsigned char firstbyte;
461         unsigned char lastbyte;
462
463         syslog(LOG_DEBUG, "network_process_buffer() processing %ld bytes\n", size);
464
465         /* Validate just a little bit.  First byte should be FF and * last byte should be 00. */
466         firstbyte = buffer[0];
467         lastbyte = buffer[size-1];
468         if ( (firstbyte != 255) || (lastbyte != 0) ) {
469                 syslog(LOG_ERR, "Corrupt message ignored.  Length=%ld, firstbyte = %d, lastbyte = %d\n",
470                         size, firstbyte, lastbyte);
471                 return;
472         }
473
474         /* Set default target room to trash */
475         strcpy(target_room, TWITROOM);
476
477         /* Load the message into memory */
478         msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
479         memset(msg, 0, sizeof(struct CtdlMessage));
480         msg->cm_magic = CTDLMESSAGE_MAGIC;
481         msg->cm_anon_type = buffer[1];
482         msg->cm_format_type = buffer[2];
483
484         for (pos = 3; pos < size; ++pos) {
485                 field = buffer[pos];
486                 msg->cm_fields[field] = strdup(&buffer[pos+1]);
487                 pos = pos + strlen(&buffer[(int)pos]);
488         }
489
490         /* Check for message routing */
491         if (msg->cm_fields['D'] != NULL) {
492                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
493
494                         /* route the message */
495                         strcpy(nexthop, "");
496                         if (is_valid_node(nexthop, 
497                                           NULL, 
498                                           msg->cm_fields['D'], 
499                                           working_ignetcfg, 
500                                           the_netmap) == 0) 
501                         {
502                                 /* prepend our node to the path */
503                                 if (msg->cm_fields['P'] != NULL) {
504                                         oldpath = msg->cm_fields['P'];
505                                         msg->cm_fields['P'] = NULL;
506                                 }
507                                 else {
508                                         oldpath = strdup("unknown_user");
509                                 }
510                                 size = strlen(oldpath) + SIZ;
511                                 msg->cm_fields['P'] = malloc(size);
512                                 snprintf(msg->cm_fields['P'], size, "%s!%s",
513                                         config.c_nodename, oldpath);
514                                 free(oldpath);
515
516                                 /* serialize the message */
517                                 serialize_message(&sermsg, msg);
518
519                                 /* now send it */
520                                 if (IsEmptyStr(nexthop)) {
521                                         strcpy(nexthop, msg->cm_fields['D']);
522                                 }
523                                 snprintf(filename, 
524                                         sizeof filename,
525                                         "%s/%s@%lx%x",
526                                         ctdl_netout_dir,
527                                         nexthop,
528                                         time(NULL),
529                                         rand()
530                                 );
531                                 syslog(LOG_DEBUG, "Appending to %s\n", filename);
532                                 fp = fopen(filename, "ab");
533                                 if (fp != NULL) {
534                                         fwrite(sermsg.ser, sermsg.len, 1, fp);
535                                         fclose(fp);
536                                 }
537                                 else {
538                                         syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno));
539                                 }
540                                 free(sermsg.ser);
541                                 CtdlFreeMessage(msg);
542                                 return;
543                         }
544                         
545                         else {  /* invalid destination node name */
546
547                                 network_bounce(msg,
548 "A message you sent could not be delivered due to an invalid destination node"
549 " name.  Please check the address and try sending the message again.\n");
550                                 msg = NULL;
551                                 return;
552
553                         }
554                 }
555         }
556
557         /*
558          * Check to see if we already have a copy of this message, and
559          * abort its processing if so.  (We used to post a warning to Aide>
560          * every time this happened, but the network is now so densely
561          * connected that it's inevitable.)
562          */
563         if (network_usetable(msg) != 0) {
564                 CtdlFreeMessage(msg);
565                 return;
566         }
567
568         /* Learn network topology from the path */
569         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
570                 network_learn_topology(msg->cm_fields['N'], 
571                                        msg->cm_fields['P'], 
572                                        the_netmap, 
573                                        netmap_changed);
574         }
575
576         /* Is the sending node giving us a very persuasive suggestion about
577          * which room this message should be saved in?  If so, go with that.
578          */
579         if (msg->cm_fields['C'] != NULL) {
580                 safestrncpy(target_room, msg->cm_fields['C'], sizeof target_room);
581         }
582
583         /* Otherwise, does it have a recipient?  If so, validate it... */
584         else if (msg->cm_fields['R'] != NULL) {
585                 recp = validate_recipients(msg->cm_fields['R'], NULL, 0);
586                 if (recp != NULL) if (recp->num_error != 0) {
587                         network_bounce(msg,
588                                 "A message you sent could not be delivered due to an invalid address.\n"
589                                 "Please check the address and try sending the message again.\n");
590                         msg = NULL;
591                         free_recipients(recp);
592                         syslog(LOG_DEBUG, "Bouncing message due to invalid recipient address.\n");
593                         return;
594                 }
595                 strcpy(target_room, "");        /* no target room if mail */
596         }
597
598         /* Our last shot at finding a home for this message is to see if
599          * it has the O field (Originating room) set.
600          */
601         else if (msg->cm_fields['O'] != NULL) {
602                 safestrncpy(target_room, msg->cm_fields['O'], sizeof target_room);
603         }
604
605         /* Strip out fields that are only relevant during transit */
606         if (msg->cm_fields['D'] != NULL) {
607                 free(msg->cm_fields['D']);
608                 msg->cm_fields['D'] = NULL;
609         }
610         if (msg->cm_fields['C'] != NULL) {
611                 free(msg->cm_fields['C']);
612                 msg->cm_fields['C'] = NULL;
613         }
614
615         /* save the message into a room */
616         if (PerformNetprocHooks(msg, target_room) == 0) {
617                 msg->cm_flags = CM_SKIP_HOOKS;
618                 CtdlSubmitMsg(msg, recp, target_room, 0);
619         }
620         CtdlFreeMessage(msg);
621         free_recipients(recp);
622 }
623
624
625 /*
626  * Process a single message from a single file from the inbound queue 
627  */
628 void network_process_message(FILE *fp, 
629                              long msgstart, 
630                              long msgend,
631                              char *working_ignetcfg,
632                              NetMap *the_netmap, 
633                              int *netmap_changed)
634 {
635         long hold_pos;
636         long size;
637         char *buffer;
638
639         hold_pos = ftell(fp);
640         size = msgend - msgstart + 1;
641         buffer = malloc(size);
642         if (buffer != NULL) {
643                 fseek(fp, msgstart, SEEK_SET);
644                 if (fread(buffer, size, 1, fp) > 0) {
645                         network_process_buffer(buffer, 
646                                                size, 
647                                                working_ignetcfg, 
648                                                the_netmap, 
649                                                netmap_changed);
650                 }
651                 free(buffer);
652         }
653
654         fseek(fp, hold_pos, SEEK_SET);
655 }
656
657
658 /*
659  * Process a single file from the inbound queue 
660  */
661 void network_process_file(char *filename,
662                           char *working_ignetcfg,
663                           NetMap *the_netmap, 
664                           int *netmap_changed)
665 {
666         FILE *fp;
667         long msgstart = (-1L);
668         long msgend = (-1L);
669         long msgcur = 0L;
670         int ch;
671
672
673         fp = fopen(filename, "rb");
674         if (fp == NULL) {
675                 syslog(LOG_CRIT, "Error opening %s: %s\n", filename, strerror(errno));
676                 return;
677         }
678
679         fseek(fp, 0L, SEEK_END);
680         syslog(LOG_INFO, "network: processing %ld bytes from %s\n", ftell(fp), filename);
681         rewind(fp);
682
683         /* Look for messages in the data stream and break them out */
684         while (ch = getc(fp), ch >= 0) {
685         
686                 if (ch == 255) {
687                         if (msgstart >= 0L) {
688                                 msgend = msgcur - 1;
689                                 network_process_message(fp,
690                                                         msgstart,
691                                                         msgend,
692                                                         working_ignetcfg,
693                                                         the_netmap,
694                                                         netmap_changed);
695                         }
696                         msgstart = msgcur;
697                 }
698
699                 ++msgcur;
700         }
701
702         msgend = msgcur - 1;
703         if (msgstart >= 0L) {
704                 network_process_message(fp,
705                                         msgstart,
706                                         msgend,
707                                         working_ignetcfg,
708                                         the_netmap,
709                                         netmap_changed);
710         }
711
712         fclose(fp);
713         unlink(filename);
714 }
715
716
717 /*
718  * Process anything in the inbound queue
719  */
720 void network_do_spoolin(char *working_ignetcfg, NetMap *the_netmap, int *netmap_changed)
721 {
722         DIR *dp;
723         struct dirent *d;
724         struct stat statbuf;
725         char filename[PATH_MAX];
726         static time_t last_spoolin_mtime = 0L;
727
728         /*
729          * Check the spoolin directory's modification time.  If it hasn't
730          * been touched, we don't need to scan it.
731          */
732         if (stat(ctdl_netin_dir, &statbuf)) return;
733         if (statbuf.st_mtime == last_spoolin_mtime) {
734                 syslog(LOG_DEBUG, "network: nothing in inbound queue\n");
735                 return;
736         }
737         last_spoolin_mtime = statbuf.st_mtime;
738         syslog(LOG_DEBUG, "network: processing inbound queue\n");
739
740         /*
741          * Ok, there's something interesting in there, so scan it.
742          */
743         dp = opendir(ctdl_netin_dir);
744         if (dp == NULL) return;
745
746         while (d = readdir(dp), d != NULL) {
747                 if ((strcmp(d->d_name, ".")) && (strcmp(d->d_name, ".."))) {
748                         snprintf(filename, 
749                                 sizeof filename,
750                                 "%s/%s",
751                                 ctdl_netin_dir,
752                                 d->d_name
753                         );
754                         network_process_file(filename,
755                                              working_ignetcfg,
756                                              the_netmap,
757                                              netmap_changed);
758                 }
759         }
760
761         closedir(dp);
762 }
763
764 /*
765  * Step 1: consolidate files in the outbound queue into one file per neighbor node
766  * Step 2: delete any files in the outbound queue that were for neighbors who no longer exist.
767  */
768 void network_consolidate_spoolout(char *working_ignetcfg, NetMap *the_netmap)
769 {
770         DIR *dp;
771         struct dirent *d;
772         char filename[PATH_MAX];
773         char cmd[PATH_MAX];
774         char nexthop[256];
775         int i;
776         char *ptr;
777
778         /* Step 1: consolidate files in the outbound queue into one file per neighbor node */
779         dp = opendir(ctdl_netout_dir);
780         if (dp == NULL) return;
781         while (d = readdir(dp), d != NULL) {
782                 if (
783                         (strcmp(d->d_name, "."))
784                         && (strcmp(d->d_name, ".."))
785                         && (strchr(d->d_name, '@') != NULL)
786                 ) {
787                         safestrncpy(nexthop, d->d_name, sizeof nexthop);
788                         ptr = strchr(nexthop, '@');
789                         if (ptr) *ptr = 0;
790         
791                         snprintf(filename, 
792                                 sizeof filename,
793                                 "%s/%s",
794                                 ctdl_netout_dir,
795                                 d->d_name
796                         );
797         
798                         syslog(LOG_DEBUG, "Consolidate %s to %s\n", filename, nexthop);
799                         if (network_talking_to(nexthop, NTT_CHECK)) {
800                                 syslog(LOG_DEBUG,
801                                         "Currently online with %s - skipping for now\n",
802                                         nexthop
803                                 );
804                         }
805                         else {
806                                 network_talking_to(nexthop, NTT_ADD);
807                                 snprintf(cmd, sizeof cmd, "/bin/cat %s >>%s/%s && /bin/rm -f %s",
808                                         filename,
809                                         ctdl_netout_dir, nexthop,
810                                         filename
811                                 );
812                                 system(cmd);
813                                 network_talking_to(nexthop, NTT_REMOVE);
814                         }
815                 }
816         }
817         closedir(dp);
818
819         /* Step 2: delete any files in the outbound queue that were for neighbors who no longer exist */
820
821         dp = opendir(ctdl_netout_dir);
822         if (dp == NULL) return;
823
824         while (d = readdir(dp), d != NULL) {
825                 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
826                         continue;
827                 ptr = strchr(d->d_name, '@');
828                 if (d != NULL)
829                         continue;
830                 snprintf(filename, 
831                         sizeof filename,
832                         "%s/%s",
833                         ctdl_netout_dir,
834                         d->d_name
835                 );
836
837                 strcpy(nexthop, "");
838                 i = is_valid_node(nexthop,
839                                   NULL,
840                                   d->d_name,
841                                   working_ignetcfg,
842                                   the_netmap);
843         
844                 if ( (i != 0) || !IsEmptyStr(nexthop) ) {
845                         unlink(filename);
846                 }
847         }
848
849
850         closedir(dp);
851 }
852
853
854
855
856 /*
857  * It's ok if these directories already exist.  Just fail silently.
858  */
859 void create_spool_dirs(void) {
860         if ((mkdir(ctdl_spool_dir, 0700) != 0) && (errno != EEXIST))
861                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_spool_dir, strerror(errno));
862         if (chown(ctdl_spool_dir, CTDLUID, (-1)) != 0)
863                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_spool_dir, strerror(errno));
864         if ((mkdir(ctdl_netin_dir, 0700) != 0) && (errno != EEXIST))
865                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_netin_dir, strerror(errno));
866         if (chown(ctdl_netin_dir, CTDLUID, (-1)) != 0)
867                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_netin_dir, strerror(errno));
868         if ((mkdir(ctdl_nettmp_dir, 0700) != 0) && (errno != EEXIST))
869                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_nettmp_dir, strerror(errno));
870         if (chown(ctdl_nettmp_dir, CTDLUID, (-1)) != 0)
871                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_nettmp_dir, strerror(errno));
872         if ((mkdir(ctdl_netout_dir, 0700) != 0) && (errno != EEXIST))
873                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_netout_dir, strerror(errno));
874         if (chown(ctdl_netout_dir, CTDLUID, (-1)) != 0)
875                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_netout_dir, strerror(errno));
876 }
877
878 /*
879  * Module entry point
880  */
881 CTDL_MODULE_INIT(network_spool)
882 {
883         if (!threading)
884         {
885                 create_spool_dirs();
886                 CtdlRegisterCleanupHook(destroy_network_queue_room);
887         }
888         return "network_spool";
889 }