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