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