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