Remove global variables; replace by stack passing.
[citadel.git] / citadel / modules / network / serv_networkclient.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2011 by the citadel.org team
6  *
7  *  This program is open source software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
22  * This is a fairly high-level type of critical section.  It ensures that no
23  * two threads work on the netconfigs files at the same time.  Since we do
24  * so many things inside these, here are the rules:
25  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
26  *  2. Do *not* perform any I/O with the client during these sections.
27  *
28  */
29
30
31 #include "sysdep.h"
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <stdio.h>
35 #include <fcntl.h>
36 #include <ctype.h>
37 #include <signal.h>
38 #include <pwd.h>
39 #include <errno.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <dirent.h>
43 #if TIME_WITH_SYS_TIME
44 # include <sys/time.h>
45 # include <time.h>
46 #else
47 # if HAVE_SYS_TIME_H
48 #  include <sys/time.h>
49 # else
50 #  include <time.h>
51 # endif
52 #endif
53 #ifdef HAVE_SYSCALL_H
54 # include <syscall.h>
55 #else 
56 # if HAVE_SYS_SYSCALL_H
57 #  include <sys/syscall.h>
58 # endif
59 #endif
60
61 #include <sys/wait.h>
62 #include <string.h>
63 #include <limits.h>
64 #include <libcitadel.h>
65 #include "citadel.h"
66 #include "server.h"
67 #include "citserver.h"
68 #include "support.h"
69 #include "config.h"
70 #include "user_ops.h"
71 #include "database.h"
72 #include "msgbase.h"
73 #include "internet_addressing.h"
74 #include "serv_network.h"
75 #include "clientsocket.h"
76 #include "file_ops.h"
77 #include "citadel_dirs.h"
78 #include "threads.h"
79
80 #ifndef HAVE_SNPRINTF
81 #include "snprintf.h"
82 #endif
83
84 #include "context.h"
85
86 #include "netconfig.h"
87 #include "ctdl_module.h"
88
89
90 /*
91  * receive network spool from the remote system
92  */
93 void receive_spool(int *sock, char *remote_nodename) {
94         int download_len = 0L;
95         int bytes_received = 0L;
96         char buf[SIZ];
97         char tempfilename[PATH_MAX];
98         char permfilename[PATH_MAX];
99         int plen;
100         FILE *fp;
101
102         snprintf(tempfilename, 
103                 sizeof tempfilename, 
104                 "%s/%s.%lx%x",
105                 ctdl_nettmp_dir,
106                 remote_nodename, 
107                 time(NULL),
108                 rand()
109         );
110
111         snprintf(permfilename, 
112                 sizeof permfilename, 
113                 "%s/%s.%lx%x",
114                 ctdl_netin_dir,
115                 remote_nodename, 
116                 time(NULL),
117                 rand()
118         );
119
120         if (sock_puts(sock, "NDOP") < 0) return;
121         if (sock_getln(sock, buf, sizeof buf) < 0) return;
122         syslog(LOG_DEBUG, "<%s\n", buf);
123         if (buf[0] != '2') {
124                 return;
125         }
126
127         download_len = extract_long(&buf[4], 0);
128         if (download_len <= 0) {
129                 return;
130         }
131
132         bytes_received = 0L;
133         fp = fopen(tempfilename, "w");
134         if (fp == NULL) {
135                 syslog(LOG_CRIT, "Cannot create %s: %s\n", tempfilename, strerror(errno));
136                 return;
137         }
138
139         syslog(LOG_DEBUG, "Expecting to transfer %d bytes\n", download_len);
140         while (bytes_received < download_len) {
141                 /*
142                  * If shutting down we can exit here and unlink the temp file.
143                  * this shouldn't loose us any messages.
144                  */
145                 if (server_shutting_down)
146                 {
147                         fclose(fp);
148                         unlink(tempfilename);
149                         return;
150                 }
151                 snprintf(buf, sizeof buf, "READ %d|%d",
152                          bytes_received,
153                          ((download_len - bytes_received > IGNET_PACKET_SIZE)
154                           ? IGNET_PACKET_SIZE : (download_len - bytes_received))
155                 );
156                 
157                 if (sock_puts(sock, buf) < 0) {
158                         fclose(fp);
159                         unlink(tempfilename);
160                         return;
161                 }
162                 if (sock_getln(sock, buf, sizeof buf) < 0) {
163                         fclose(fp);
164                         unlink(tempfilename);
165                         return;
166                 }
167                 
168                 if (buf[0] == '6') {
169                         plen = extract_int(&buf[4], 0);
170                         StrBuf *pbuf = NewStrBuf();
171                         if (socket_read_blob(sock, pbuf, plen, CLIENT_TIMEOUT) != plen) {
172                                 syslog(LOG_INFO, "Short read from peer; aborting.\n");
173                                 fclose(fp);
174                                 unlink(tempfilename);
175                                 FreeStrBuf(&pbuf);
176                                 return;
177                         }
178                         fwrite(ChrPtr(pbuf), plen, 1, fp);
179                         bytes_received += plen;
180                         FreeStrBuf(&pbuf);
181                 }
182         }
183
184         fclose(fp);
185
186         /* Last chance for shutdown exit */
187         if (server_shutting_down)
188         {
189                 unlink(tempfilename);
190                 return;
191         }
192
193         if (sock_puts(sock, "CLOS") < 0) {
194                 unlink(tempfilename);
195                 return;
196         }
197
198         /*
199          * From here on we must complete or messages will get lost
200          */
201         if (sock_getln(sock, buf, sizeof buf) < 0) {
202                 unlink(tempfilename);
203                 return;
204         }
205
206         syslog(LOG_DEBUG, "%s\n", buf);
207
208         /*
209          * Now move the temp file to its permanent location.
210          */
211         if (link(tempfilename, permfilename) != 0) {
212                 syslog(LOG_ALERT, "Could not link %s to %s: %s\n",
213                         tempfilename, permfilename, strerror(errno)
214                 );
215         }
216         
217         unlink(tempfilename);
218 }
219
220
221
222 /*
223  * transmit network spool to the remote system
224  */
225 void transmit_spool(int *sock, char *remote_nodename)
226 {
227         char buf[SIZ];
228         char pbuf[4096];
229         long plen;
230         long bytes_to_write, thisblock, bytes_written;
231         int fd;
232         char sfname[128];
233
234         if (sock_puts(sock, "NUOP") < 0) return;
235         if (sock_getln(sock, buf, sizeof buf) < 0) return;
236         syslog(LOG_DEBUG, "<%s\n", buf);
237         if (buf[0] != '2') {
238                 return;
239         }
240
241         snprintf(sfname, sizeof sfname, 
242                 "%s/%s",
243                 ctdl_netout_dir,
244                 remote_nodename
245         );
246         fd = open(sfname, O_RDONLY);
247         if (fd < 0) {
248                 if (errno != ENOENT) {
249                         syslog(LOG_CRIT, "cannot open %s: %s\n", sfname, strerror(errno));
250                 }
251                 return;
252         }
253         bytes_written = 0;
254         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
255                 bytes_to_write = plen;
256                 while (bytes_to_write > 0L) {
257                         /* Exit if shutting down */
258                         if (server_shutting_down)
259                         {
260                                 close(fd);
261                                 return;
262                         }
263                         
264                         snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
265                         if (sock_puts(sock, buf) < 0) {
266                                 close(fd);
267                                 return;
268                         }
269                         if (sock_getln(sock, buf, sizeof buf) < 0) {
270                                 close(fd);
271                                 return;
272                         }
273                         thisblock = atol(&buf[4]);
274                         if (buf[0] == '7') {
275                                 if (sock_write(sock, pbuf, (int) thisblock) < 0) {
276                                         close(fd);
277                                         return;
278                                 }
279                                 bytes_to_write -= thisblock;
280                                 bytes_written += thisblock;
281                         } else {
282                                 goto ABORTUPL;
283                         }
284                 }
285         }
286
287 ABORTUPL:
288         close(fd);
289
290         /* Last chance for shutdown exit */
291         if(server_shutting_down)
292                 return;
293                 
294         if (sock_puts(sock, "UCLS 1") < 0) return;
295
296         /*
297          * From here on we must complete or messages will get lost
298          */
299         if (sock_getln(sock, buf, sizeof buf) < 0) return;
300         syslog(LOG_NOTICE, "Sent %ld octets to <%s>\n", bytes_written, remote_nodename);
301         syslog(LOG_DEBUG, "<%s\n", buf);
302         if (buf[0] == '2') {
303                 syslog(LOG_DEBUG, "Removing <%s>\n", sfname);
304                 unlink(sfname);
305         }
306 }
307
308
309 /*
310  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
311  */
312 void network_poll_node(char *node, char *secret, char *host, char *port) {
313         int sock;
314         char buf[SIZ];
315         char err_buf[SIZ];
316         char connected_to[SIZ];
317         CitContext *CCC=CC;
318
319         if (network_talking_to(node, NTT_CHECK)) return;
320         network_talking_to(node, NTT_ADD);
321         syslog(LOG_DEBUG, "network: polling <%s>\n", node);
322         syslog(LOG_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port);
323
324         sock = sock_connect(host, port);
325         if (sock < 0) {
326                 syslog(LOG_ERR, "Could not connect: %s\n", strerror(errno));
327                 network_talking_to(node, NTT_REMOVE);
328                 return;
329         }
330         
331         syslog(LOG_DEBUG, "Connected!\n");
332         CCC->SBuf.Buf = NewStrBuf();
333         CCC->sMigrateBuf = NewStrBuf();
334         CCC->SBuf.ReadWritePointer = NULL;
335
336         /* Read the server greeting */
337         if (sock_getln(&sock, buf, sizeof buf) < 0) goto bail;
338         syslog(LOG_DEBUG, ">%s\n", buf);
339
340         /* Check that the remote is who we think it is and warn the Aide if not */
341         extract_token (connected_to, buf, 1, ' ', sizeof connected_to);
342         if (strcmp(connected_to, node))
343         {
344                 snprintf(err_buf, sizeof(err_buf),
345                         "Connected to node \"%s\" but I was expecting to connect to node \"%s\".",
346                         connected_to, node
347                 );
348                 syslog(LOG_ERR, "%s\n", err_buf);
349                 CtdlAideMessage(err_buf, "Network error");
350         }
351         else {
352                 /* We're talking to the correct node.  Now identify ourselves. */
353                 snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
354                 syslog(LOG_DEBUG, "<%s\n", buf);
355                 if (sock_puts(&sock, buf) <0) goto bail;
356                 if (sock_getln(&sock, buf, sizeof buf) < 0) goto bail;
357                 syslog(LOG_DEBUG, ">%s\n", buf);
358                 if (buf[0] != '2') {
359                         goto bail;
360                 }
361         
362                 /* At this point we are authenticated. */
363                 if (!server_shutting_down)
364                         receive_spool(&sock, node);
365                 if (!server_shutting_down)
366                         transmit_spool(&sock, node);
367         }
368
369         sock_puts(&sock, "QUIT");
370 bail:   
371         FreeStrBuf(&CCC->SBuf.Buf);
372         FreeStrBuf(&CCC->sMigrateBuf);
373         if (sock != -1)
374                 sock_close(sock);
375         network_talking_to(node, NTT_REMOVE);
376 }
377
378
379
380 /*
381  * Poll other Citadel nodes and transfer inbound/outbound network data.
382  * Set "full" to nonzero to force a poll of every node, or to zero to poll
383  * only nodes to which we have data to send.
384  */
385 void network_poll_other_citadel_nodes(int full_poll, char *working_ignetcfg)
386 {
387         int i;
388         char linebuf[256];
389         char node[SIZ];
390         char host[256];
391         char port[256];
392         char secret[256];
393         int poll = 0;
394         char spoolfile[256];
395
396         if (working_ignetcfg == NULL) {
397                 syslog(LOG_DEBUG, "network: no neighbor nodes are configured - not polling.\n");
398                 return;
399         }
400
401         /* Use the string tokenizer to grab one line at a time */
402         for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
403                 if(server_shutting_down)
404                         return;
405                 extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
406                 extract_token(node, linebuf, 0, '|', sizeof node);
407                 extract_token(secret, linebuf, 1, '|', sizeof secret);
408                 extract_token(host, linebuf, 2, '|', sizeof host);
409                 extract_token(port, linebuf, 3, '|', sizeof port);
410                 if ( !IsEmptyStr(node) && !IsEmptyStr(secret) 
411                    && !IsEmptyStr(host) && !IsEmptyStr(port)) {
412                         poll = full_poll;
413                         if (poll == 0) {
414                                 snprintf(spoolfile, 
415                                          sizeof spoolfile,
416                                          "%s/%s",
417                                          ctdl_netout_dir, 
418                                          node
419                                 );
420                                 if (access(spoolfile, R_OK) == 0) {
421                                         poll = 1;
422                                 }
423                         }
424                         if (poll) {
425                                 network_poll_node(node, secret, host, port);
426                         }
427                 }
428         }
429
430 }
431
432
433 void network_do_clientqueue(void)
434 {
435         char *working_ignetcfg;
436         int full_processing = 1;
437         static time_t last_run = 0L;
438
439         /*
440          * Run the full set of processing tasks no more frequently
441          * than once every n seconds
442          */
443         if ( (time(NULL) - last_run) < config.c_net_freq ) {
444                 full_processing = 0;
445                 syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n",
446                         config.c_net_freq - (time(NULL)- last_run)
447                 );
448         }
449
450         working_ignetcfg = load_working_ignetcfg();
451         /*
452          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
453          * then we poll everyone.  Otherwise we only poll nodes we have stuff
454          * to send to.
455          */
456         network_poll_other_citadel_nodes(full_processing, working_ignetcfg);
457         if (working_ignetcfg)
458                 free(working_ignetcfg);
459 }
460
461
462
463 /*
464  * Module entry point
465  */
466 CTDL_MODULE_INIT(network_client)
467 {
468         if (!threading)
469         {
470                 CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER);
471         }
472         return "network_client";
473 }