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