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