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