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