Chop networker into handy bits; related functions now live in their own file.
[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  * receive network spool from the remote system
91  */
92 void receive_spool(int *sock, char *remote_nodename) {
93         int download_len = 0L;
94         int bytes_received = 0L;
95         char buf[SIZ];
96         char tempfilename[PATH_MAX];
97         char permfilename[PATH_MAX];
98         int plen;
99         FILE *fp;
100
101         snprintf(tempfilename, 
102                 sizeof tempfilename, 
103                 "%s/%s.%lx%x",
104                 ctdl_nettmp_dir,
105                 remote_nodename, 
106                 time(NULL),
107                 rand()
108         );
109
110         snprintf(permfilename, 
111                 sizeof permfilename, 
112                 "%s/%s.%lx%x",
113                 ctdl_netin_dir,
114                 remote_nodename, 
115                 time(NULL),
116                 rand()
117         );
118
119         if (sock_puts(sock, "NDOP") < 0) return;
120         if (sock_getln(sock, buf, sizeof buf) < 0) return;
121         syslog(LOG_DEBUG, "<%s\n", buf);
122         if (buf[0] != '2') {
123                 return;
124         }
125
126         download_len = extract_long(&buf[4], 0);
127         if (download_len <= 0) {
128                 return;
129         }
130
131         bytes_received = 0L;
132         fp = fopen(tempfilename, "w");
133         if (fp == NULL) {
134                 syslog(LOG_CRIT, "Cannot create %s: %s\n", tempfilename, strerror(errno));
135                 return;
136         }
137
138         syslog(LOG_DEBUG, "Expecting to transfer %d bytes\n", download_len);
139         while (bytes_received < download_len) {
140                 /*
141                  * If shutting down we can exit here and unlink the temp file.
142                  * this shouldn't loose us any messages.
143                  */
144                 if (server_shutting_down)
145                 {
146                         fclose(fp);
147                         unlink(tempfilename);
148                         return;
149                 }
150                 snprintf(buf, sizeof buf, "READ %d|%d",
151                          bytes_received,
152                          ((download_len - bytes_received > IGNET_PACKET_SIZE)
153                           ? IGNET_PACKET_SIZE : (download_len - bytes_received))
154                 );
155                 
156                 if (sock_puts(sock, buf) < 0) {
157                         fclose(fp);
158                         unlink(tempfilename);
159                         return;
160                 }
161                 if (sock_getln(sock, buf, sizeof buf) < 0) {
162                         fclose(fp);
163                         unlink(tempfilename);
164                         return;
165                 }
166                 
167                 if (buf[0] == '6') {
168                         plen = extract_int(&buf[4], 0);
169                         StrBuf *pbuf = NewStrBuf();
170                         if (socket_read_blob(sock, pbuf, plen, CLIENT_TIMEOUT) != plen) {
171                                 syslog(LOG_INFO, "Short read from peer; aborting.\n");
172                                 fclose(fp);
173                                 unlink(tempfilename);
174                                 FreeStrBuf(&pbuf);
175                                 return;
176                         }
177                         fwrite(ChrPtr(pbuf), plen, 1, fp);
178                         bytes_received += plen;
179                         FreeStrBuf(&pbuf);
180                 }
181         }
182
183         fclose(fp);
184
185         /* Last chance for shutdown exit */
186         if (server_shutting_down)
187         {
188                 unlink(tempfilename);
189                 return;
190         }
191
192         if (sock_puts(sock, "CLOS") < 0) {
193                 unlink(tempfilename);
194                 return;
195         }
196
197         /*
198          * From here on we must complete or messages will get lost
199          */
200         if (sock_getln(sock, buf, sizeof buf) < 0) {
201                 unlink(tempfilename);
202                 return;
203         }
204
205         syslog(LOG_DEBUG, "%s\n", buf);
206
207         /*
208          * Now move the temp file to its permanent location.
209          */
210         if (link(tempfilename, permfilename) != 0) {
211                 syslog(LOG_ALERT, "Could not link %s to %s: %s\n",
212                         tempfilename, permfilename, strerror(errno)
213                 );
214         }
215         
216         unlink(tempfilename);
217 }
218
219
220
221 /*
222  * transmit network spool to the remote system
223  */
224 void transmit_spool(int *sock, char *remote_nodename)
225 {
226         char buf[SIZ];
227         char pbuf[4096];
228         long plen;
229         long bytes_to_write, thisblock, bytes_written;
230         int fd;
231         char sfname[128];
232
233         if (sock_puts(sock, "NUOP") < 0) return;
234         if (sock_getln(sock, buf, sizeof buf) < 0) return;
235         syslog(LOG_DEBUG, "<%s\n", buf);
236         if (buf[0] != '2') {
237                 return;
238         }
239
240         snprintf(sfname, sizeof sfname, 
241                 "%s/%s",
242                 ctdl_netout_dir,
243                 remote_nodename
244         );
245         fd = open(sfname, O_RDONLY);
246         if (fd < 0) {
247                 if (errno != ENOENT) {
248                         syslog(LOG_CRIT, "cannot open %s: %s\n", sfname, strerror(errno));
249                 }
250                 return;
251         }
252         bytes_written = 0;
253         while (plen = (long) read(fd, pbuf, IGNET_PACKET_SIZE), plen > 0L) {
254                 bytes_to_write = plen;
255                 while (bytes_to_write > 0L) {
256                         /* Exit if shutting down */
257                         if (server_shutting_down)
258                         {
259                                 close(fd);
260                                 return;
261                         }
262                         
263                         snprintf(buf, sizeof buf, "WRIT %ld", bytes_to_write);
264                         if (sock_puts(sock, buf) < 0) {
265                                 close(fd);
266                                 return;
267                         }
268                         if (sock_getln(sock, buf, sizeof buf) < 0) {
269                                 close(fd);
270                                 return;
271                         }
272                         thisblock = atol(&buf[4]);
273                         if (buf[0] == '7') {
274                                 if (sock_write(sock, pbuf, (int) thisblock) < 0) {
275                                         close(fd);
276                                         return;
277                                 }
278                                 bytes_to_write -= thisblock;
279                                 bytes_written += thisblock;
280                         } else {
281                                 goto ABORTUPL;
282                         }
283                 }
284         }
285
286 ABORTUPL:
287         close(fd);
288
289         /* Last chance for shutdown exit */
290         if(server_shutting_down)
291                 return;
292                 
293         if (sock_puts(sock, "UCLS 1") < 0) return;
294
295         /*
296          * From here on we must complete or messages will get lost
297          */
298         if (sock_getln(sock, buf, sizeof buf) < 0) return;
299         syslog(LOG_NOTICE, "Sent %ld octets to <%s>\n", bytes_written, remote_nodename);
300         syslog(LOG_DEBUG, "<%s\n", buf);
301         if (buf[0] == '2') {
302                 syslog(LOG_DEBUG, "Removing <%s>\n", sfname);
303                 unlink(sfname);
304         }
305 }
306
307
308 /*
309  * Poll one Citadel node (called by network_poll_other_citadel_nodes() below)
310  */
311 void network_poll_node(char *node, char *secret, char *host, char *port) {
312         int sock;
313         char buf[SIZ];
314         char err_buf[SIZ];
315         char connected_to[SIZ];
316         CitContext *CCC=CC;
317
318         if (network_talking_to(node, NTT_CHECK)) return;
319         network_talking_to(node, NTT_ADD);
320         syslog(LOG_DEBUG, "network: polling <%s>\n", node);
321         syslog(LOG_NOTICE, "Connecting to <%s> at %s:%s\n", node, host, port);
322
323         sock = sock_connect(host, port);
324         if (sock < 0) {
325                 syslog(LOG_ERR, "Could not connect: %s\n", strerror(errno));
326                 network_talking_to(node, NTT_REMOVE);
327                 return;
328         }
329         
330         syslog(LOG_DEBUG, "Connected!\n");
331         CCC->SBuf.Buf = NewStrBuf();
332         CCC->sMigrateBuf = NewStrBuf();
333         CCC->SBuf.ReadWritePointer = NULL;
334
335         /* Read the server greeting */
336         if (sock_getln(&sock, buf, sizeof buf) < 0) goto bail;
337         syslog(LOG_DEBUG, ">%s\n", buf);
338
339         /* Check that the remote is who we think it is and warn the Aide if not */
340         extract_token (connected_to, buf, 1, ' ', sizeof connected_to);
341         if (strcmp(connected_to, node))
342         {
343                 snprintf(err_buf, sizeof(err_buf),
344                         "Connected to node \"%s\" but I was expecting to connect to node \"%s\".",
345                         connected_to, node
346                 );
347                 syslog(LOG_ERR, "%s\n", err_buf);
348                 CtdlAideMessage(err_buf, "Network error");
349         }
350         else {
351                 /* We're talking to the correct node.  Now identify ourselves. */
352                 snprintf(buf, sizeof buf, "NETP %s|%s", config.c_nodename, secret);
353                 syslog(LOG_DEBUG, "<%s\n", buf);
354                 if (sock_puts(&sock, buf) <0) goto bail;
355                 if (sock_getln(&sock, buf, sizeof buf) < 0) goto bail;
356                 syslog(LOG_DEBUG, ">%s\n", buf);
357                 if (buf[0] != '2') {
358                         goto bail;
359                 }
360         
361                 /* At this point we are authenticated. */
362                 if (!server_shutting_down)
363                         receive_spool(&sock, node);
364                 if (!server_shutting_down)
365                         transmit_spool(&sock, node);
366         }
367
368         sock_puts(&sock, "QUIT");
369 bail:   
370         FreeStrBuf(&CCC->SBuf.Buf);
371         FreeStrBuf(&CCC->sMigrateBuf);
372         if (sock != -1)
373                 sock_close(sock);
374         network_talking_to(node, NTT_REMOVE);
375 }
376
377
378
379 /*
380  * Poll other Citadel nodes and transfer inbound/outbound network data.
381  * Set "full" to nonzero to force a poll of every node, or to zero to poll
382  * only nodes to which we have data to send.
383  */
384 void network_poll_other_citadel_nodes(int full_poll) {
385         int i;
386         char linebuf[256];
387         char node[SIZ];
388         char host[256];
389         char port[256];
390         char secret[256];
391         int poll = 0;
392         char spoolfile[256];
393
394         if (working_ignetcfg == NULL) {
395                 syslog(LOG_DEBUG, "network: no neighbor nodes are configured - not polling.\n");
396                 return;
397         }
398
399         /* Use the string tokenizer to grab one line at a time */
400         for (i=0; i<num_tokens(working_ignetcfg, '\n'); ++i) {
401                 if(server_shutting_down)
402                         return;
403                 extract_token(linebuf, working_ignetcfg, i, '\n', sizeof linebuf);
404                 extract_token(node, linebuf, 0, '|', sizeof node);
405                 extract_token(secret, linebuf, 1, '|', sizeof secret);
406                 extract_token(host, linebuf, 2, '|', sizeof host);
407                 extract_token(port, linebuf, 3, '|', sizeof port);
408                 if ( !IsEmptyStr(node) && !IsEmptyStr(secret) 
409                    && !IsEmptyStr(host) && !IsEmptyStr(port)) {
410                         poll = full_poll;
411                         if (poll == 0) {
412                                 snprintf(spoolfile, 
413                                          sizeof spoolfile,
414                                          "%s/%s",
415                                          ctdl_netout_dir, 
416                                          node
417                                 );
418                                 if (access(spoolfile, R_OK) == 0) {
419                                         poll = 1;
420                                 }
421                         }
422                         if (poll) {
423                                 network_poll_node(node, secret, host, port);
424                         }
425                 }
426         }
427
428 }
429
430
431 void network_do_clientqueue(void)
432 {
433         int full_processing = 1;
434         static time_t last_run = 0L;
435
436         /*
437          * Run the full set of processing tasks no more frequently
438          * than once every n seconds
439          */
440         if ( (time(NULL) - last_run) < config.c_net_freq ) {
441                 full_processing = 0;
442                 syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n",
443                         config.c_net_freq - (time(NULL)- last_run)
444                 );
445         }
446
447
448         /*
449          * Poll other Citadel nodes.  Maybe.  If "full_processing" is set
450          * then we poll everyone.  Otherwise we only poll nodes we have stuff
451          * to send to.
452          */
453         network_poll_other_citadel_nodes(full_processing);
454 }
455
456
457
458 /*
459  * Module entry point
460  */
461 CTDL_MODULE_INIT(network_client)
462 {
463         if (!threading)
464         {
465                 CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER);
466         }
467         return "network_client";
468 }