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