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