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