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