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