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