SEEN-Database: refactor database interface for remembering whether we already aggrega...
[citadel.git] / citadel / modules / networkclient / serv_networkclient.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 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <signal.h>
32 #include <pwd.h>
33 #include <errno.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <dirent.h>
37 #if TIME_WITH_SYS_TIME
38 # include <sys/time.h>
39 # include <time.h>
40 #else
41 # if HAVE_SYS_TIME_H
42 #  include <sys/time.h>
43 # else
44 #  include <time.h>
45 # endif
46 #endif
47 #ifdef HAVE_SYSCALL_H
48 # include <syscall.h>
49 #else 
50 # if HAVE_SYS_SYSCALL_H
51 #  include <sys/syscall.h>
52 # endif
53 #endif
54
55 #include <sys/wait.h>
56 #include <string.h>
57 #include <limits.h>
58 #include <libcitadel.h>
59 #include "citadel.h"
60 #include "server.h"
61 #include "citserver.h"
62 #include "support.h"
63 #include "config.h"
64 #include "user_ops.h"
65 #include "database.h"
66 #include "msgbase.h"
67 #include "internet_addressing.h"
68 #include "clientsocket.h"
69 #include "file_ops.h"
70 #include "citadel_dirs.h"
71 #include "threads.h"
72 #include "context.h"
73 #include "ctdl_module.h"
74
75 struct CitContext networker_client_CC;
76
77 #define NODE ChrPtr(((AsyncNetworker*)IO->Data)->node)
78 #define N ((AsyncNetworker*)IO->Data)->n
79
80 int NetworkClientDebugEnabled = 0;
81
82 #define NCDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (NetworkClientDebugEnabled != 0))
83
84 #define EVN_syslog(LEVEL, FORMAT, ...)                                  \
85         NCDBGLOG(LEVEL) syslog(LEVEL,                                   \
86                                "IO[%ld]CC[%d]NW[%s][%ld]" FORMAT,       \
87                                IO->ID, CCID, NODE, N, __VA_ARGS__)
88
89 #define EVNM_syslog(LEVEL, FORMAT)                                      \
90         NCDBGLOG(LEVEL) syslog(LEVEL,                                   \
91                                "IO[%ld]CC[%d]NW[%s][%ld]" FORMAT,       \
92                                IO->ID, CCID, NODE, N)
93
94 #define EVNCS_syslog(LEVEL, FORMAT, ...) \
95         NCDBGLOG(LEVEL) syslog(LEVEL, "IO[%ld]NW[%s][%ld]" FORMAT,      \
96                                IO->ID, NODE, N, __VA_ARGS__)
97
98 #define EVNCSM_syslog(LEVEL, FORMAT) \
99         NCDBGLOG(LEVEL) syslog(LEVEL, "IO[%ld]NW[%s][%ld]" FORMAT,      \
100                                IO->ID, NODE, N)
101
102
103 typedef enum _eNWCState {
104         eGreating,
105         eAuth,
106         eNDOP,
107         eREAD,
108         eReadBLOB,
109         eCLOS,
110         eNUOP,
111         eWRIT,
112         eWriteBLOB,
113         eUCLS,
114         eQUIT
115 }eNWCState;
116
117 typedef enum _eNWCVState {
118         eNWCVSLookup,
119         eNWCVSConnecting,
120         eNWCVSConnFail,
121         eNWCVSGreating,
122         eNWCVSAuth,
123         eNWCVSAuthFailNTT,
124         eNWCVSAuthFail,
125         eNWCVSNDOP,
126         eNWCVSNDOPDone,
127         eNWCVSNUOP,
128         eNWCVSNUOPDone,
129         eNWCVSFail
130 }eNWCVState;
131
132 ConstStr NWCStateStr[] = {
133         {HKEY("Looking up Host")},
134         {HKEY("Connecting host")},
135         {HKEY("Failed to connect")},
136         {HKEY("Rread Greeting")},
137         {HKEY("Authenticating")},
138         {HKEY("Auth failed by NTT")},
139         {HKEY("Auth failed")},
140         {HKEY("Downloading")},
141         {HKEY("Downloading Success")},
142         {HKEY("Uploading Spoolfile")},
143         {HKEY("Uploading done")},
144         {HKEY("failed")}
145 };
146
147 void SetNWCState(AsyncIO *IO, eNWCVState State)
148 {
149         CitContext* CCC = IO->CitContext;
150         memcpy(CCC->cs_clientname, NWCStateStr[State].Key, NWCStateStr[State].len + 1);
151 }
152
153 typedef struct _async_networker {
154         AsyncIO IO;
155         DNSQueryParts HostLookup;
156         eNWCState State;
157         long n;
158         StrBuf *SpoolFileName;
159         StrBuf *tempFileName;
160         StrBuf *node;
161         StrBuf *host;
162         StrBuf *port;
163         StrBuf *secret;
164         StrBuf          *Url;
165 } AsyncNetworker;
166
167 typedef eNextState(*NWClientHandler)(AsyncNetworker* NW);
168 eNextState nwc_get_one_host_ip(AsyncIO *IO);
169
170 eNextState nwc_connect_ip(AsyncIO *IO);
171
172 eNextState NWC_SendQUIT(AsyncNetworker *NW);
173 eNextState NWC_DispatchWriteDone(AsyncIO *IO);
174
175 void DeleteNetworker(void *vptr)
176 {
177         AsyncNetworker *NW = (AsyncNetworker *)vptr;
178         FreeStrBuf(&NW->SpoolFileName);
179         FreeStrBuf(&NW->tempFileName);
180         FreeStrBuf(&NW->node);
181         FreeStrBuf(&NW->host);
182         FreeStrBuf(&NW->port);
183         FreeStrBuf(&NW->secret);
184         FreeStrBuf(&NW->Url);
185         FreeStrBuf(&NW->IO.ErrMsg);
186         FreeAsyncIOContents(&NW->IO);
187         if (NW->HostLookup.VParsedDNSReply != NULL) {
188                 NW->HostLookup.DNSReplyFree(NW->HostLookup.VParsedDNSReply);
189                 NW->HostLookup.VParsedDNSReply = NULL;
190         }
191         free(NW);
192 }
193
194 #define NWC_DBG_SEND() EVN_syslog(LOG_DEBUG, ": > %s", ChrPtr(NW->IO.SendBuf.Buf))
195 #define NWC_DBG_READ() EVN_syslog(LOG_DEBUG, ": < %s\n", ChrPtr(NW->IO.IOBuf))
196 #define NWC_OK (strncasecmp(ChrPtr(NW->IO.IOBuf), "+OK", 3) == 0)
197
198 eNextState SendFailureMessage(AsyncIO *IO)
199 {
200         AsyncNetworker *NW = IO->Data;
201         long lens[2];
202         const char *strs[2];
203
204         strs[0] = ChrPtr(NW->node);
205         lens[0] = StrLength(NW->node);
206         
207         strs[1] = ChrPtr(NW->IO.ErrMsg);
208         lens[1] = StrLength(NW->IO.ErrMsg);
209         CtdlAideFPMessage(
210                 ChrPtr(NW->IO.ErrMsg),
211                 "Networker error",
212                 2, strs, (long*) &lens,
213                 IO->Now,
214                 IO->ID, CCID);
215         
216         return eAbort;
217 }
218
219 eNextState FinalizeNetworker(AsyncIO *IO)
220 {
221         AsyncNetworker *NW = (AsyncNetworker *)IO->Data;
222
223         CtdlNetworkTalkingTo(SKEY(NW->node), NTT_REMOVE);
224
225         DeleteNetworker(IO->Data);
226         return eAbort;
227 }
228
229 eNextState NWC_ReadGreeting(AsyncNetworker *NW)
230 {
231         char connected_to[SIZ];
232         AsyncIO *IO = &NW->IO;
233         SetNWCState(IO, eNWCVSGreating);
234         NWC_DBG_READ();
235         /* Read the server greeting */
236         /* Check that the remote is who we think it is and warn the Aide if not */
237         extract_token (connected_to, ChrPtr(NW->IO.IOBuf), 1, ' ', sizeof connected_to);
238         if (strcmp(connected_to, ChrPtr(NW->node)) != 0)
239         {
240                 if (NW->IO.ErrMsg == NULL)
241                         NW->IO.ErrMsg = NewStrBuf();
242                 StrBufPrintf(NW->IO.ErrMsg,
243                              "Connected to node \"%s\" but I was expecting to connect to node \"%s\".",
244                              connected_to, ChrPtr(NW->node));
245                 EVN_syslog(LOG_ERR, "%s\n", ChrPtr(NW->IO.ErrMsg));
246                 StopClientWatchers(IO, 1);
247                 return QueueDBOperation(IO, SendFailureMessage);
248         }
249         return eSendReply;
250 }
251
252 eNextState NWC_SendAuth(AsyncNetworker *NW)
253 {
254         AsyncIO *IO = &NW->IO;
255         SetNWCState(IO, eNWCVSAuth);
256         /* We're talking to the correct node.  Now identify ourselves. */
257         StrBufPrintf(NW->IO.SendBuf.Buf, "NETP %s|%s\n", 
258                      config.c_nodename, 
259                      ChrPtr(NW->secret));
260         NWC_DBG_SEND();
261         return eSendReply;
262 }
263
264 eNextState NWC_ReadAuthReply(AsyncNetworker *NW)
265 {
266         AsyncIO *IO = &NW->IO;
267         NWC_DBG_READ();
268         if (ChrPtr(NW->IO.IOBuf)[0] == '2')
269         {
270                 return eSendReply;
271         }
272         else
273         {
274                 int Error = atol(ChrPtr(NW->IO.IOBuf));
275                 if (NW->IO.ErrMsg == NULL)
276                         NW->IO.ErrMsg = NewStrBuf();
277                 StrBufPrintf(NW->IO.ErrMsg,
278                              "Connected to node \"%s\" but my secret wasn't accurate.\nReason was:%s\n",
279                              ChrPtr(NW->node), ChrPtr(NW->IO.IOBuf) + 4);
280                 if (Error == 552) {
281                         SetNWCState(IO, eNWCVSAuthFailNTT);
282                         EVN_syslog(LOG_INFO,
283                                    "Already talking to %s; skipping this time.\n",
284                                    ChrPtr(NW->node));
285                         
286                 }
287                 else {
288                         SetNWCState(IO, eNWCVSAuthFailNTT);
289                         EVN_syslog(LOG_ERR, "%s\n", ChrPtr(NW->IO.ErrMsg));
290                         StopClientWatchers(IO, 1);
291                         return QueueDBOperation(IO, SendFailureMessage);
292                 }
293                 return eAbort;
294         }
295 }
296
297 eNextState NWC_SendNDOP(AsyncNetworker *NW)
298 {
299         AsyncIO *IO = &NW->IO;
300         SetNWCState(IO, eNWCVSNDOP);
301         NW->tempFileName = NewStrBuf();
302         NW->SpoolFileName = NewStrBuf();
303         StrBufPrintf(NW->SpoolFileName,
304                      "%s/%s.%lx%x",
305                      ctdl_netin_dir,
306                      ChrPtr(NW->node),
307                      time(NULL),// TODO: get time from libev
308                      rand());
309         StrBufStripSlashes(NW->SpoolFileName, 1);
310         StrBufPrintf(NW->tempFileName, 
311                      "%s/%s.%lx%x",
312                      ctdl_nettmp_dir,
313                      ChrPtr(NW->node),
314                      time(NULL),// TODO: get time from libev
315                      rand());
316         StrBufStripSlashes(NW->tempFileName, 1);
317         /* We're talking to the correct node.  Now identify ourselves. */
318         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("NDOP\n"));
319         NWC_DBG_SEND();
320         return eSendReply;
321 }
322
323 eNextState NWC_ReadNDOPReply(AsyncNetworker *NW)
324 {
325         AsyncIO *IO = &NW->IO;
326         int TotalSendSize;
327         NWC_DBG_READ();
328         if (ChrPtr(NW->IO.IOBuf)[0] == '2')
329         {
330
331                 NW->IO.IOB.TotalSentAlready = 0;
332                 TotalSendSize = atol (ChrPtr(NW->IO.IOBuf) + 4);
333                 EVN_syslog(LOG_DEBUG, "Expecting to transfer %d bytes\n", TotalSendSize);
334                 if (TotalSendSize <= 0) {
335                         NW->State = eNUOP - 1;
336                 }
337                 else {
338                         int fd;
339                         fd = open(ChrPtr(NW->tempFileName), 
340                                   O_EXCL|O_CREAT|O_NONBLOCK|O_WRONLY, 
341                                   S_IRUSR|S_IWUSR);
342                         if (fd < 0)
343                         {
344                                 SetNWCState(IO, eNWCVSFail);
345                                 EVN_syslog(LOG_CRIT,
346                                        "cannot open %s: %s\n", 
347                                        ChrPtr(NW->tempFileName), 
348                                        strerror(errno));
349
350                                 NW->State = eQUIT - 1;
351                                 return eAbort;
352                         }
353                         FDIOBufferInit(&NW->IO.IOB, &NW->IO.RecvBuf, fd, TotalSendSize);
354                 }
355                 return eSendReply;
356         }
357         else
358         {
359                 SetNWCState(IO, eNWCVSFail);
360                 return eAbort;
361         }
362 }
363
364 eNextState NWC_SendREAD(AsyncNetworker *NW)
365 {
366         AsyncIO *IO = &NW->IO;
367         eNextState rc;
368
369         if (NW->IO.IOB.TotalSentAlready < NW->IO.IOB.TotalSendSize)
370         {
371                 /*
372                  * If shutting down we can exit here and unlink the temp file.
373                  * this shouldn't loose us any messages.
374                  */
375                 if (server_shutting_down)
376                 {
377                         FDIOBufferDelete(&NW->IO.IOB);
378                         unlink(ChrPtr(NW->tempFileName));
379                         FDIOBufferDelete(&IO->IOB);
380                         SetNWCState(IO, eNWCVSFail);
381                         return eAbort;
382                 }
383                 StrBufPrintf(NW->IO.SendBuf.Buf, "READ "LOFF_T_FMT"|%ld\n",
384                              NW->IO.IOB.TotalSentAlready,
385                              NW->IO.IOB.TotalSendSize);
386 /*
387                              ((NW->IO.IOB.TotalSendSize - NW->IO.IOB.TotalSentAlready > IGNET_PACKET_SIZE)
388                               ? IGNET_PACKET_SIZE : 
389                               (NW->IO.IOB.TotalSendSize - NW->IO.IOB.TotalSentAlready))
390                         );
391 */
392                 NWC_DBG_SEND();
393                 return eSendReply;
394         }
395         else 
396         {
397                 NW->State = eCLOS;
398                 rc = NWC_DispatchWriteDone(&NW->IO);
399                 NWC_DBG_SEND();
400
401                 return rc;
402         }
403 }
404
405 eNextState NWC_ReadREADState(AsyncNetworker *NW)
406 {
407         AsyncIO *IO = &NW->IO;
408         NWC_DBG_READ();
409         if (ChrPtr(NW->IO.IOBuf)[0] == '6')
410         {
411                 NW->IO.IOB.ChunkSendRemain = 
412                         NW->IO.IOB.ChunkSize = atol(ChrPtr(NW->IO.IOBuf)+4);
413                 return eReadFile;
414         }
415         FDIOBufferDelete(&IO->IOB);
416         return eAbort;
417 }
418 eNextState NWC_ReadREADBlobDone(AsyncNetworker *NW);
419 eNextState NWC_ReadREADBlob(AsyncNetworker *NW)
420 {
421         eNextState rc;
422         AsyncIO *IO = &NW->IO;
423         NWC_DBG_READ();
424         if (NW->IO.IOB.TotalSendSize == NW->IO.IOB.TotalSentAlready)
425         {
426                 NW->State ++;
427
428                 FDIOBufferDelete(&NW->IO.IOB);
429
430                 if (link(ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)) != 0) {
431                         EVN_syslog(LOG_ALERT, 
432                                "Could not link %s to %s: %s\n",
433                                ChrPtr(NW->tempFileName), 
434                                ChrPtr(NW->SpoolFileName), 
435                                strerror(errno));
436                 }
437         
438                 unlink(ChrPtr(NW->tempFileName));
439                 rc = NWC_DispatchWriteDone(&NW->IO);
440                 NW->State --;
441                 return rc;
442         }
443         else {
444                 NW->State --;
445                 NW->IO.IOB.ChunkSendRemain = NW->IO.IOB.ChunkSize;
446                 return eSendReply; //NWC_DispatchWriteDone(&NW->IO);
447         }
448 }
449
450 eNextState NWC_ReadREADBlobDone(AsyncNetworker *NW)
451 {
452         eNextState rc;
453         AsyncIO *IO = &NW->IO;
454 /* we don't have any data to debug print here. */
455         if (NW->IO.IOB.TotalSentAlready >= NW->IO.IOB.TotalSendSize)
456         {
457                 NW->State ++;
458
459                 FDIOBufferDelete(&NW->IO.IOB);
460                 if (link(ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)) != 0) {
461                         EVN_syslog(LOG_ALERT, 
462                                "Could not link %s to %s: %s\n",
463                                ChrPtr(NW->tempFileName), 
464                                ChrPtr(NW->SpoolFileName), 
465                                strerror(errno));
466                 }
467         
468                 unlink(ChrPtr(NW->tempFileName));
469                 rc = NWC_DispatchWriteDone(&NW->IO);
470                 NW->State --;
471                 return rc;
472         }
473         else {
474                 NW->State --;
475                 NW->IO.IOB.ChunkSendRemain = NW->IO.IOB.ChunkSize;
476                 return NWC_DispatchWriteDone(&NW->IO);
477         }
478 }
479 eNextState NWC_SendCLOS(AsyncNetworker *NW)
480 {
481         AsyncIO *IO = &NW->IO;
482         SetNWCState(IO, eNWCVSNDOPDone);
483         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("CLOS\n"));
484         NWC_DBG_SEND();
485         return eSendReply;
486 }
487
488 eNextState NWC_ReadCLOSReply(AsyncNetworker *NW)
489 {
490         AsyncIO *IO = &NW->IO;
491         NWC_DBG_READ();
492         FDIOBufferDelete(&IO->IOB);
493         if (ChrPtr(NW->IO.IOBuf)[0] != '2')
494                 return eTerminateConnection;
495         return eSendReply;
496 }
497
498
499 eNextState NWC_SendNUOP(AsyncNetworker *NW)
500 {
501         AsyncIO *IO = &NW->IO;
502         eNextState rc;
503         long TotalSendSize;
504         struct stat statbuf;
505         int fd;
506
507         SetNWCState(IO, eNWCVSNUOP);
508         StrBufPrintf(NW->SpoolFileName,
509                      "%s/%s",
510                      ctdl_netout_dir,
511                      ChrPtr(NW->node));
512         StrBufStripSlashes(NW->SpoolFileName, 1);
513
514         fd = open(ChrPtr(NW->SpoolFileName), O_EXCL|O_NONBLOCK|O_RDONLY);
515         if (fd < 0) {
516                 if (errno != ENOENT) {
517                         EVN_syslog(LOG_CRIT,
518                                "cannot open %s: %s\n", 
519                                ChrPtr(NW->SpoolFileName), 
520                                strerror(errno));
521                 }
522                 NW->State = eQUIT;
523                 rc = NWC_SendQUIT(NW);
524                 NWC_DBG_SEND();
525                 return rc;
526         }
527
528         if (fstat(fd, &statbuf) == -1) {
529                 EVN_syslog(LOG_CRIT, "FSTAT FAILED %s [%s]--\n", 
530                            ChrPtr(NW->SpoolFileName), 
531                            strerror(errno));
532                 if (fd > 0) close(fd);
533                 return eAbort;
534         }
535         TotalSendSize = statbuf.st_size;
536         if (TotalSendSize == 0) {
537                 EVNM_syslog(LOG_DEBUG,
538                        "Nothing to send.\n");
539                 NW->State = eQUIT;
540                 rc = NWC_SendQUIT(NW);
541                 NWC_DBG_SEND();
542                 if (fd > 0) close(fd);
543                 return rc;
544         }
545         FDIOBufferInit(&NW->IO.IOB, &NW->IO.SendBuf, fd, TotalSendSize);
546
547         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("NUOP\n"));
548         NWC_DBG_SEND();
549         return eSendReply;
550
551 }
552 eNextState NWC_ReadNUOPReply(AsyncNetworker *NW)
553 {
554         AsyncIO *IO = &NW->IO;
555         NWC_DBG_READ();
556         if (ChrPtr(NW->IO.IOBuf)[0] != '2') {
557                 FDIOBufferDelete(&IO->IOB);
558                 return eAbort;
559         }
560         return eSendReply;
561 }
562
563 eNextState NWC_SendWRIT(AsyncNetworker *NW)
564 {
565         AsyncIO *IO = &NW->IO;
566         StrBufPrintf(NW->IO.SendBuf.Buf, "WRIT "LOFF_T_FMT"\n", 
567                      NW->IO.IOB.TotalSendSize - NW->IO.IOB.TotalSentAlready);
568         NWC_DBG_SEND();
569         return eSendReply;
570 }
571 eNextState NWC_ReadWRITReply(AsyncNetworker *NW)
572 {
573         AsyncIO *IO = &NW->IO;
574         NWC_DBG_READ();
575         if (ChrPtr(NW->IO.IOBuf)[0] != '7')
576         {
577                 FDIOBufferDelete(&IO->IOB);
578                 return eAbort;
579         }
580
581         NW->IO.IOB.ChunkSendRemain = 
582                 NW->IO.IOB.ChunkSize = atol(ChrPtr(NW->IO.IOBuf)+4);
583         return eSendFile;
584 }
585
586 eNextState NWC_SendBlobDone(AsyncNetworker *NW)
587 {
588         AsyncIO *IO = &NW->IO;
589         eNextState rc;
590         if (NW->IO.IOB.TotalSentAlready >= IO->IOB.TotalSendSize)
591         {
592                 NW->State ++;
593
594                 FDIOBufferDelete(&IO->IOB);
595                 rc =  NWC_DispatchWriteDone(IO);
596                 NW->State --;
597                 return rc;
598         }
599         else {
600                 NW->State --;
601                 IO->IOB.ChunkSendRemain = IO->IOB.ChunkSize;
602                 rc = NWC_DispatchWriteDone(IO);
603                 NW->State --;
604                 return rc;
605         }
606 }
607
608 eNextState NWC_SendUCLS(AsyncNetworker *NW)
609 {
610         AsyncIO *IO = &NW->IO;
611         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("UCLS 1\n"));
612         NWC_DBG_SEND();
613         return eSendReply;
614
615 }
616 eNextState NWC_ReadUCLS(AsyncNetworker *NW)
617 {
618         AsyncIO *IO = &NW->IO;
619         NWC_DBG_READ();
620
621         EVN_syslog(LOG_NOTICE, "Sent %ld octets to <%s>\n", NW->IO.IOB.ChunkSize, ChrPtr(NW->node));
622         if (ChrPtr(NW->IO.IOBuf)[0] == '2') {
623                 EVN_syslog(LOG_DEBUG, "Removing <%s>\n", ChrPtr(NW->SpoolFileName));
624                 unlink(ChrPtr(NW->SpoolFileName));
625         }
626         FDIOBufferDelete(&IO->IOB);
627         SetNWCState(IO, eNWCVSNUOPDone);
628         return eSendReply;
629 }
630
631 eNextState NWC_SendQUIT(AsyncNetworker *NW)
632 {
633         AsyncIO *IO = &NW->IO;
634         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("QUIT\n"));
635
636         NWC_DBG_SEND();
637         return eSendReply;
638 }
639
640 eNextState NWC_ReadQUIT(AsyncNetworker *NW)
641 {
642         AsyncIO *IO = &NW->IO;
643         NWC_DBG_READ();
644
645         return eAbort;
646 }
647
648
649 NWClientHandler NWC_ReadHandlers[] = {
650         NWC_ReadGreeting,
651         NWC_ReadAuthReply,
652         NWC_ReadNDOPReply,
653         NWC_ReadREADState,
654         NWC_ReadREADBlob,
655         NWC_ReadCLOSReply,
656         NWC_ReadNUOPReply,
657         NWC_ReadWRITReply,
658         NWC_SendBlobDone,
659         NWC_ReadUCLS,
660         NWC_ReadQUIT};
661
662 long NWC_ConnTimeout = 100;
663
664 const long NWC_SendTimeouts[] = {
665         100,
666         100,
667         100,
668         100,
669         100,
670         100,
671         100,
672         100
673 };
674 const ConstStr NWC[] = {
675         {HKEY("Connection broken during ")},
676         {HKEY("Connection broken during ")},
677         {HKEY("Connection broken during ")},
678         {HKEY("Connection broken during ")},
679         {HKEY("Connection broken during ")},
680         {HKEY("Connection broken during ")},
681         {HKEY("Connection broken during ")},
682         {HKEY("Connection broken during ")}
683 };
684
685 NWClientHandler NWC_SendHandlers[] = {
686         NULL,
687         NWC_SendAuth,
688         NWC_SendNDOP,
689         NWC_SendREAD,
690         NWC_ReadREADBlobDone,
691         NWC_SendCLOS,
692         NWC_SendNUOP,
693         NWC_SendWRIT,
694         NWC_SendBlobDone,
695         NWC_SendUCLS,
696         NWC_SendQUIT
697 };
698
699 const long NWC_ReadTimeouts[] = {
700         100,
701         100,
702         100,
703         100,
704         100,
705         100,
706         100,
707         100,
708         100,
709         100
710 };
711
712
713
714
715 eNextState nwc_get_one_host_ip_done(AsyncIO *IO)
716 {
717         AsyncNetworker *NW = IO->Data;
718         struct hostent *hostent;
719
720         QueryCbDone(IO);
721
722         hostent = NW->HostLookup.VParsedDNSReply;
723         if ((NW->HostLookup.DNSStatus == ARES_SUCCESS) && 
724             (hostent != NULL) ) {
725                 memset(&NW->IO.ConnectMe->Addr, 0, sizeof(struct in6_addr));
726                 if (NW->IO.ConnectMe->IPv6) {
727                         memcpy(&NW->IO.ConnectMe->Addr.sin6_addr.s6_addr, 
728                                &hostent->h_addr_list[0],
729                                sizeof(struct in6_addr));
730                         
731                         NW->IO.ConnectMe->Addr.sin6_family = hostent->h_addrtype;
732                         NW->IO.ConnectMe->Addr.sin6_port   = htons(atol(ChrPtr(NW->port)));//// TODO use the one from the URL.
733                 }
734                 else {
735                         struct sockaddr_in *addr = (struct sockaddr_in*) &NW->IO.ConnectMe->Addr;
736                         /* Bypass the ns lookup result like this: IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
737 //                      addr->sin_addr.s_addr = htonl((uint32_t)&hostent->h_addr_list[0]);
738                         memcpy(&addr->sin_addr.s_addr, 
739                                hostent->h_addr_list[0], 
740                                sizeof(uint32_t));
741                         
742                         addr->sin_family = hostent->h_addrtype;
743                         addr->sin_port   = htons(504);/// default citadel port
744                         
745                 }
746                 return nwc_connect_ip(IO);
747         }
748         else
749                 return eAbort;
750 }
751
752
753 eNextState nwc_get_one_host_ip(AsyncIO *IO)
754 {
755         AsyncNetworker *NW = IO->Data;
756         /* 
757          * here we start with the lookup of one host.
758          */ 
759
760         EVN_syslog(LOG_DEBUG, "NWC: %s\n", __FUNCTION__);
761
762         EVN_syslog(LOG_DEBUG, 
763                    "NWC client[%ld]: looking up %s-Record %s : %d ...\n", 
764                    NW->n, 
765                    (NW->IO.ConnectMe->IPv6)? "aaaa": "a",
766                    NW->IO.ConnectMe->Host, 
767                    NW->IO.ConnectMe->Port);
768
769         QueueQuery((NW->IO.ConnectMe->IPv6)? ns_t_aaaa : ns_t_a, 
770                    NW->IO.ConnectMe->Host, 
771                    &NW->IO, 
772                    &NW->HostLookup, 
773                    nwc_get_one_host_ip_done);
774         IO->NextState = eReadDNSReply;
775         return IO->NextState;
776 }
777 /**
778  * @brief lineread Handler; understands when to read more POP3 lines, and when this is a one-lined reply.
779  */
780 eReadState NWC_ReadServerStatus(AsyncIO *IO)
781 {
782 //      AsyncNetworker *NW = IO->Data;
783         eReadState Finished = eBufferNotEmpty; 
784
785         switch (IO->NextState) {
786         case eSendDNSQuery:
787         case eReadDNSReply:
788         case eDBQuery:
789         case eConnect:
790         case eTerminateConnection:
791         case eAbort:
792                 Finished = eReadFail;
793                 break;
794         case eSendReply: 
795         case eSendMore:
796         case eReadMore:
797         case eReadMessage: 
798                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
799                 break;
800         case eReadFile:
801         case eSendFile:
802         case eReadPayload:
803                 break;
804         }
805         return Finished;
806 }
807
808
809
810 eNextState NWC_FailNetworkConnection(AsyncIO *IO)
811 {
812         SetNWCState(IO, eNWCVSConnFail);
813         StopClientWatchers(IO, 1);
814         return QueueDBOperation(IO, SendFailureMessage);
815 }
816
817 void NWC_SetTimeout(eNextState NextTCPState, AsyncNetworker *NW)
818 {
819         AsyncIO *IO = &NW->IO;
820         double Timeout = 0.0;
821
822         EVN_syslog(LOG_DEBUG, "%s - %d\n", __FUNCTION__, NextTCPState);
823
824         switch (NextTCPState) {
825         case eSendReply:
826         case eSendMore:
827                 break;
828         case eReadFile:
829         case eReadMessage:
830                 Timeout = NWC_ReadTimeouts[NW->State];
831                 break;
832         case eReadPayload:
833                 Timeout = 100000;
834                 /* TODO!!! */
835                 break;
836         case eSendDNSQuery:
837         case eReadDNSReply:
838         case eConnect:
839         case eSendFile:
840 //TODO
841         case eTerminateConnection:
842         case eDBQuery:
843         case eAbort:
844         case eReadMore://// TODO
845                 return;
846         }
847         if (Timeout > 0) {
848                 EVN_syslog(LOG_DEBUG, 
849                            "%s - %d %f\n",
850                            __FUNCTION__,
851                            NextTCPState,
852                            Timeout);
853                 SetNextTimeout(&NW->IO, Timeout*100);
854         }
855 }
856
857
858 eNextState NWC_DispatchReadDone(AsyncIO *IO)
859 {
860         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
861         AsyncNetworker *NW = IO->Data;
862         eNextState rc;
863
864         rc = NWC_ReadHandlers[NW->State](NW);
865         if (rc != eReadMore)
866                 NW->State++;
867         NWC_SetTimeout(rc, NW);
868         return rc;
869 }
870 eNextState NWC_DispatchWriteDone(AsyncIO *IO)
871 {
872         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
873         AsyncNetworker *NW = IO->Data;
874         eNextState rc;
875
876         rc = NWC_SendHandlers[NW->State](NW);
877         NWC_SetTimeout(rc, NW);
878         return rc;
879 }
880
881 /*****************************************************************************/
882 /*                     Networker CLIENT ERROR CATCHERS                       */
883 /*****************************************************************************/
884 eNextState NWC_Terminate(AsyncIO *IO)
885 {
886         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
887         FinalizeNetworker(IO);
888         return eAbort;
889 }
890
891 eNextState NWC_TerminateDB(AsyncIO *IO)
892 {
893         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
894         FinalizeNetworker(IO);
895         return eAbort;
896 }
897
898 eNextState NWC_Timeout(AsyncIO *IO)
899 {
900         AsyncNetworker *NW = IO->Data;
901         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
902
903         if (NW->IO.ErrMsg == NULL)
904                 NW->IO.ErrMsg = NewStrBuf();
905         StrBufPrintf(NW->IO.ErrMsg, "Timeout while talking to %s \r\n", ChrPtr(NW->host));
906         return NWC_FailNetworkConnection(IO);
907 }
908 eNextState NWC_ConnFail(AsyncIO *IO)
909 {
910         AsyncNetworker *NW = IO->Data;
911
912         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
913         if (NW->IO.ErrMsg == NULL)
914                 NW->IO.ErrMsg = NewStrBuf();
915         StrBufPrintf(NW->IO.ErrMsg, "failed to connect %s \r\n", ChrPtr(NW->host));
916
917         return NWC_FailNetworkConnection(IO);
918 }
919 eNextState NWC_DNSFail(AsyncIO *IO)
920 {
921         AsyncNetworker *NW = IO->Data;
922
923         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
924         if (NW->IO.ErrMsg == NULL)
925                 NW->IO.ErrMsg = NewStrBuf();
926         StrBufPrintf(NW->IO.ErrMsg, "failed to look up %s \r\n", ChrPtr(NW->host));
927
928         return NWC_FailNetworkConnection(IO);
929 }
930 eNextState NWC_Shutdown(AsyncIO *IO)
931 {
932         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
933
934         FinalizeNetworker(IO);
935         return eAbort;
936 }
937
938
939 eNextState nwc_connect_ip(AsyncIO *IO)
940 {
941         AsyncNetworker *NW = IO->Data;
942
943         SetNWCState(&NW->IO, eNWCVSConnecting);
944         EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
945         EVN_syslog(LOG_NOTICE, "Connecting to <%s> at %s:%s\n", 
946                    ChrPtr(NW->node), 
947                    ChrPtr(NW->host),
948                    ChrPtr(NW->port));
949         
950         return EvConnectSock(IO,
951                              NWC_ConnTimeout,
952                              NWC_ReadTimeouts[0],
953                              1);
954 }
955
956 static int NetworkerCount = 0;
957 void RunNetworker(AsyncNetworker *NW)
958 {
959         NW->n = NetworkerCount++;
960         CtdlNetworkTalkingTo(SKEY(NW->node), NTT_ADD);
961         syslog(LOG_DEBUG, "NW[%s][%ld]: polling\n", ChrPtr(NW->node), NW->n);
962         ParseURL(&NW->IO.ConnectMe, NW->Url, 504);
963
964         InitIOStruct(&NW->IO,
965                      NW,
966                      eReadMessage,
967                      NWC_ReadServerStatus,
968                      NWC_DNSFail,
969                      NWC_DispatchWriteDone,
970                      NWC_DispatchReadDone,
971                      NWC_Terminate,
972                      NWC_TerminateDB,
973                      NWC_ConnFail,
974                      NWC_Timeout,
975                      NWC_Shutdown);
976
977         safestrncpy(((CitContext *)NW->IO.CitContext)->cs_host, 
978                     ChrPtr(NW->host),
979                     sizeof(((CitContext *)NW->IO.CitContext)->cs_host)); 
980
981         if (NW->IO.ConnectMe->IsIP) {
982                 SetNWCState(&NW->IO, eNWCVSLookup);
983                 QueueEventContext(&NW->IO,
984                                   nwc_connect_ip);
985         }
986         else { /* uneducated admin has chosen to add DNS to the equation... */
987                 SetNWCState(&NW->IO, eNWCVSConnecting);
988                 QueueEventContext(&NW->IO,
989                                   nwc_get_one_host_ip);
990         }
991 }
992
993 /*
994  * Poll other Citadel nodes and transfer inbound/outbound network data.
995  * Set "full" to nonzero to force a poll of every node, or to zero to poll
996  * only nodes to which we have data to send.
997  */
998 void network_poll_other_citadel_nodes(int full_poll, HashList *ignetcfg)
999 {
1000         const char *key;
1001         long len;
1002         HashPos *Pos;
1003         void *vCfg;
1004         AsyncNetworker *NW;
1005         StrBuf *SpoolFileName;
1006         
1007         int poll = 0;
1008         
1009         if (GetCount(ignetcfg) ==0) {
1010                 syslog(LOG_DEBUG, "network: no neighbor nodes are configured - not polling.\n");
1011                 return;
1012         }
1013         become_session(&networker_client_CC);
1014
1015         SpoolFileName = NewStrBufPlain(ctdl_netout_dir, -1);
1016
1017         Pos = GetNewHashPos(ignetcfg, 0);
1018
1019         while (GetNextHashPos(ignetcfg, Pos, &len, &key, &vCfg))
1020         {
1021                 /* Use the string tokenizer to grab one line at a time */
1022                 if(server_shutting_down)
1023                         return;/* TODO free stuff*/
1024                 CtdlNodeConf *pNode = (CtdlNodeConf*) vCfg;
1025                 poll = 0;
1026                 NW = (AsyncNetworker*)malloc(sizeof(AsyncNetworker));
1027                 memset(NW, 0, sizeof(AsyncNetworker));
1028                 
1029                 NW->node = NewStrBufDup(pNode->NodeName);
1030                 NW->host = NewStrBufDup(pNode->Host);
1031                 NW->port = NewStrBufDup(pNode->Port);
1032                 NW->secret = NewStrBufDup(pNode->Secret);
1033                 
1034                 if ( (StrLength(NW->node) != 0) && 
1035                      (StrLength(NW->secret) != 0) &&
1036                      (StrLength(NW->host) != 0) &&
1037                      (StrLength(NW->port) != 0))
1038                 {
1039                         poll = full_poll;
1040                         if (poll == 0)
1041                         {
1042                                 StrBufAppendBufPlain(SpoolFileName, HKEY("/"), 0);
1043                                 StrBufAppendBuf(SpoolFileName, NW->node, 0);
1044                                 StrBufStripSlashes(SpoolFileName, 1);
1045                                 
1046                                 if (access(ChrPtr(SpoolFileName), R_OK) == 0) {
1047                                         poll = 1;
1048                                 }
1049                         }
1050                 }
1051                 if (poll && 
1052                     (StrLength(NW->host) > 0) && 
1053                     strcmp("0.0.0.0", ChrPtr(NW->host)))
1054                 {
1055                         NW->Url = NewStrBuf();
1056                         StrBufPrintf(NW->Url, "citadel://%s@%s:%s", 
1057                                      ChrPtr(NW->secret),
1058                                      ChrPtr(NW->host),
1059                                      ChrPtr(NW->port));
1060                         if (!CtdlNetworkTalkingTo(SKEY(NW->node), NTT_CHECK))
1061                         {
1062                                 RunNetworker(NW);
1063                                 continue;
1064                         }
1065                 }
1066                 DeleteNetworker(NW);
1067         }
1068         FreeStrBuf(&SpoolFileName);
1069         DeleteHashPos(&Pos);
1070 }
1071
1072
1073 void network_do_clientqueue(void)
1074 {
1075         HashList *working_ignetcfg;
1076         int full_processing = 1;
1077         static time_t last_run = 0L;
1078
1079         /*
1080          * Run the full set of processing tasks no more frequently
1081          * than once every n seconds
1082          */
1083         if ( (time(NULL) - last_run) < config.c_net_freq ) {
1084                 full_processing = 0;
1085                 syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n",
1086                         config.c_net_freq - (time(NULL)- last_run)
1087                 );
1088         }
1089
1090         working_ignetcfg = CtdlLoadIgNetCfg();
1091         /*
1092          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
1093          * then we poll everyone.  Otherwise we only poll nodes we have stuff
1094          * to send to.
1095          */
1096         network_poll_other_citadel_nodes(full_processing, working_ignetcfg);
1097         DeleteHash(&working_ignetcfg);
1098 }
1099
1100 void LogDebugEnableNetworkClient(const int n)
1101 {
1102         NetworkClientDebugEnabled = n;
1103 }
1104 /*
1105  * Module entry point
1106  */
1107 CTDL_MODULE_INIT(network_client)
1108 {
1109         if (!threading)
1110         {
1111                 CtdlFillSystemContext(&networker_client_CC, "CitNetworker");
1112                 
1113                 CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER, PRIO_SEND + 10);
1114                 CtdlRegisterDebugFlagHook(HKEY("networkclient"), LogDebugEnableNetworkClient, &NetworkClientDebugEnabled);
1115
1116         }
1117         return "networkclient";
1118 }