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