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