Initial version of new room sharing poller. I don't really like this because it...
[citadel.git] / citadel / modules / networkclient / serv_networkclient.c
1 /*
2  * This module polls other Citadel servers for inter-site networking.
3  *
4  * Copyright (c) 2000-2017 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License, version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
15  * This is a fairly high-level type of critical section.  It ensures that no
16  * two threads work on the netconfigs files at the same time.  Since we do
17  * so many things inside these, here are the rules:
18  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
19  *  2. Do *not* perform any I/O with the client during these sections.
20  *
21  */
22
23
24 #include "sysdep.h"
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <ctype.h>
30 #include <signal.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <dirent.h>
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
38 # include <time.h>
39 #else
40 # if HAVE_SYS_TIME_H
41 #  include <sys/time.h>
42 # else
43 #  include <time.h>
44 # endif
45 #endif
46 #ifdef HAVE_SYSCALL_H
47 # include <syscall.h>
48 #else 
49 # if HAVE_SYS_SYSCALL_H
50 #  include <sys/syscall.h>
51 # endif
52 #endif
53
54 #include <sys/wait.h>
55 #include <string.h>
56 #include <limits.h>
57 #include <libcitadel.h>
58 #include "citadel.h"
59 #include "server.h"
60 #include "citserver.h"
61 #include "support.h"
62 #include "config.h"
63 #include "user_ops.h"
64 #include "database.h"
65 #include "msgbase.h"
66 #include "internet_addressing.h"
67 #include "clientsocket.h"
68 #include "citadel_dirs.h"
69 #include "threads.h"
70 #include "context.h"
71 #include "ctdl_module.h"
72
73 struct CitContext networker_client_CC;
74
75
76 /*
77  * Poll one Citadel node (the Citadel node name, host/ip, port number, and shared secret are supplied by the caller)
78  */
79 void network_poll_node(StrBuf *node, StrBuf *host, StrBuf *port, StrBuf *secret)
80 {
81         char buf[SIZ];
82         CC->SBuf.Buf = NewStrBuf();
83         CC->sMigrateBuf = NewStrBuf();
84         CC->SBuf.ReadWritePointer = NULL;
85         int bytes_read = 0;
86         int bytes_total = 0;
87         int this_block = 0;
88         StrBuf *SpoolFileName = NULL;
89
90         syslog(LOG_DEBUG, "netpoll: polling %s at %s:%s", ChrPtr(node), ChrPtr(host), ChrPtr(port));
91
92         int sock = sock_connect((char *)ChrPtr(host), (char *)ChrPtr(port));
93         if (sock < 0) {
94                 syslog(LOG_ERR, "%s: %s", ChrPtr(host), strerror(errno));
95                 return;
96         }
97
98         /* Read the server greeting */
99         if (sock_getln(&sock, buf, sizeof buf) < 0) {
100                 goto bail;
101         }
102
103         /* Check that the remote is who we think it is and warn the site admin if not */
104         if (strncmp(&buf[4], ChrPtr(node), StrLength(node))) {
105                 CtdlAideMessage(buf, "Connected to wrong node!");
106                 syslog(LOG_ERR, "netpoll: was expecting node <%s> but got %s", ChrPtr(node), buf);
107                 goto bail;
108         }
109
110         /* We're talking to the correct node.  Now identify ourselves. */
111         snprintf(buf, sizeof buf, "NETP %s|%s", CtdlGetConfigStr("c_nodename"), ChrPtr(secret));
112         sock_puts(&sock, buf);
113         if (sock_getln(&sock, buf, sizeof buf) < 0) {
114                 goto bail;
115         }
116         if (buf[0] != '2') {
117                 CtdlAideMessage(buf, "Could not authenticate to network peer");
118                 syslog(LOG_ERR, "netpoll: could not authenticate to <%s> : %s", ChrPtr(node), buf);
119                 goto bail;
120         }
121
122         /* Tell it we want to download anything headed our way. */
123         sock_puts(&sock, "NDOP");
124         if (sock_getln(&sock, buf, sizeof buf) < 0) {
125                 goto bail;
126         }
127         if (buf[0] != '2') {
128                 CtdlAideMessage(buf, "NDOP error");
129                 syslog(LOG_ERR, "netpoll: NDOP error talking to <%s> : %s", ChrPtr(node), buf);
130                 goto bail;
131         }
132
133         bytes_total = atoi(&buf[4]);
134         bytes_read = 0;
135
136         SpoolFileName = NewStrBuf();
137         StrBufPrintf(SpoolFileName,                     // Incoming packets get dropped into the "spoolin/" directory
138                 "%s/%s.%lx%x",
139                 ctdl_netin_dir,
140                 ChrPtr(node),
141                 time(NULL),
142                 rand()
143         );
144         StrBufStripSlashes(SpoolFileName, 1);
145
146         FILE *netinfp = fopen(ChrPtr(SpoolFileName), "w");
147         FreeStrBuf(&SpoolFileName);
148         if (!netinfp) {
149                 goto bail;
150         }
151
152         while (bytes_read < bytes_total) {
153                 snprintf(buf, sizeof buf, "READ %d|%d", bytes_read, bytes_total-bytes_read);
154                 sock_puts(&sock, buf);
155                 if (sock_getln(&sock, buf, sizeof buf) < 0) {
156                         fclose(netinfp);
157                         goto bail;
158                 }
159                 if (buf[0] == '6') {
160                         this_block = atoi(&buf[4]);
161
162                         // Use buffered reads to download the data from remote server
163                         StrBuf *ThisBlockBuf = NewStrBuf();
164                         int blen = socket_read_blob(&sock, ThisBlockBuf, this_block, 20);
165                         if (blen > 0) {
166                                 fwrite(ChrPtr(ThisBlockBuf), blen, 1, netinfp);
167                                 bytes_read += blen;
168                         }
169                         FreeStrBuf(&ThisBlockBuf);
170                         if (blen < this_block) {
171                                 syslog(LOG_DEBUG, "netpoll: got short block, ftn");
172                                 fclose(netinfp);
173                                 goto bail;
174                         }
175                 }
176         }
177
178         syslog(LOG_DEBUG, "netpoll: downloaded %d of %d bytes from %s", bytes_read, bytes_total, ChrPtr(node));
179
180         if (fclose(netinfp) == 0) {
181                 sock_puts(&sock, "CLOS");                               // CLOSing the download causes it to be deleted on the other node
182                 if (sock_getln(&sock, buf, sizeof buf) < 0) {
183                         goto bail;
184                 }
185         }
186
187         // Now get ready to send our network data to the other node.
188         SpoolFileName = NewStrBuf();
189         StrBufPrintf(SpoolFileName,                     // Outgoing packets come from the "spoolout/" directory
190                 "%s/%s",
191                 ctdl_netout_dir,
192                 ChrPtr(node)
193         );
194         FILE *netoutfp = fopen(ChrPtr(SpoolFileName), "w");
195         FreeStrBuf(&SpoolFileName);
196         if (!netoutfp) {
197                 goto bail;
198         }
199         fclose(netoutfp);
200         //unlink(netoutfp);
201
202 bail:   close(sock);
203         FreeStrBuf(&CC->SBuf.Buf);
204         FreeStrBuf(&CC->sMigrateBuf);
205 }
206
207
208 /*
209  * Poll other Citadel nodes and transfer inbound/outbound network data.
210  * Set "full" to nonzero to force a poll of every node, or to zero to poll
211  * only nodes to which we have data to send.
212  */
213 void network_poll_other_citadel_nodes(int full_poll, HashList *ignetcfg)
214 {
215         const char *key;
216         long len;
217         HashPos *Pos;
218         void *vCfg;
219         StrBuf *SpoolFileName;
220         
221         int poll = 0;
222         
223         if (GetCount(ignetcfg) ==0) {
224                 syslog(LOG_DEBUG, "netpoll: no neighbor nodes are configured - not polling");
225                 return;
226         }
227         become_session(&networker_client_CC);
228
229         SpoolFileName = NewStrBufPlain(ctdl_netout_dir, -1);
230
231         Pos = GetNewHashPos(ignetcfg, 0);
232
233         while (GetNextHashPos(ignetcfg, Pos, &len, &key, &vCfg))
234         {
235                 /* Use the string tokenizer to grab one line at a time */
236                 if (server_shutting_down) {
237                         return;
238                 }
239                 CtdlNodeConf *pNode = (CtdlNodeConf*) vCfg;
240                 poll = 0;
241                 
242                 StrBuf *node = NewStrBufDup(pNode->NodeName);
243                 StrBuf *host = NewStrBufDup(pNode->Host);
244                 StrBuf *port = NewStrBufDup(pNode->Port);
245                 StrBuf *secret = NewStrBufDup(pNode->Secret);
246                 
247                 if ( (StrLength(node) != 0) && 
248                      (StrLength(secret) != 0) &&
249                      (StrLength(host) != 0)
250                 ) {
251                         poll = full_poll;
252                         if (poll == 0)
253                         {
254                                 StrBufAppendBufPlain(SpoolFileName, HKEY("/"), 0);
255                                 StrBufAppendBuf(SpoolFileName, node, 0);
256                                 StrBufStripSlashes(SpoolFileName, 1);
257                                 
258                                 if (access(ChrPtr(SpoolFileName), R_OK) == 0) {
259                                         poll = 1;
260                                 }
261                         }
262                 }
263                 if (poll && (StrLength(host) > 0) && strcmp("0.0.0.0", ChrPtr(host))) {
264                         if (!CtdlNetworkTalkingTo(SKEY(node), NTT_CHECK)) {
265                                 network_poll_node(node, host, port, secret);
266                         }
267                 }
268                 FreeStrBuf(&node);
269                 FreeStrBuf(&host);
270                 FreeStrBuf(&secret);
271                 FreeStrBuf(&port);
272         }
273         FreeStrBuf(&SpoolFileName);
274         DeleteHashPos(&Pos);
275 }
276
277
278 void network_do_clientqueue(void)
279 {
280         HashList *working_ignetcfg;
281         int full_processing = 1;
282         static time_t last_run = 0L;
283
284         /*
285          * Run the full set of processing tasks no more frequently
286          * than once every n seconds
287          */
288         if ( (time(NULL) - last_run) < CtdlGetConfigLong("c_net_freq") )
289         {
290                 full_processing = 0;
291                 syslog(LOG_DEBUG, "netpoll: full processing in %ld seconds.",
292                         CtdlGetConfigLong("c_net_freq") - (time(NULL)- last_run)
293                 );
294         }
295
296         working_ignetcfg = CtdlLoadIgNetCfg();
297         /*
298          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
299          * then we poll everyone.  Otherwise we only poll nodes we have stuff
300          * to send to.
301          */
302         network_poll_other_citadel_nodes(full_processing, working_ignetcfg);
303         DeleteHash(&working_ignetcfg);
304 }
305
306 /*
307  * Module entry point
308  */
309 CTDL_MODULE_INIT(network_client)
310 {
311         if (!threading)
312         {
313                 CtdlFillSystemContext(&networker_client_CC, "CitNetworker");
314                 CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER, PRIO_SEND + 10);
315
316         }
317         return "networkclient";
318 }