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