659f8e25918dfd20ea8aad048041f043e8a97ca6
[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
80 #include "ctdl_module.h"
81
82 #include "netspool.h"
83 #include "netmail.h"
84
85
86 #ifndef DT_UNKNOWN
87 #define DT_UNKNOWN     0
88 #define DT_DIR         4
89 #define DT_REG         8
90 #define DT_LNK         10
91
92 #define IFTODT(mode)   (((mode) & 0170000) >> 12)
93 #define DTTOIF(dirtype)        ((dirtype) << 12)
94 #endif
95
96
97 void ParseLastSent(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG)
98 {
99         RoomNetCfgLine *nptr;
100         nptr = (RoomNetCfgLine *)
101                 malloc(sizeof(RoomNetCfgLine));
102         
103         OneRNCFG->lastsent = extract_long(LinePos, 0);
104         OneRNCFG->NetConfigs[ThisOne->C] = nptr;
105 }
106
107 void ParseRoomAlias(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *rncfg)
108 {
109 /*
110         if (rncfg->RNCfg->sender != NULL)
111                 continue; / * just one alowed... * /
112         extract_token(nptr->name, buf, 1, '|', sizeof nptr->name);
113         rncfg->RNCfg->sender = nptr;
114 */
115 }
116
117 void ParseSubPendingLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG)
118 {
119         if (time(NULL) - extract_long(LinePos, 3) > EXP) 
120                 return; /* expired subscription... */
121
122         ParseGeneric(ThisOne, Line, LinePos, OneRNCFG);
123 }
124 void ParseUnSubPendingLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG)
125 {
126         if (time(NULL) - extract_long(LinePos, 2) > EXP)
127                 return; /* expired subscription... */
128
129         ParseGeneric(ThisOne, Line, LinePos, OneRNCFG);
130 }
131
132
133 void SerializeLastSent(const CfgLineType *ThisOne, StrBuf *OutputBuffer, OneRoomNetCfg *RNCfg, RoomNetCfgLine *data)
134 {
135         StrBufAppendBufPlain(OutputBuffer, CKEY(ThisOne->Str), 0);
136         StrBufAppendPrintf(OutputBuffer, "|%ld\n", RNCfg->lastsent);
137 }
138
139 void DeleteLastSent(const CfgLineType *ThisOne, RoomNetCfgLine **data)
140 {
141         free(*data);
142         *data = NULL;
143 }
144
145
146
147
148 /*
149  * Batch up and send all outbound traffic from the current room
150  */
151 void network_spoolout_room(RoomProcList *room_to_spool,                        
152                            HashList *working_ignetcfg,
153                            HashList *the_netmap)
154 {
155         struct CitContext *CCC = CC;
156         char buf[SIZ];
157         SpoolControl sc;
158         int i;
159
160         if ((sc.RNCfg->NetConfigs[listrecp] == NULL) && 
161             (sc.RNCfg->NetConfigs[digestrecp] == NULL) && 
162             (sc.RNCfg->NetConfigs[participate] == NULL) && 
163             (sc.RNCfg->NetConfigs[ignet_push_share] == NULL))
164         {
165                 /* nothing to do for this room... */
166                 return;
167         }
168
169         /*
170          * If the room doesn't exist, don't try to perform its networking tasks.
171          * Normally this should never happen, but once in a while maybe a room gets
172          * queued for networking and then deleted before it can happen.
173          */
174         if (CtdlGetRoom(&CCC->room, room_to_spool->name) != 0) {
175                 syslog(LOG_CRIT, "ERROR: cannot load <%s>\n", room_to_spool->name);
176                 return;
177         }
178
179         
180         syslog(LOG_INFO, "Networking started for <%s>\n", CCC->room.QRname);
181         sc.RNCfg = room_to_spool->OneRNCfg;
182         sc.lastsent = room_to_spool->OneRNCfg->lastsent;
183         sc.working_ignetcfg = working_ignetcfg;
184         sc.the_netmap = the_netmap;
185
186         /* If there are digest recipients, we have to build a digest */
187         if (sc.RNCfg->NetConfigs[digestrecp] != NULL) {
188                 sc.digestfp = tmpfile();
189                 fprintf(sc.digestfp, "Content-type: text/plain\n\n");
190         }
191
192         /* Do something useful */
193         CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, NULL,
194                 network_spool_msg, &sc);
195
196         /* If we wrote a digest, deliver it and then close it */
197         if (StrLength(sc.RNCfg->Sender) > 0)
198         {
199                 long len;
200                 len = StrLength(sc.RNCfg->Sender);
201                 if (len + 1 > sizeof(buf))
202                         len = sizeof(buf) - 1;
203                 memcpy(buf, ChrPtr(sc.RNCfg->Sender), len);
204                 buf[len] = '\0';
205         }
206         else
207         {
208                 snprintf(buf, sizeof buf, "room_%s@%s",
209                          CCC->room.QRname, config.c_fqdn);
210         }
211
212         for (i=0; buf[i]; ++i) {
213                 buf[i] = tolower(buf[i]);
214                 if (isspace(buf[i])) buf[i] = '_';
215         }
216         if (sc.digestfp != NULL) {
217                 fprintf(sc.digestfp,
218                         " -----------------------------------"
219                         "------------------------------------"
220                         "-------\n"
221                         "You are subscribed to the '%s' "
222                         "list.\n"
223                         "To post to the list: %s\n",
224                         CCC->room.QRname, buf
225                 );
226                 network_deliver_digest(&sc);    /* deliver and close */
227         }
228
229         /* Now rewrite the config file */
230         if (sc.lastsent != room_to_spool->OneRNCfg->lastsent)
231         {
232                 room_to_spool->OneRNCfg->lastsent = sc.lastsent;
233                 room_to_spool->OneRNCfg->changed = 1;
234         }
235         end_critical_section(S_NETCONFIGS);
236 }
237
238 /*
239  * Process a buffer containing a single message from a single file
240  * from the inbound queue 
241  */
242 void network_process_buffer(char *buffer, long size, HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed)
243 {
244         struct CitContext *CCC = CC;
245         StrBuf *Buf = NULL;
246         struct CtdlMessage *msg = NULL;
247         long pos;
248         int field;
249         struct recptypes *recp = NULL;
250         char target_room[ROOMNAMELEN];
251         struct ser_ret sermsg;
252         char *oldpath = NULL;
253         char filename[PATH_MAX];
254         FILE *fp;
255         const StrBuf *nexthop = NULL;
256         unsigned char firstbyte;
257         unsigned char lastbyte;
258
259         QN_syslog(LOG_DEBUG, "network_process_buffer() processing %ld bytes\n", size);
260
261         /* Validate just a little bit.  First byte should be FF and * last byte should be 00. */
262         firstbyte = buffer[0];
263         lastbyte = buffer[size-1];
264         if ( (firstbyte != 255) || (lastbyte != 0) ) {
265                 QN_syslog(LOG_ERR, "Corrupt message ignored.  Length=%ld, firstbyte = %d, lastbyte = %d\n",
266                           size, firstbyte, lastbyte);
267                 return;
268         }
269
270         /* Set default target room to trash */
271         strcpy(target_room, TWITROOM);
272
273         /* Load the message into memory */
274         msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
275         memset(msg, 0, sizeof(struct CtdlMessage));
276         msg->cm_magic = CTDLMESSAGE_MAGIC;
277         msg->cm_anon_type = buffer[1];
278         msg->cm_format_type = buffer[2];
279
280         for (pos = 3; pos < size; ++pos) {
281                 field = buffer[pos];
282                 msg->cm_fields[field] = strdup(&buffer[pos+1]);
283                 pos = pos + strlen(&buffer[(int)pos]);
284         }
285
286         /* Check for message routing */
287         if (msg->cm_fields['D'] != NULL) {
288                 if (strcasecmp(msg->cm_fields['D'], config.c_nodename)) {
289
290                         /* route the message */
291                         Buf = NewStrBufPlain(msg->cm_fields['D'], -1);
292                         if (CtdlIsValidNode(&nexthop, 
293                                             NULL, 
294                                             Buf, 
295                                             working_ignetcfg, 
296                                             the_netmap) == 0) 
297                         {
298                                 /* prepend our node to the path */
299                                 if (msg->cm_fields['P'] != NULL) {
300                                         oldpath = msg->cm_fields['P'];
301                                         msg->cm_fields['P'] = NULL;
302                                 }
303                                 else {
304                                         oldpath = strdup("unknown_user");
305                                 }
306                                 size = strlen(oldpath) + SIZ;
307                                 msg->cm_fields['P'] = malloc(size);
308                                 snprintf(msg->cm_fields['P'], size, "%s!%s",
309                                         config.c_nodename, oldpath);
310                                 free(oldpath);
311
312                                 /* serialize the message */
313                                 serialize_message(&sermsg, msg);
314
315                                 /* now send it */
316                                 if (StrLength(nexthop) == 0) {
317                                         nexthop = Buf;
318                                 }
319                                 snprintf(filename,
320                                          sizeof filename,
321                                          "%s/%s@%lx%x",
322                                          ctdl_netout_dir,
323                                          ChrPtr(nexthop),
324                                          time(NULL),
325                                          rand()
326                                 );
327                                 QN_syslog(LOG_DEBUG, "Appending to %s\n", filename);
328                                 fp = fopen(filename, "ab");
329                                 if (fp != NULL) {
330                                         fwrite(sermsg.ser, sermsg.len, 1, fp);
331                                         fclose(fp);
332                                 }
333                                 else {
334                                         QN_syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno));
335                                 }
336                                 free(sermsg.ser);
337                                 CtdlFreeMessage(msg);
338                                 FreeStrBuf(&Buf);
339                                 return;
340                         }
341                         
342                         else {  /* invalid destination node name */
343                                 FreeStrBuf(&Buf);
344
345                                 network_bounce(msg,
346 "A message you sent could not be delivered due to an invalid destination node"
347 " name.  Please check the address and try sending the message again.\n");
348                                 msg = NULL;
349                                 return;
350
351                         }
352                 }
353         }
354
355         /*
356          * Check to see if we already have a copy of this message, and
357          * abort its processing if so.  (We used to post a warning to Aide>
358          * every time this happened, but the network is now so densely
359          * connected that it's inevitable.)
360          */
361         if (network_usetable(msg) != 0) {
362                 CtdlFreeMessage(msg);
363                 return;
364         }
365
366         /* Learn network topology from the path */
367         if ((msg->cm_fields['N'] != NULL) && (msg->cm_fields['P'] != NULL)) {
368                 NetworkLearnTopology(msg->cm_fields['N'], 
369                                      msg->cm_fields['P'], 
370                                      the_netmap, 
371                                      netmap_changed);
372         }
373
374         /* Is the sending node giving us a very persuasive suggestion about
375          * which room this message should be saved in?  If so, go with that.
376          */
377         if (msg->cm_fields['C'] != NULL) {
378                 safestrncpy(target_room, msg->cm_fields['C'], sizeof target_room);
379         }
380
381         /* Otherwise, does it have a recipient?  If so, validate it... */
382         else if (msg->cm_fields['R'] != NULL) {
383                 recp = validate_recipients(msg->cm_fields['R'], NULL, 0);
384                 if (recp != NULL) if (recp->num_error != 0) {
385                         network_bounce(msg,
386                                 "A message you sent could not be delivered due to an invalid address.\n"
387                                 "Please check the address and try sending the message again.\n");
388                         msg = NULL;
389                         free_recipients(recp);
390                         QNM_syslog(LOG_DEBUG, "Bouncing message due to invalid recipient address.\n");
391                         return;
392                 }
393                 strcpy(target_room, "");        /* no target room if mail */
394         }
395
396         /* Our last shot at finding a home for this message is to see if
397          * it has the O field (Originating room) set.
398          */
399         else if (msg->cm_fields['O'] != NULL) {
400                 safestrncpy(target_room, msg->cm_fields['O'], sizeof target_room);
401         }
402
403         /* Strip out fields that are only relevant during transit */
404         if (msg->cm_fields['D'] != NULL) {
405                 free(msg->cm_fields['D']);
406                 msg->cm_fields['D'] = NULL;
407         }
408         if (msg->cm_fields['C'] != NULL) {
409                 free(msg->cm_fields['C']);
410                 msg->cm_fields['C'] = NULL;
411         }
412
413         /* save the message into a room */
414         if (PerformNetprocHooks(msg, target_room) == 0) {
415                 msg->cm_flags = CM_SKIP_HOOKS;
416                 CtdlSubmitMsg(msg, recp, target_room, 0);
417         }
418         CtdlFreeMessage(msg);
419         free_recipients(recp);
420 }
421
422
423 /*
424  * Process a single message from a single file from the inbound queue 
425  */
426 void network_process_message(FILE *fp, 
427                              long msgstart, 
428                              long msgend,
429                              HashList *working_ignetcfg,
430                              HashList *the_netmap, 
431                              int *netmap_changed)
432 {
433         long hold_pos;
434         long size;
435         char *buffer;
436
437         hold_pos = ftell(fp);
438         size = msgend - msgstart + 1;
439         buffer = malloc(size);
440         if (buffer != NULL) {
441                 fseek(fp, msgstart, SEEK_SET);
442                 if (fread(buffer, size, 1, fp) > 0) {
443                         network_process_buffer(buffer, 
444                                                size, 
445                                                working_ignetcfg, 
446                                                the_netmap, 
447                                                netmap_changed);
448                 }
449                 free(buffer);
450         }
451
452         fseek(fp, hold_pos, SEEK_SET);
453 }
454
455
456 /*
457  * Process a single file from the inbound queue 
458  */
459 void network_process_file(char *filename,
460                           HashList *working_ignetcfg,
461                           HashList *the_netmap, 
462                           int *netmap_changed)
463 {
464         struct CitContext *CCC = CC;
465         FILE *fp;
466         long msgstart = (-1L);
467         long msgend = (-1L);
468         long msgcur = 0L;
469         int ch;
470
471
472         fp = fopen(filename, "rb");
473         if (fp == NULL) {
474                 QN_syslog(LOG_CRIT, "Error opening %s: %s\n", filename, strerror(errno));
475                 return;
476         }
477
478         fseek(fp, 0L, SEEK_END);
479         QN_syslog(LOG_INFO, "network: processing %ld bytes from %s\n", ftell(fp), filename);
480         rewind(fp);
481
482         /* Look for messages in the data stream and break them out */
483         while (ch = getc(fp), ch >= 0) {
484         
485                 if (ch == 255) {
486                         if (msgstart >= 0L) {
487                                 msgend = msgcur - 1;
488                                 network_process_message(fp,
489                                                         msgstart,
490                                                         msgend,
491                                                         working_ignetcfg,
492                                                         the_netmap,
493                                                         netmap_changed);
494                         }
495                         msgstart = msgcur;
496                 }
497
498                 ++msgcur;
499         }
500
501         msgend = msgcur - 1;
502         if (msgstart >= 0L) {
503                 network_process_message(fp,
504                                         msgstart,
505                                         msgend,
506                                         working_ignetcfg,
507                                         the_netmap,
508                                         netmap_changed);
509         }
510
511         fclose(fp);
512         unlink(filename);
513 }
514
515
516 /*
517  * Process anything in the inbound queue
518  */
519 void network_do_spoolin(HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed)
520 {
521         struct CitContext *CCC = CC;
522         DIR *dp;
523         struct dirent *d;
524         struct dirent *filedir_entry;
525         struct stat statbuf;
526         char filename[PATH_MAX];
527         static time_t last_spoolin_mtime = 0L;
528         int d_type = 0;
529         int d_namelen;
530
531         /*
532          * Check the spoolin directory's modification time.  If it hasn't
533          * been touched, we don't need to scan it.
534          */
535         if (stat(ctdl_netin_dir, &statbuf)) return;
536         if (statbuf.st_mtime == last_spoolin_mtime) {
537                 QNM_syslog(LOG_DEBUG, "network: nothing in inbound queue\n");
538                 return;
539         }
540         last_spoolin_mtime = statbuf.st_mtime;
541         QNM_syslog(LOG_DEBUG, "network: processing inbound queue\n");
542
543         /*
544          * Ok, there's something interesting in there, so scan it.
545          */
546         dp = opendir(ctdl_netin_dir);
547         if (dp == NULL) return;
548
549         d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 1);
550         if (d == NULL) {
551                 closedir(dp);
552                 return;
553         }
554
555         while ((readdir_r(dp, d, &filedir_entry) == 0) &&
556                (filedir_entry != NULL))
557         {
558 #ifdef _DIRENT_HAVE_D_NAMLEN
559                 d_namelen = filedir_entry->d_namelen;
560
561 #else
562                 d_namelen = strlen(filedir_entry->d_name);
563 #endif
564
565 #ifdef _DIRENT_HAVE_D_TYPE
566                 d_type = filedir_entry->d_type;
567 #else
568                 d_type = DT_UNKNOWN;
569 #endif
570                 if ((d_namelen > 1) && filedir_entry->d_name[d_namelen - 1] == '~')
571                         continue; /* Ignore backup files... */
572
573                 if ((d_namelen == 1) && 
574                     (filedir_entry->d_name[0] == '.'))
575                         continue;
576
577                 if ((d_namelen == 2) && 
578                     (filedir_entry->d_name[0] == '.') &&
579                     (filedir_entry->d_name[1] == '.'))
580                         continue;
581
582                 if (d_type == DT_UNKNOWN) {
583                         struct stat s;
584                         char path[PATH_MAX];
585
586                         snprintf(path,
587                                  PATH_MAX,
588                                  "%s/%s", 
589                                  ctdl_netin_dir,
590                                  filedir_entry->d_name);
591
592                         if (lstat(path, &s) == 0) {
593                                 d_type = IFTODT(s.st_mode);
594                         }
595                 }
596
597                 switch (d_type)
598                 {
599                 case DT_DIR:
600                         break;
601                 case DT_LNK: /* TODO: check whether its a file or a directory */
602                 case DT_REG:
603                         snprintf(filename, 
604                                 sizeof filename,
605                                 "%s/%s",
606                                 ctdl_netin_dir,
607                                 d->d_name
608                         );
609                         network_process_file(filename,
610                                              working_ignetcfg,
611                                              the_netmap,
612                                              netmap_changed);
613                 }
614         }
615
616         closedir(dp);
617         free(d);
618 }
619
620 /*
621  * Step 1: consolidate files in the outbound queue into one file per neighbor node
622  * Step 2: delete any files in the outbound queue that were for neighbors who no longer exist.
623  */
624 void network_consolidate_spoolout(HashList *working_ignetcfg, HashList *the_netmap)
625 {
626         struct CitContext *CCC = CC;
627         IOBuffer IOB;
628         FDIOBuffer FDIO;
629         int d_namelen;
630         DIR *dp;
631         struct dirent *d;
632         struct dirent *filedir_entry;
633         const char *pch;
634         char spooloutfilename[PATH_MAX];
635         char filename[PATH_MAX];
636         const StrBuf *nexthop;
637         StrBuf *NextHop;
638         int i;
639         struct stat statbuf;
640         int nFailed = 0;
641         int d_type = 0;
642
643
644         /* Step 1: consolidate files in the outbound queue into one file per neighbor node */
645         d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 1);
646         if (d == NULL)  return;
647
648         dp = opendir(ctdl_netout_dir);
649         if (dp == NULL) {
650                 free(d);
651                 return;
652         }
653
654         NextHop = NewStrBuf();
655         memset(&IOB, 0, sizeof(IOBuffer));
656         memset(&FDIO, 0, sizeof(FDIOBuffer));
657         FDIO.IOB = &IOB;
658
659         while ((readdir_r(dp, d, &filedir_entry) == 0) &&
660                (filedir_entry != NULL))
661         {
662 #ifdef _DIRENT_HAVE_D_NAMLEN
663                 d_namelen = filedir_entry->d_namelen;
664
665 #else
666                 d_namelen = strlen(filedir_entry->d_name);
667 #endif
668
669 #ifdef _DIRENT_HAVE_D_TYPE
670                 d_type = filedir_entry->d_type;
671 #else
672                 d_type = DT_UNKNOWN;
673 #endif
674                 if (d_type == DT_DIR)
675                         continue;
676
677                 if ((d_namelen > 1) && filedir_entry->d_name[d_namelen - 1] == '~')
678                         continue; /* Ignore backup files... */
679
680                 if ((d_namelen == 1) && 
681                     (filedir_entry->d_name[0] == '.'))
682                         continue;
683
684                 if ((d_namelen == 2) && 
685                     (filedir_entry->d_name[0] == '.') &&
686                     (filedir_entry->d_name[1] == '.'))
687                         continue;
688
689                 pch = strchr(filedir_entry->d_name, '@');
690                 if (pch == NULL)
691                         continue;
692
693                 snprintf(filename, 
694                          sizeof filename,
695                          "%s/%s",
696                          ctdl_netout_dir,
697                          filedir_entry->d_name);
698
699                 StrBufPlain(NextHop,
700                             filedir_entry->d_name,
701                             pch - filedir_entry->d_name);
702
703                 snprintf(spooloutfilename,
704                          sizeof spooloutfilename,
705                          "%s/%s",
706                          ctdl_netout_dir,
707                          ChrPtr(NextHop));
708
709                 QN_syslog(LOG_DEBUG, "Consolidate %s to %s\n", filename, ChrPtr(NextHop));
710                 if (CtdlNetworkTalkingTo(SKEY(NextHop), NTT_CHECK)) {
711                         nFailed++;
712                         QN_syslog(LOG_DEBUG,
713                                   "Currently online with %s - skipping for now\n",
714                                   ChrPtr(NextHop)
715                                 );
716                 }
717                 else {
718                         size_t dsize;
719                         size_t fsize;
720                         int infd, outfd;
721                         const char *err = NULL;
722                         CtdlNetworkTalkingTo(SKEY(NextHop), NTT_ADD);
723
724                         infd = open(filename, O_RDONLY);
725                         if (infd == -1) {
726                                 nFailed++;
727                                 QN_syslog(LOG_ERR,
728                                           "failed to open %s for reading due to %s; skipping.\n",
729                                           filename, strerror(errno)
730                                         );
731                                 CtdlNetworkTalkingTo(SKEY(NextHop), NTT_REMOVE);
732                                 continue;                               
733                         }
734                         
735                         outfd = open(spooloutfilename,
736                                   O_EXCL|O_CREAT|O_NONBLOCK|O_WRONLY, 
737                                   S_IRUSR|S_IWUSR);
738                         if (outfd == -1)
739                         {
740                                 outfd = open(spooloutfilename,
741                                              O_EXCL|O_NONBLOCK|O_WRONLY, 
742                                              S_IRUSR | S_IWUSR);
743                         }
744                         if (outfd == -1) {
745                                 nFailed++;
746                                 QN_syslog(LOG_ERR,
747                                           "failed to open %s for reading due to %s; skipping.\n",
748                                           spooloutfilename, strerror(errno)
749                                         );
750                                 close(infd);
751                                 CtdlNetworkTalkingTo(SKEY(NextHop), NTT_REMOVE);
752                                 continue;
753                         }
754
755                         dsize = lseek(outfd, 0, SEEK_END);
756                         lseek(outfd, -dsize, SEEK_SET);
757
758                         fstat(infd, &statbuf);
759                         fsize = statbuf.st_size;
760 /*
761                         fsize = lseek(infd, 0, SEEK_END);
762 */                      
763                         IOB.fd = infd;
764                         FDIOBufferInit(&FDIO, &IOB, outfd, fsize + dsize);
765                         FDIO.ChunkSendRemain = fsize;
766                         FDIO.TotalSentAlready = dsize;
767                         err = NULL;
768                         errno = 0;
769                         do {} while ((FileMoveChunked(&FDIO, &err) > 0) && (err == NULL));
770                         if (err == NULL) {
771                                 unlink(filename);
772                         }
773                         else {
774                                 nFailed++;
775                                 QN_syslog(LOG_ERR,
776                                           "failed to append to %s [%s]; rolling back..\n",
777                                           spooloutfilename, strerror(errno)
778                                         );
779                                 /* whoops partial append?? truncate spooloutfilename again! */
780                                 ftruncate(outfd, dsize);
781                         }
782                         FDIOBufferDelete(&FDIO);
783                         close(infd);
784                         close(outfd);
785                         CtdlNetworkTalkingTo(SKEY(NextHop), NTT_REMOVE);
786                 }
787         }
788         closedir(dp);
789
790         if (nFailed > 0) {
791                 FreeStrBuf(&NextHop);
792                 QN_syslog(LOG_INFO,
793                           "skipping Spoolcleanup because of %d files unprocessed.\n",
794                           nFailed
795                         );
796
797                 return;
798         }
799
800         /* Step 2: delete any files in the outbound queue that were for neighbors who no longer exist */
801         dp = opendir(ctdl_netout_dir);
802         if (dp == NULL) {
803                 FreeStrBuf(&NextHop);
804                 free(d);
805                 return;
806         }
807
808         while ((readdir_r(dp, d, &filedir_entry) == 0) &&
809                (filedir_entry != NULL))
810         {
811 #ifdef _DIRENT_HAVE_D_NAMLEN
812                 d_namelen = filedir_entry->d_namelen;
813
814 #else
815                 d_namelen = strlen(filedir_entry->d_name);
816 #endif
817
818 #ifdef _DIRENT_HAVE_D_TYPE
819                 d_type = filedir_entry->d_type;
820 #else
821                 d_type = DT_UNKNOWN;
822 #endif
823                 if (d_type == DT_DIR)
824                         continue;
825
826                 if ((d_namelen == 1) && 
827                     (filedir_entry->d_name[0] == '.'))
828                         continue;
829
830                 if ((d_namelen == 2) && 
831                     (filedir_entry->d_name[0] == '.') &&
832                     (filedir_entry->d_name[1] == '.'))
833                         continue;
834
835                 pch = strchr(filedir_entry->d_name, '@');
836                 if (pch == NULL) /* no @ in name? consolidated file. */
837                         continue;
838
839                 StrBufPlain(NextHop,
840                             filedir_entry->d_name,
841                             pch - filedir_entry->d_name);
842
843                 snprintf(filename, 
844                         sizeof filename,
845                         "%s/%s",
846                         ctdl_netout_dir,
847                         filedir_entry->d_name
848                 );
849
850                 i = CtdlIsValidNode(&nexthop,
851                                     NULL,
852                                     NextHop,
853                                     working_ignetcfg,
854                                     the_netmap);
855         
856                 if ( (i != 0) || (StrLength(nexthop) > 0) ) {
857                         unlink(filename);
858                 }
859         }
860         FreeStrBuf(&NextHop);
861         free(d);
862         closedir(dp);
863 }
864
865
866
867
868 /*
869  * It's ok if these directories already exist.  Just fail silently.
870  */
871 void create_spool_dirs(void) {
872         if ((mkdir(ctdl_spool_dir, 0700) != 0) && (errno != EEXIST))
873                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_spool_dir, strerror(errno));
874         if (chown(ctdl_spool_dir, CTDLUID, (-1)) != 0)
875                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_spool_dir, strerror(errno));
876         if ((mkdir(ctdl_netin_dir, 0700) != 0) && (errno != EEXIST))
877                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_netin_dir, strerror(errno));
878         if (chown(ctdl_netin_dir, CTDLUID, (-1)) != 0)
879                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_netin_dir, strerror(errno));
880         if ((mkdir(ctdl_nettmp_dir, 0700) != 0) && (errno != EEXIST))
881                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_nettmp_dir, strerror(errno));
882         if (chown(ctdl_nettmp_dir, CTDLUID, (-1)) != 0)
883                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_nettmp_dir, strerror(errno));
884         if ((mkdir(ctdl_netout_dir, 0700) != 0) && (errno != EEXIST))
885                 syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_netout_dir, strerror(errno));
886         if (chown(ctdl_netout_dir, CTDLUID, (-1)) != 0)
887                 syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_netout_dir, strerror(errno));
888 }
889
890 /*
891  * Module entry point
892  */
893 CTDL_MODULE_INIT(network_spool)
894 {
895         if (!threading)
896         {
897                 CtdlREGISTERRoomCfgType(subpending,       ParseSubPendingLine,   0, 5, SerializeGeneric,  DeleteGenericCfgLine); /// todo: move this to mailinglist manager
898                 CtdlREGISTERRoomCfgType(unsubpending,     ParseUnSubPendingLine, 0, 4, SerializeGeneric,  DeleteGenericCfgLine); /// todo: move this to mailinglist manager
899                 CtdlREGISTERRoomCfgType(lastsent,         ParseLastSent,         1, 1, SerializeLastSent, DeleteLastSent);
900                 CtdlREGISTERRoomCfgType(ignet_push_share, ParseGeneric,          0, 2, SerializeGeneric,  DeleteGenericCfgLine); // [remotenode|remoteroomname (optional)]// todo: move this to the ignet client
901                 CtdlREGISTERRoomCfgType(listrecp,         ParseGeneric,          0, 1, SerializeGeneric,  DeleteGenericCfgLine);
902                 CtdlREGISTERRoomCfgType(digestrecp,       ParseGeneric,          0, 1, SerializeGeneric,  DeleteGenericCfgLine);
903                 CtdlREGISTERRoomCfgType(participate,      ParseGeneric,          0, 1, SerializeGeneric,  DeleteGenericCfgLine);
904                 CtdlREGISTERRoomCfgType(roommailalias,    ParseRoomAlias,        0, 1, SerializeGeneric,  DeleteGenericCfgLine);
905
906                 create_spool_dirs();
907 //////todo              CtdlRegisterCleanupHook(destroy_network_queue_room);
908         }
909         return "network_spool";
910 }