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