Networker AsyncIO Rewrite
[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 typedef enum _eNWCState {
92         eeGreating,
93         eAuth,
94         eNDOP,
95         eREAD,
96         eReadBLOB,
97         eCLOS,
98         eNUOP,
99         eWRIT,
100         eWriteBLOB,
101         eUCLS,
102         eQUIT
103 }eNWCState;
104
105
106 typedef struct _async_networker {
107         AsyncIO IO;
108         DNSQueryParts HostLookup;
109         eNWCState State;
110         int fd;
111         long n;
112         FILE *TmpFile;
113         long bytes_received;
114         StrBuf *SpoolFileName;
115         StrBuf *tempFileName;
116         StrBuf *node;
117         StrBuf *host;
118         StrBuf *port;
119         StrBuf *secret;
120         StrBuf          *Url;
121
122         long download_len;
123         long BlobReadSize;
124         long bytes_written;
125         long bytes_to_write;
126 } AsyncNetworker;
127
128 typedef eNextState(*NWClientHandler)(AsyncNetworker* NW);
129 eNextState nwc_get_one_host_ip(AsyncIO *IO);
130
131 eNextState nwc_connect_ip(AsyncIO *IO);
132
133 eNextState NWC_SendQUIT(AsyncNetworker *NW);
134
135
136
137 void DestroyNetworker(AsyncNetworker *NW)
138 {
139 }
140
141 #define NWC_DBG_SEND() syslog(LOG_DEBUG, "NW client[%ld]: > %s", NW->n, ChrPtr(NW->IO.SendBuf.Buf))
142 #define NWC_DBG_READ() syslog(LOG_DEBUG, "NW client[%ld]: < %s\n", NW->n, ChrPtr(NW->IO.IOBuf))
143 #define NWC_OK (strncasecmp(ChrPtr(NW->IO.IOBuf), "+OK", 3) == 0)
144
145 eNextState NWC_ReadGreeting(AsyncNetworker *NW)
146 {
147         char connected_to[SIZ];
148         NWC_DBG_READ();
149         /* Read the server greeting */
150         /* Check that the remote is who we think it is and warn the Aide if not */
151         extract_token (connected_to, ChrPtr(NW->IO.IOBuf), 1, ' ', sizeof connected_to);
152         if (strcmp(connected_to, ChrPtr(NW->node)) != 0)
153         {
154                 StrBufPrintf(NW->IO.ErrMsg,
155                              "Connected to node \"%s\" but I was expecting to connect to node \"%s\".",
156                              connected_to, ChrPtr(NW->node));
157                 syslog(LOG_ERR, "%s\n", ChrPtr(NW->IO.ErrMsg));
158                 CtdlAideMessage(ChrPtr(NW->IO.ErrMsg), "Network error");
159                 return eAbort;/// todo: aide message in anderer queue speichern
160         }
161         return eSendReply;
162 }
163
164 eNextState NWC_SendAuth(AsyncNetworker *NW)
165 {
166         /* We're talking to the correct node.  Now identify ourselves. */
167         StrBufPrintf(NW->IO.SendBuf.Buf, "NETP %s|%s\n", 
168                      config.c_nodename, 
169                      ChrPtr(NW->secret));
170         NWC_DBG_SEND();
171         return eSendReply;
172 }
173
174 eNextState NWC_ReadAuthReply(AsyncNetworker *NW)
175 {
176         NWC_DBG_READ();
177         if (ChrPtr(NW->IO.IOBuf)[0] == '2')
178         {
179                 return eSendReply;
180         }
181         else
182         {
183                 StrBufPrintf(NW->IO.ErrMsg,
184                              "Connected to node \"%s\" but my secret wasn't accurate.",
185                              ChrPtr(NW->node));
186                 syslog(LOG_ERR, "%s\n", ChrPtr(NW->IO.ErrMsg));
187                 CtdlAideMessage(ChrPtr(NW->IO.ErrMsg), "Network error");
188                 
189                 return eAbort;
190         }
191 }
192
193 eNextState NWC_SendNDOP(AsyncNetworker *NW)
194 {
195         NW->tempFileName = NewStrBuf();
196         NW->SpoolFileName = NewStrBuf();
197         StrBufPrintf(NW->tempFileName, 
198                      "%s/%s.%lx%x",
199                      ctdl_netin_dir,
200                      ChrPtr(NW->node),
201                      time(NULL),// TODO: get time from libev
202                      rand());
203         StrBufPrintf(NW->SpoolFileName, 
204                      "%s/%s.%lx%x",
205                      ctdl_nettmp_dir,
206                      ChrPtr(NW->node),
207                      time(NULL),// TODO: get time from libev
208                      rand());
209
210         /* We're talking to the correct node.  Now identify ourselves. */
211         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("NDOP\n"));
212         NWC_DBG_SEND();
213         return eSendReply;
214 }
215
216 eNextState NWC_ReadNDOPReply(AsyncNetworker *NW)
217 {
218         NWC_DBG_READ();
219         if (ChrPtr(NW->IO.IOBuf)[0] == '2')
220         {
221                 NW->download_len = atol (ChrPtr(NW->IO.IOBuf) + 4);
222                 syslog(LOG_DEBUG, "Expecting to transfer %ld bytes\n", NW->download_len);
223                 if (NW->download_len <= 0)
224                         NW->State = eNUOP - 1;
225                 return eSendReply;
226         }
227         else
228         {
229                 return eAbort;
230         }
231 }
232
233 eNextState NWC_SendREAD(AsyncNetworker *NW)
234 {
235         if (NW->bytes_received < NW->download_len)
236         {
237                 /*
238                  * If shutting down we can exit here and unlink the temp file.
239                  * this shouldn't loose us any messages.
240                  */
241                 if (server_shutting_down)
242                 {
243                         fclose(NW->TmpFile);
244                         unlink(ChrPtr(NW->tempFileName));
245                         return eAbort;
246                 }
247                 StrBufPrintf(NW->IO.SendBuf.Buf, "READ %ld|%ld\n",
248                              NW->bytes_received,
249                              ((NW->download_len - NW->bytes_received > IGNET_PACKET_SIZE)
250                               ? IGNET_PACKET_SIZE : 
251                               (NW->download_len - NW->bytes_received))
252                         );
253                 return eSendReply;
254
255
256
257         }
258         else {} // continue sending
259         return eSendReply;
260 }
261
262 eNextState NWC_ReadREADState(AsyncNetworker *NW)
263 {
264         NWC_DBG_READ();
265         if (ChrPtr(NW->IO.IOBuf)[0] == '6')
266         {
267                 NW->BlobReadSize = atol(ChrPtr(NW->IO.IOBuf)+4);
268 /// TODO                StrBufReadjustIOBuffer(NW->IO.RecvBuf, NW->BlobReadSize);
269                 return eReadPayload;
270         }
271         return eAbort;
272 }
273 eNextState NWC_ReadREADBlob(AsyncNetworker *NW)
274 {
275         fwrite(ChrPtr(NW->IO.RecvBuf.Buf), NW->BlobReadSize, 1, NW->TmpFile);
276         NW->bytes_received += NW->BlobReadSize;
277         /// FlushIOBuffer(NW->IO.RecvBuf); /// TODO
278         if (NW->bytes_received < NW->download_len)
279         {
280                 return eSendReply;/* now fetch next chunk*/
281         }
282         else 
283         {
284                 fclose(NW->TmpFile);
285                 
286                 if (link(ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)) != 0) {
287                         syslog(LOG_ALERT, 
288                                "Could not link %s to %s: %s\n",
289                                ChrPtr(NW->tempFileName), 
290                                ChrPtr(NW->SpoolFileName), 
291                                strerror(errno));
292                 }
293         
294                 unlink(ChrPtr(NW->tempFileName));
295                 return eSendReply; //// TODO: step forward.
296         }
297 }
298
299
300 eNextState NWC_SendCLOS(AsyncNetworker *NW)
301 {
302         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("CLOS\n"));
303         unlink(ChrPtr(NW->tempFileName));
304         return eReadMessage;
305 }
306
307 eNextState NWC_ReadCLOSReply(AsyncNetworker *NW)
308 {
309 /// todo
310         return eTerminateConnection;
311 }
312
313
314 eNextState NWC_SendNUOP(AsyncNetworker *NW)
315 {
316         struct stat statbuf;
317
318         StrBufPrintf(NW->tempFileName,
319                      "%s/%s",
320                      ctdl_netout_dir,
321                      ChrPtr(NW->node));
322         NW->fd = open(ChrPtr(NW->tempFileName), O_RDONLY);
323         if (NW->fd < 0) {
324                 if (errno != ENOENT) {
325                         syslog(LOG_CRIT,
326                                "cannot open %s: %s\n", 
327                                ChrPtr(NW->tempFileName), 
328                                strerror(errno));
329                 }
330                 NW->State = eQUIT;
331                 return NWC_SendQUIT(NW);
332         }
333
334         if (fstat(NW->fd, &statbuf) == -1) {
335                 syslog(9, "FSTAT FAILED %s [%s]--\n", 
336                        ChrPtr(NW->tempFileName), 
337                        strerror(errno));
338                 if (NW->fd > 0) close(NW->fd);
339                 return eAbort;
340         }
341         
342         NW->download_len = statbuf.st_size;
343         if (NW->download_len == 0) {
344                 syslog(LOG_DEBUG,
345                        "Nothing to send.\n");
346                 NW->State = eQUIT;
347                 return NWC_SendQUIT(NW);
348         }
349
350         NW->bytes_written = 0;
351
352         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("NUOP\n"));
353         return eSendReply;
354
355 }
356 eNextState NWC_ReadNUOPReply(AsyncNetworker *NW)
357 {
358         NWC_DBG_READ();
359 ///     if (ChrPtr(NW->IO.IOBuf)[0] == '2');;;; //// todo
360         return eReadMessage;
361 }
362
363 eNextState NWC_SendWRIT(AsyncNetworker *NW)
364 {
365         StrBufPrintf(NW->IO.SendBuf.Buf, "WRIT %ld\n", NW->bytes_to_write);
366
367         return eSendReply;
368 }
369 eNextState NWC_ReadWRITReply(AsyncNetworker *NW)
370 {
371         NWC_DBG_READ();
372         if (ChrPtr(NW->IO.IOBuf)[0] != '7')
373         {
374                 return eAbort;
375         }
376
377         NW->BlobReadSize = atol(ChrPtr(NW->IO.IOBuf)+4);
378         return eSendMore;
379 }
380
381 eNextState NWC_SendBlob(AsyncNetworker *NW)
382 {
383
384         ///                     bytes_to_write -= thisblock;
385         ///                     bytes_written += thisblock;
386
387         return eReadMessage;
388 }
389
390 eNextState NWC_SendUCLS(AsyncNetworker *NW)
391 {
392         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("UCLS 1\n"));
393         return eSendReply;
394
395 }
396 eNextState NWC_ReadUCLS(AsyncNetworker *NW)
397 {
398         NWC_DBG_READ();
399
400         syslog(LOG_NOTICE, "Sent %ld octets to <%s>\n", NW->bytes_written, ChrPtr(NW->node));
401 ///     syslog(LOG_DEBUG, "<%s\n", buf);
402         if (ChrPtr(NW->IO.IOBuf)[0] == '2') {
403                 syslog(LOG_DEBUG, "Removing <%s>\n", ChrPtr(NW->tempFileName));
404                 unlink(ChrPtr(NW->tempFileName));
405         }
406         return eSendReply;
407 }
408
409 eNextState NWC_SendQUIT(AsyncNetworker *NW)
410 {
411         StrBufPlain(NW->IO.SendBuf.Buf, HKEY("QUIT\n"));
412
413         network_talking_to(ChrPtr(NW->node), NTT_REMOVE);
414         return eSendReply;
415 }
416
417 eNextState NWC_ReadQUIT(AsyncNetworker *NW)
418 {
419         NWC_DBG_READ();
420
421         return eTerminateConnection;
422 }
423
424
425 NWClientHandler NWC_ReadHandlers[] = {
426         NWC_ReadGreeting,
427         NWC_ReadAuthReply,
428         NWC_ReadNDOPReply,
429         NWC_ReadREADState,
430         NWC_ReadREADBlob,
431         NWC_ReadCLOSReply,
432         NWC_ReadNUOPReply,
433         NWC_ReadWRITReply,
434         NULL,
435         NWC_ReadUCLS,
436         NWC_ReadQUIT
437 };
438
439 const long NWC_SendTimeouts[] = {
440         100,
441         100,
442         100,
443         100,
444         100,
445         100,
446         100,
447         100
448 };
449 const ConstStr NWC[] = {
450         {HKEY("Connection broken during ")},
451         {HKEY("Connection broken during ")},
452         {HKEY("Connection broken during ")},
453         {HKEY("Connection broken during ")},
454         {HKEY("Connection broken during ")},
455         {HKEY("Connection broken during ")},
456         {HKEY("Connection broken during ")},
457         {HKEY("Connection broken during ")}
458 };
459
460 NWClientHandler NWC_SendHandlers[] = {
461         NULL,
462         NWC_SendAuth,
463         NWC_SendNDOP,
464         NWC_SendREAD,
465         NULL,
466         NWC_SendCLOS,
467         NWC_SendNUOP,
468         NWC_SendWRIT,
469         NWC_SendBlob,
470         NWC_SendUCLS,
471         NWC_SendQUIT
472 };
473
474 const long NWC_ReadTimeouts[] = {
475         100,
476         100,
477         100,
478         100,
479         100,
480         100,
481         100,
482         100,
483         100,
484         100
485 };
486
487
488
489
490 eNextState nwc_get_one_host_ip_done(AsyncIO *IO)
491 {
492         AsyncNetworker *NW = IO->Data;
493         struct hostent *hostent;
494
495         QueryCbDone(IO);
496
497         hostent = NW->HostLookup.VParsedDNSReply;
498         if ((NW->HostLookup.DNSStatus == ARES_SUCCESS) && 
499             (hostent != NULL) ) {
500                 memset(&NW->IO.ConnectMe->Addr, 0, sizeof(struct in6_addr));
501                 if (NW->IO.ConnectMe->IPv6) {
502                         memcpy(&NW->IO.ConnectMe->Addr.sin6_addr.s6_addr, 
503                                &hostent->h_addr_list[0],
504                                sizeof(struct in6_addr));
505                         
506                         NW->IO.ConnectMe->Addr.sin6_family = hostent->h_addrtype;
507                         NW->IO.ConnectMe->Addr.sin6_port   = htons(atol(ChrPtr(NW->port)));//// TODO use the one from the URL.
508                 }
509                 else {
510                         struct sockaddr_in *addr = (struct sockaddr_in*) &NW->IO.ConnectMe->Addr;
511                         /* Bypass the ns lookup result like this: IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
512 //                      addr->sin_addr.s_addr = htonl((uint32_t)&hostent->h_addr_list[0]);
513                         memcpy(&addr->sin_addr.s_addr, 
514                                hostent->h_addr_list[0], 
515                                sizeof(uint32_t));
516                         
517                         addr->sin_family = hostent->h_addrtype;
518                         addr->sin_port   = htons(504);/// default citadel port
519                         
520                 }
521                 return nwc_connect_ip(IO);
522         }
523         else
524                 return eAbort;
525 }
526
527
528 eNextState nwc_get_one_host_ip(AsyncIO *IO)
529 {
530         AsyncNetworker *NW = IO->Data;
531         /* 
532          * here we start with the lookup of one host. it might be...
533          * - the relay host *sigh*
534          * - the direct hostname if there was no mx record
535          * - one of the mx'es
536          */ 
537
538         InitC_ares_dns(IO);
539
540         syslog(LOG_DEBUG, "NWC: %s\n", __FUNCTION__);
541
542         syslog(LOG_DEBUG, 
543                       "NWC client[%ld]: looking up %s-Record %s : %d ...\n", 
544                       NW->n, 
545                       (NW->IO.ConnectMe->IPv6)? "aaaa": "a",
546                       NW->IO.ConnectMe->Host, 
547                       NW->IO.ConnectMe->Port);
548
549         QueueQuery((NW->IO.ConnectMe->IPv6)? ns_t_aaaa : ns_t_a, 
550                    NW->IO.ConnectMe->Host, 
551                    &NW->IO, 
552                    &NW->HostLookup, 
553                    nwc_get_one_host_ip_done);
554         IO->NextState = eReadDNSReply;
555         return IO->NextState;
556 }
557 /**
558  * @brief lineread Handler; understands when to read more POP3 lines, and when this is a one-lined reply.
559  */
560 eReadState NWC_ReadServerStatus(AsyncIO *IO)
561 {
562         eReadState Finished = eBufferNotEmpty; 
563
564         switch (IO->NextState) {
565         case eSendDNSQuery:
566         case eReadDNSReply:
567         case eDBQuery:
568         case eConnect:
569         case eTerminateConnection:
570         case eAbort:
571                 Finished = eReadFail;
572                 break;
573         case eSendReply: 
574         case eSendMore:
575         case eReadMore:
576         case eReadMessage: 
577                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
578                 break;
579         case eReadPayload:
580                 break;
581         }
582         return Finished;
583 }
584
585
586
587 eNextState NWC_FailNetworkConnection(AsyncIO *IO)
588 {
589         return eTerminateConnection;
590 }
591
592
593 eNextState NWC_DispatchReadDone(AsyncIO *IO)
594 {
595         syslog(LOG_DEBUG, "NWC: %s\n", __FUNCTION__);
596         AsyncNetworker *NW = IO->Data;
597         eNextState rc;
598
599         rc = NWC_ReadHandlers[NW->State](NW);
600         if (rc != eReadMore)
601                 NW->State++;
602         ////NWCSetTimeout(rc, NW);
603         return rc;
604 }
605 eNextState NWC_DispatchWriteDone(AsyncIO *IO)
606 {
607         syslog(LOG_DEBUG, "NWC: %s\n", __FUNCTION__);
608         AsyncNetworker *NW = IO->Data;
609         eNextState rc;
610
611         rc = NWC_SendHandlers[NW->State](NW);
612         ////NWCSetTimeout(rc, NW);
613         return rc;
614 }
615
616 /*****************************************************************************/
617 /*                     Networker CLIENT ERROR CATCHERS                       */
618 /*****************************************************************************/
619 eNextState NWC_Terminate(AsyncIO *IO)
620 {
621         syslog(LOG_DEBUG, "Nw: %s\n", __FUNCTION__);
622 ///     FinalizeNetworker(IO); TODO
623         return eAbort;
624 }
625
626 eNextState NWC_Timeout(AsyncIO *IO)
627 {
628 //      AsyncNetworker *NW = IO->Data;
629
630         syslog(LOG_DEBUG, "NW: %s\n", __FUNCTION__);
631 //      StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State])); todo
632         return NWC_FailNetworkConnection(IO);
633 }
634 eNextState NWC_ConnFail(AsyncIO *IO)
635 {
636 ///     AsyncNetworker *NW = IO->Data;
637
638         syslog(LOG_DEBUG, "NW: %s\n", __FUNCTION__);
639 ////    StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State])); todo
640         return NWC_FailNetworkConnection(IO);
641 }
642 eNextState NWC_Shutdown(AsyncIO *IO)
643 {
644         syslog(LOG_DEBUG, "NW: %s\n", __FUNCTION__);
645 ////    pop3aggr *pMsg = IO->Data;
646
647         ////pMsg->MyQEntry->Status = 3;
648         ///StrBufPlain(pMsg->MyQEntry->StatusMessage, HKEY("server shutdown during message retrieval."));
649 ///     FinalizePOP3AggrRun(IO); todo
650         return eAbort;
651 }
652
653
654 eNextState nwc_connect_ip(AsyncIO *IO)
655 {
656         AsyncNetworker *NW = IO->Data;
657
658         syslog(LOG_DEBUG, "NW: %s\n", __FUNCTION__);
659         syslog(LOG_DEBUG, "network: polling <%s>\n", ChrPtr(NW->node));
660         syslog(LOG_NOTICE, "Connecting to <%s> at %s:%s\n", 
661                ChrPtr(NW->node), 
662                ChrPtr(NW->host),
663                ChrPtr(NW->port));
664         
665 ////    IO->ConnectMe = &NW->Pop3Host;
666         /*  Bypass the ns lookup result like this: IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
667
668         /////// SetConnectStatus(IO);
669
670         return InitEventIO(IO, NW, 100, 100, 1); /*
671                                                  NWC_ConnTimeout, 
672                                                  NWC_ReadTimeouts[0],
673                                                  1);*/
674 }
675
676 void RunNetworker(AsyncNetworker *NW)
677 {
678         CitContext *SubC;
679
680         ParseURL(&NW->IO.ConnectMe, NW->Url, 504);
681
682         NW->IO.Data          = NW;
683         NW->IO.SendDone      = NWC_DispatchWriteDone;
684         NW->IO.ReadDone      = NWC_DispatchReadDone;
685         NW->IO.Terminate     = NWC_Terminate;
686         NW->IO.LineReader    = NWC_ReadServerStatus;
687         NW->IO.ConnFail      = NWC_ConnFail;
688         NW->IO.Timeout       = NWC_Timeout;
689         NW->IO.ShutdownAbort = NWC_Shutdown;
690         
691         NW->IO.SendBuf.Buf   = NewStrBufPlain(NULL, 1024);
692         NW->IO.RecvBuf.Buf   = NewStrBufPlain(NULL, 1024);
693         NW->IO.IOBuf         = NewStrBuf();
694         
695         NW->IO.NextState     = eReadMessage;
696         SubC = CloneContext (&networker_client_CC);
697         SubC->session_specific_data = (char*) NW;
698         NW->IO.CitContext = SubC;
699
700         if (NW->IO.ConnectMe->IsIP) {
701                 QueueEventContext(&NW->IO,
702                                   nwc_connect_ip);
703         }
704         else { /* uneducated admin has chosen to add DNS to the equation... */
705                 QueueEventContext(&NW->IO,
706                                   nwc_get_one_host_ip);
707         }
708 }
709
710 /*
711  * Poll other Citadel nodes and transfer inbound/outbound network data.
712  * Set "full" to nonzero to force a poll of every node, or to zero to poll
713  * only nodes to which we have data to send.
714  */
715 void network_poll_other_citadel_nodes(int full_poll, char *working_ignetcfg)
716 {
717         AsyncNetworker *NW;
718         StrBuf *CfgData;
719         StrBuf *Line;
720         const char *lptr;
721         const char *CfgPtr;
722         int Done;
723         
724         int poll = 0;
725         
726         if ((working_ignetcfg == NULL) || (*working_ignetcfg == '\0')) {
727                 syslog(LOG_DEBUG, "network: no neighbor nodes are configured - not polling.\n");
728                 return;
729         }
730         CfgData = NewStrBufPlain(working_ignetcfg, -1);
731         Line = NewStrBufPlain(NULL, StrLength(CfgData));
732         Done = 0;
733         CfgPtr = NULL;
734         while (!Done)
735         {
736                 /* Use the string tokenizer to grab one line at a time */
737                 StrBufSipLine(Line, CfgData, &CfgPtr);
738                 Done = CfgPtr == StrBufNOTNULL;
739                 if (StrLength(Line) > 0)
740                 {
741                         if(server_shutting_down)
742                                 return;/* TODO free stuff*/
743                         lptr = NULL;
744                         poll = 0;
745                         NW = (AsyncNetworker*)malloc(sizeof(AsyncNetworker));
746                         memset(NW, 0, sizeof(AsyncNetworker));
747                         
748                         NW->node = NewStrBufPlain(NULL, StrLength(Line));
749                         NW->host = NewStrBufPlain(NULL, StrLength(Line));
750                         NW->port = NewStrBufPlain(NULL, StrLength(Line));
751                         NW->secret = NewStrBufPlain(NULL, StrLength(Line));
752                         
753                         StrBufExtract_NextToken(NW->node, Line, &lptr, '|');
754                         StrBufExtract_NextToken(NW->secret, Line, &lptr, '|');
755                         StrBufExtract_NextToken(NW->host, Line, &lptr, '|');
756                         StrBufExtract_NextToken(NW->port, Line, &lptr, '|');
757                         if ( (StrLength(NW->node) != 0) && 
758                              (StrLength(NW->secret) != 0) &&
759                              (StrLength(NW->host) != 0) &&
760                              (StrLength(NW->port) != 0))
761                         {
762                                 poll = full_poll;
763                                 if (poll == 0)
764                                 {
765                                         NW->SpoolFileName = NewStrBufPlain(ctdl_netout_dir, -1);
766                                         StrBufAppendBufPlain(NW->SpoolFileName, HKEY("/"), 0);
767                                         StrBufAppendBuf(NW->SpoolFileName, NW->node, 0);
768                                         if (access(ChrPtr(NW->SpoolFileName), R_OK) == 0) {
769                                                 poll = 1;
770                                         }
771                                 }
772                         }
773                         if (poll) {
774                                 NW->Url = NewStrBufPlain(NULL, StrLength(Line));
775                                 StrBufPrintf(NW->Url, "citadel://:%s@%s:%s", 
776                                              ChrPtr(NW->secret),
777                                              ChrPtr(NW->host),
778                                              ChrPtr(NW->port));
779                                 if (!network_talking_to(ChrPtr(NW->node), NTT_CHECK))
780                                 {
781                                         network_talking_to(ChrPtr(NW->node), NTT_ADD);
782                                         RunNetworker(NW);
783                                         continue;
784                                 }
785                         }
786                         DestroyNetworker(NW);
787                 }
788         }
789
790 }
791
792
793 void network_do_clientqueue(void)
794 {
795         char *working_ignetcfg;
796         int full_processing = 1;
797         static time_t last_run = 0L;
798
799         /*
800          * Run the full set of processing tasks no more frequently
801          * than once every n seconds
802          */
803         if ( (time(NULL) - last_run) < config.c_net_freq ) {
804                 full_processing = 0;
805                 syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n",
806                         config.c_net_freq - (time(NULL)- last_run)
807                 );
808         }
809
810         working_ignetcfg = load_working_ignetcfg();
811         /*
812          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
813          * then we poll everyone.  Otherwise we only poll nodes we have stuff
814          * to send to.
815          */
816         network_poll_other_citadel_nodes(full_processing, working_ignetcfg);
817         if (working_ignetcfg)
818                 free(working_ignetcfg);
819 }
820
821
822
823 /*
824  * Module entry point
825  */
826 CTDL_MODULE_INIT(network_client)
827 {
828         if (!threading)
829         {
830                 CtdlFillSystemContext(&networker_client_CC, "CitNetworker");
831                 
832                 CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER);
833         }
834         return "network_client";
835 }