2 * This module polls other Citadel servers for inter-site networking.
4 * Copyright (c) 2000-2017 by the citadel.org team
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.
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.
14 * Implementation note:
15 * I'm not really happy with this. It looks more like a Gen 1 poller
16 * than a Gen 3 poller, because libcurl (understandably) does not have
17 * support for Citadel protocol. In the not too distant future I would
18 * like to remove Citadel-to-Citadel protocol entirely, and replace it
33 #include <sys/types.h>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
48 # if HAVE_SYS_SYSCALL_H
49 # include <sys/syscall.h>
56 #include <libcitadel.h>
59 #include "citserver.h"
65 #include "internet_addressing.h"
66 #include "clientsocket.h"
67 #include "citadel_dirs.h"
70 #include "ctdl_module.h"
72 struct CitContext networker_client_CC;
76 * Poll one Citadel node (the Citadel node name, host/ip, port number, and shared secret are supplied by the caller)
78 void network_poll_node(StrBuf *node, StrBuf *host, StrBuf *port, StrBuf *secret)
81 CC->SBuf.Buf = NewStrBuf();
82 CC->sMigrateBuf = NewStrBuf();
83 CC->SBuf.ReadWritePointer = NULL;
87 StrBuf *SpoolFileName = NULL;
88 FILE *netoutfp = NULL;
90 syslog(LOG_DEBUG, "netpoll: polling %s at %s:%s", ChrPtr(node), ChrPtr(host), ChrPtr(port));
92 int sock = sock_connect((char *)ChrPtr(host), (char *)ChrPtr(port));
94 syslog(LOG_ERR, "%s: %s", ChrPtr(host), strerror(errno));
98 /* Read the server greeting */
99 if (sock_getln(&sock, buf, sizeof buf) < 0) {
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);
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) {
117 CtdlAideMessage(buf, "Could not authenticate to network peer");
118 syslog(LOG_ERR, "netpoll: could not authenticate to <%s> : %s", ChrPtr(node), buf);
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) {
128 CtdlAideMessage(buf, "NDOP error");
129 syslog(LOG_ERR, "netpoll: NDOP error talking to <%s> : %s", ChrPtr(node), buf);
133 bytes_total = atoi(&buf[4]);
136 SpoolFileName = NewStrBuf();
137 StrBufPrintf(SpoolFileName, // Incoming packets get dropped into the "spoolin/" directory
144 StrBufStripSlashes(SpoolFileName, 1);
146 FILE *netinfp = fopen(ChrPtr(SpoolFileName), "w");
147 FreeStrBuf(&SpoolFileName);
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) {
160 this_block = atoi(&buf[4]);
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);
166 fwrite(ChrPtr(ThisBlockBuf), blen, 1, netinfp);
169 FreeStrBuf(&ThisBlockBuf);
170 if (blen < this_block) {
171 syslog(LOG_DEBUG, "netpoll: got short block, ftn");
178 syslog(LOG_DEBUG, "netpoll: downloaded %d of %d bytes from %s", bytes_read, bytes_total, ChrPtr(node));
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) {
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
194 netoutfp = fopen(ChrPtr(SpoolFileName), "r");
199 /* Tell it we want to upload. */
200 sock_puts(&sock, "NUOP");
201 if (sock_getln(&sock, buf, sizeof buf) < 0) {
205 CtdlAideMessage(buf, "NUOP error");
206 syslog(LOG_ERR, "netpoll: NUOP error talking to <%s> : %s", ChrPtr(node), buf);
210 fseek(netoutfp, 0, SEEK_END);
211 long total_to_send = ftell(netoutfp);
214 syslog(LOG_DEBUG, "netpoll: I want to send %ld bytes to %s", total_to_send, ChrPtr(node));
216 while (bytes_sent < total_to_send) {
217 if (server_shutting_down) {
221 this_block = total_to_send - bytes_sent;
222 if (this_block > sizeof(buf)) {
223 this_block = sizeof(buf);
226 snprintf(buf, sizeof buf, "WRIT %d", this_block);
227 sock_puts(&sock, buf);
228 if (sock_getln(&sock, buf, sizeof buf) < 0) {
234 this_block = atol(&buf[4]);
235 fread(buf, this_block, 1, netoutfp);
236 if (sock_write(&sock, buf, this_block) != this_block) {
239 bytes_sent += this_block;
240 syslog(LOG_DEBUG, "netpoll: sent %ld of %ld bytes to %s", bytes_sent, total_to_send, ChrPtr(node));
243 /* Tell them we're done. */
244 sock_puts(&sock, "UCLS 1"); // UCLS 1 causes it to close *and delete* on the other node
245 if (sock_getln(&sock, buf, sizeof buf) < 0) {
249 unlink(ChrPtr(SpoolFileName));
253 if (SpoolFileName != NULL) FreeStrBuf(&SpoolFileName);
254 if (netoutfp != NULL) fclose(netoutfp);
255 FreeStrBuf(&CC->SBuf.Buf);
256 FreeStrBuf(&CC->sMigrateBuf);
261 * Poll other Citadel nodes and transfer inbound/outbound network data.
262 * Set "full" to nonzero to force a poll of every node, or to zero to poll
263 * only nodes to which we have data to send.
265 void network_poll_other_citadel_nodes(int full_poll, HashList *ignetcfg)
271 StrBuf *SpoolFileName;
275 if (GetCount(ignetcfg) ==0) {
276 syslog(LOG_DEBUG, "netpoll: no neighbor nodes are configured - not polling");
279 become_session(&networker_client_CC);
281 SpoolFileName = NewStrBufPlain(ctdl_netout_dir, -1);
283 Pos = GetNewHashPos(ignetcfg, 0);
285 while (GetNextHashPos(ignetcfg, Pos, &len, &key, &vCfg))
287 /* Use the string tokenizer to grab one line at a time */
288 if (server_shutting_down) {
291 CtdlNodeConf *pNode = (CtdlNodeConf*) vCfg;
294 StrBuf *node = NewStrBufDup(pNode->NodeName);
295 StrBuf *host = NewStrBufDup(pNode->Host);
296 StrBuf *port = NewStrBufDup(pNode->Port);
297 StrBuf *secret = NewStrBufDup(pNode->Secret);
299 if ( (StrLength(node) != 0) &&
300 (StrLength(secret) != 0) &&
301 (StrLength(host) != 0)
306 StrBufAppendBufPlain(SpoolFileName, HKEY("/"), 0);
307 StrBufAppendBuf(SpoolFileName, node, 0);
308 StrBufStripSlashes(SpoolFileName, 1);
310 if (access(ChrPtr(SpoolFileName), R_OK) == 0) {
315 if (poll && (StrLength(host) > 0) && strcmp("0.0.0.0", ChrPtr(host))) {
316 if (!CtdlNetworkTalkingTo(SKEY(node), NTT_CHECK)) {
317 network_poll_node(node, host, port, secret);
325 FreeStrBuf(&SpoolFileName);
330 void network_do_clientqueue(void)
332 HashList *working_ignetcfg;
333 int full_processing = 1;
334 static time_t last_run = 0L;
337 * Run the full set of processing tasks no more frequently
338 * than once every n seconds
340 if ( (time(NULL) - last_run) < CtdlGetConfigLong("c_net_freq") )
343 syslog(LOG_DEBUG, "netpoll: full processing in %ld seconds.",
344 CtdlGetConfigLong("c_net_freq") - (time(NULL)- last_run)
348 working_ignetcfg = CtdlLoadIgNetCfg();
350 * Poll other Citadel nodes. Maybe. If "full_processing" is set
351 * then we poll everyone. Otherwise we only poll nodes we have stuff
354 network_poll_other_citadel_nodes(full_processing, working_ignetcfg);
355 DeleteHash(&working_ignetcfg);
362 CTDL_MODULE_INIT(network_client)
366 CtdlFillSystemContext(&networker_client_CC, "CitNetworker");
367 CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER, PRIO_SEND + 10);
370 return "networkclient";