Removed the caching of netconfig entries. The caching of configdb entries now serves...
[citadel.git] / citadel / modules / network / serv_network.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2015 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, version 3.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
16  * This is a fairly high-level type of critical section.  It ensures that no
17  * two threads work on the netconfigs files at the same time.  Since we do
18  * so many things inside these, here are the rules:
19  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
20  *  2. Do *not* perform any I/O with the client during these sections.
21  *
22  */
23
24 /*
25  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
26  * requests that have not been confirmed will be deleted.
27  */
28 #define EXP     259200  /* three days */
29
30 #include "sysdep.h"
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <stdio.h>
34 #include <fcntl.h>
35 #include <ctype.h>
36 #include <signal.h>
37 #include <pwd.h>
38 #include <errno.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <dirent.h>
42 #if TIME_WITH_SYS_TIME
43 # include <sys/time.h>
44 # include <time.h>
45 #else
46 # if HAVE_SYS_TIME_H
47 #  include <sys/time.h>
48 # else
49 #  include <time.h>
50 # endif
51 #endif
52 #ifdef HAVE_SYSCALL_H
53 # include <syscall.h>
54 #else 
55 # if HAVE_SYS_SYSCALL_H
56 #  include <sys/syscall.h>
57 # endif
58 #endif
59
60 #include <sys/wait.h>
61 #include <string.h>
62 #include <limits.h>
63 #include <libcitadel.h>
64 #include "citadel.h"
65 #include "server.h"
66 #include "citserver.h"
67 #include "support.h"
68 #include "config.h"
69 #include "user_ops.h"
70 #include "database.h"
71 #include "msgbase.h"
72 #include "internet_addressing.h"
73 #include "serv_network.h"
74 #include "clientsocket.h"
75 #include "citadel_dirs.h"
76 #include "threads.h"
77 #include "context.h"
78 #include "ctdl_module.h"
79 #include "netspool.h"
80 #include "netmail.h"
81
82 int NetQDebugEnabled = 0;
83 struct CitContext networker_spool_CC;
84
85 /* comes from lookup3.c from libcitadel... */
86 extern uint32_t hashlittle( const void *key, size_t length, uint32_t initval);
87
88 typedef struct __roomlists {
89         RoomProcList *rplist;
90 } roomlists;
91
92
93 /*
94  * When we do network processing, it's accomplished in two passes; one to
95  * gather a list of rooms and one to actually do them.  It's ok that rplist
96  * is global; we have a mutex that keeps it safe.
97  */
98 struct RoomProcList *rplist = NULL;
99
100
101 /*
102  * Send the *entire* contents of the current room to one specific network node,
103  * ignoring anything we know about which messages have already undergone
104  * network processing.  This can be used to bring a new node into sync.
105  */
106 int network_sync_to(char *target_node, long len)
107 {
108         struct CitContext *CCC = CC;
109         OneRoomNetCfg OneRNCFG;
110         OneRoomNetCfg *pRNCFG;
111         const RoomNetCfgLine *pCfgLine;
112         SpoolControl sc;
113         int num_spooled = 0;
114
115         /* Grab the configuration line we're looking for */
116         begin_critical_section(S_NETCONFIGS);
117         pRNCFG = CtdlGetNetCfgForRoom(CCC->room.QRnumber);
118         if ((pRNCFG == NULL) || (pRNCFG->NetConfigs[ignet_push_share] == NULL))
119         {
120                 return -1;
121         }
122
123         pCfgLine = pRNCFG->NetConfigs[ignet_push_share];
124         while (pCfgLine != NULL)
125         {
126                 if (!strcmp(ChrPtr(pCfgLine->Value[0]), target_node))
127                         break;
128                 pCfgLine = pCfgLine->next;
129         }
130         if (pCfgLine == NULL)
131         {
132                 return -1;
133         }
134
135         memset(&sc, 0, sizeof(SpoolControl));
136         memset(&OneRNCFG, 0, sizeof(OneRoomNetCfg));
137         sc.RNCfg = &OneRNCFG;
138         sc.RNCfg->NetConfigs[ignet_push_share] = DuplicateOneGenericCfgLine(pCfgLine);
139         sc.Users[ignet_push_share] = NewStrBufPlain(NULL,
140                                                     StrLength(pCfgLine->Value[0]) +
141                                                     StrLength(pCfgLine->Value[1]) + 10
142         );
143         StrBufAppendBuf(sc.Users[ignet_push_share], pCfgLine->Value[0], 0);
144         StrBufAppendBufPlain(sc.Users[ignet_push_share], HKEY(","), 0);
145         StrBufAppendBuf(sc.Users[ignet_push_share], pCfgLine->Value[1], 0);
146         CalcListID(&sc);
147
148         end_critical_section(S_NETCONFIGS);
149
150         sc.working_ignetcfg = CtdlLoadIgNetCfg();
151         sc.the_netmap = CtdlReadNetworkMap();
152
153         /* Send ALL messages */
154         num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, network_spool_msg, &sc);
155
156         /* Concise cleanup because we know there's only one node in the sc */
157         DeleteGenericCfgLine(NULL/*TODO*/, &sc.RNCfg->NetConfigs[ignet_push_share]);
158
159         DeleteHash(&sc.working_ignetcfg);
160         DeleteHash(&sc.the_netmap);
161         free_spoolcontrol_struct_members(&sc);
162
163         QN_syslog(LOG_NOTICE, "Synchronized %d messages to <%s>\n",
164                   num_spooled, target_node);
165         return(num_spooled);
166 }
167
168
169 /*
170  * Implements the NSYN command
171  */
172 void cmd_nsyn(char *argbuf) {
173         int num_spooled;
174         long len;
175         char target_node[256];
176
177         if (CtdlAccessCheck(ac_aide)) return;
178
179         len = extract_token(target_node, argbuf, 0, '|', sizeof target_node);
180         num_spooled = network_sync_to(target_node, len);
181         if (num_spooled >= 0) {
182                 cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled);
183         }
184         else {
185                 cprintf("%d No such room/node share exists.\n",
186                         ERROR + ROOM_NOT_FOUND);
187         }
188 }
189
190 RoomProcList *CreateRoomProcListEntry(struct ctdlroom *qrbuf, OneRoomNetCfg *OneRNCFG)
191 {
192         int i;
193         struct RoomProcList *ptr;
194
195         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
196         if (ptr == NULL) return NULL;
197
198         ptr->namelen = strlen(qrbuf->QRname);
199         if (ptr->namelen > ROOMNAMELEN)
200                 ptr->namelen = ROOMNAMELEN - 1;
201
202         memcpy (ptr->name, qrbuf->QRname, ptr->namelen);
203         ptr->name[ptr->namelen] = '\0';
204         ptr->QRNum = qrbuf->QRnumber;
205
206         for (i = 0; i < ptr->namelen; i++)
207         {
208                 ptr->lcname[i] = tolower(ptr->name[i]);
209         }
210
211         ptr->lcname[ptr->namelen] = '\0';
212         ptr->key = hashlittle(ptr->lcname, ptr->namelen, 9872345);
213         ptr->lastsent = OneRNCFG->lastsent;
214         ptr->OneRNCfg = OneRNCFG;
215         return ptr;
216 }
217
218 /*
219  * Batch up and send all outbound traffic from the current room
220  */
221 void network_queue_interesting_rooms(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCfg)
222 {
223         struct RoomProcList *ptr;
224         roomlists *RP = (roomlists*) data;
225
226         if (!HaveSpoolConfig(OneRNCfg))
227                 return;
228
229         ptr = CreateRoomProcListEntry(qrbuf, OneRNCfg);
230
231         if (ptr != NULL)
232         {
233                 ptr->next = RP->rplist;
234                 RP->rplist = ptr;
235         }
236 }
237
238 /*
239  * Batch up and send all outbound traffic from the current room
240  */
241 int network_room_handler (struct ctdlroom *qrbuf)
242 {
243         struct RoomProcList *ptr;
244         OneRoomNetCfg* RNCfg;
245
246         if (qrbuf->QRdefaultview == VIEW_QUEUE)
247                 return 1;
248
249         RNCfg = CtdlGetNetCfgForRoom(qrbuf->QRnumber);
250         if (RNCfg == NULL)
251                 return 1;
252
253         if (!HaveSpoolConfig(RNCfg))
254                 return 1;
255
256         ptr = CreateRoomProcListEntry(qrbuf, RNCfg);
257         if (ptr == NULL)
258                 return 1;
259
260         ptr->OneRNCfg = NULL;
261         begin_critical_section(S_RPLIST);
262         ptr->next = rplist;
263         rplist = ptr;
264         end_critical_section(S_RPLIST);
265         return 1;
266 }
267
268 void destroy_network_queue_room(RoomProcList *rplist)
269 {
270         struct RoomProcList *cur, *p;
271
272         cur = rplist;
273         while (cur != NULL)
274         {
275                 p = cur->next;
276                 free (cur);
277                 cur = p;                
278         }
279 }
280
281 void destroy_network_queue_room_locked (void)
282 {
283         begin_critical_section(S_RPLIST);
284         destroy_network_queue_room(rplist);
285         end_critical_section(S_RPLIST);
286 }
287
288
289 /*
290  * network_do_queue()
291  * 
292  * Run through the rooms doing various types of network stuff.
293  */
294 void network_do_queue(void)
295 {
296         struct CitContext *CCC = CC;
297         static time_t last_run = 0L;
298         int full_processing = 1;
299         HashList *working_ignetcfg;
300         HashList *the_netmap = NULL;
301         int netmap_changed = 0;
302         roomlists RL;
303         SpoolControl *sc = NULL;
304         SpoolControl *pSC;
305
306         /*
307          * Run the full set of processing tasks no more frequently
308          * than once every n seconds
309          */
310         if ( (time(NULL) - last_run) < CtdlGetConfigLong("c_net_freq") )
311         {
312                 full_processing = 0;
313                 syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n",
314                        CtdlGetConfigLong("c_net_freq") - (time(NULL)- last_run)
315                 );
316         }
317
318         become_session(&networker_spool_CC);
319         begin_critical_section(S_RPLIST);
320         RL.rplist = rplist;
321         rplist = NULL;
322         end_critical_section(S_RPLIST);
323 ///TODO hm, check whether we have a config at all here?
324         /* Load the IGnet Configuration into memory */
325         working_ignetcfg = CtdlLoadIgNetCfg();
326
327         /*
328          * Load the network map and filter list into memory.
329          */
330         if (!server_shutting_down)
331                 the_netmap = CtdlReadNetworkMap();
332 #if 0
333         /* filterlist isn't supported anymore
334         if (!server_shutting_down)
335                 load_network_filter_list();
336         */
337 #endif
338
339         /* 
340          * Go ahead and run the queue
341          */
342         if (full_processing && !server_shutting_down) {
343                 QNM_syslog(LOG_DEBUG, "network: loading outbound queue");
344                 CtdlForEachNetCfgRoom(network_queue_interesting_rooms, &RL, maxRoomNetCfg);
345         }
346
347         if ((RL.rplist != NULL) && (!server_shutting_down)) {
348                 RoomProcList *ptr, *cmp;
349                 ptr = RL.rplist;
350                 QNM_syslog(LOG_DEBUG, "network: running outbound queue");
351                 while (ptr != NULL && !server_shutting_down) {
352                         
353                         cmp = ptr->next;
354                         /* filter duplicates from the list... */
355                         while (cmp != NULL) {
356                                 if ((cmp->namelen > 0) &&
357                                     (cmp->key == ptr->key) &&
358                                     (cmp->namelen == ptr->namelen) &&
359                                     (strcmp(cmp->lcname, ptr->lcname) == 0))
360                                 {
361                                         cmp->namelen = 0;
362                                 }
363                                 cmp = cmp->next;
364                         }
365
366                         if (ptr->namelen > 0) {
367                                 InspectQueuedRoom(&sc,
368                                                   ptr, 
369                                                   working_ignetcfg,
370                                                   the_netmap);
371                         }
372                         ptr = ptr->next;
373                 }
374         }
375
376
377         pSC = sc;
378         while (pSC != NULL)
379         {
380                 network_spoolout_room(pSC);
381                 pSC = pSC->next;
382         }
383
384         pSC = sc;
385         while (pSC != NULL)
386         {
387                 sc = pSC->next;
388                 free_spoolcontrol_struct(&pSC);
389                 pSC = sc;
390         }
391         /* If there is anything in the inbound queue, process it */
392         if (!server_shutting_down) {
393                 network_do_spoolin(working_ignetcfg, 
394                                    the_netmap,
395                                    &netmap_changed);
396         }
397
398         /* Free the filter list in memory */
399         free_netfilter_list();
400
401         /* Save the network map back to disk */
402         if (netmap_changed) {
403                 StrBuf *MapStr = CtdlSerializeNetworkMap(the_netmap);
404                 char *pMapStr = SmashStrBuf(&MapStr);
405                 CtdlPutSysConfig(IGNETMAP, pMapStr);
406                 free(pMapStr);
407         }
408
409         /* combine singe message files into one spool entry per remote node. */
410         network_consolidate_spoolout(working_ignetcfg, the_netmap);
411
412         /* shut down. */
413
414         DeleteHash(&the_netmap);
415
416         DeleteHash(&working_ignetcfg);
417
418         QNM_syslog(LOG_DEBUG, "network: queue run completed");
419
420         if (full_processing) {
421                 last_run = time(NULL);
422         }
423         destroy_network_queue_room(RL.rplist);
424         // SaveChangedConfigs();        // FIXME FOOFOO SAVE CHANGED THIS AACACACACCKK
425
426 }
427
428
429
430
431
432
433
434
435 void network_logout_hook(void)
436 {
437         CitContext *CCC = MyContext();
438
439         /*
440          * If we were talking to a network node, we're not anymore...
441          */
442         if (!IsEmptyStr(CCC->net_node)) {
443                 CtdlNetworkTalkingTo(CCC->net_node, strlen(CCC->net_node), NTT_REMOVE);
444                 CCC->net_node[0] = '\0';
445         }
446 }
447 void network_cleanup_function(void)
448 {
449         struct CitContext *CCC = CC;
450
451         if (!IsEmptyStr(CCC->net_node)) {
452                 CtdlNetworkTalkingTo(CCC->net_node, strlen(CCC->net_node), NTT_REMOVE);
453                 CCC->net_node[0] = '\0';
454         }
455 }
456
457
458 int ignet_aftersave(struct CtdlMessage *msg,
459                     recptypes *recps)   /* recipients (if mail) */
460 {
461         /* For IGnet mail, we have to save a new copy into the spooler for
462          * each recipient, with the R and D fields set to the recipient and
463          * destination-node.  This has two ugly side effects: all other
464          * recipients end up being unlisted in this recipient's copy of the
465          * message, and it has to deliver multiple messages to the same
466          * node.  We'll revisit this again in a year or so when everyone has
467          * a network spool receiver that can handle the new style messages.
468          */
469         if ((recps != NULL) && (recps->num_ignet > 0))
470         {
471                 char *recipient;
472                 int rv = 0;
473                 struct ser_ret smr;
474                 FILE *network_fp = NULL;
475                 char submit_filename[128];
476                 static int seqnum = 1;
477                 int i;
478                 char *hold_R, *hold_D, *RBuf, *DBuf;
479                 long hrlen, hdlen, rblen, dblen, count, rlen;
480                 CitContext *CCC = MyContext();
481
482                 CM_GetAsField(msg, eRecipient, &hold_R, &hrlen);;
483                 CM_GetAsField(msg, eDestination, &hold_D, &hdlen);;
484
485                 count = num_tokens(recps->recp_ignet, '|');
486                 rlen = strlen(recps->recp_ignet);
487                 recipient = malloc(rlen + 1);
488                 RBuf = malloc(rlen + 1);
489                 DBuf = malloc(rlen + 1);
490                 for (i=0; i<count; ++i) {
491                         extract_token(recipient, recps->recp_ignet, i,
492                                       '|', rlen + 1);
493
494                         rblen = extract_token(RBuf, recipient, 0, '@', rlen + 1);
495                         dblen = extract_token(DBuf, recipient, 1, '@', rlen + 1);
496                 
497                         CM_SetAsField(msg, eRecipient, &RBuf, rblen);;
498                         CM_SetAsField(msg, eDestination, &DBuf, dblen);;
499                         CtdlSerializeMessage(&smr, msg);
500                         if (smr.len > 0) {
501                                 snprintf(submit_filename, sizeof submit_filename,
502                                          "%s/netmail.%04lx.%04x.%04x",
503                                          ctdl_netin_dir,
504                                          (long) getpid(),
505                                          CCC->cs_pid,
506                                          ++seqnum);
507
508                                 network_fp = fopen(submit_filename, "wb+");
509                                 if (network_fp != NULL) {
510                                         rv = fwrite(smr.ser, smr.len, 1, network_fp);
511                                         if (rv == -1) {
512                                                 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
513                                                            strerror(errno));
514                                         }
515                                         fclose(network_fp);
516                                 }
517                                 free(smr.ser);
518                         }
519                         CM_GetAsField(msg, eRecipient, &RBuf, &rblen);;
520                         CM_GetAsField(msg, eDestination, &DBuf, &dblen);;
521                 }
522                 free(RBuf);
523                 free(DBuf);
524                 free(recipient);
525                 CM_SetAsField(msg, eRecipient, &hold_R, hrlen);
526                 CM_SetAsField(msg, eDestination, &hold_D, hdlen);
527                 return 1;
528         }
529         return 0;
530 }
531
532 /*
533  * Module entry point
534  */
535
536 void SetNetQDebugEnabled(const int n)
537 {
538         NetQDebugEnabled = n;
539 }
540
541 CTDL_MODULE_INIT(network)
542 {
543         if (!threading)
544         {
545                 CtdlRegisterMessageHook(ignet_aftersave, EVT_AFTERSAVE);
546
547                 CtdlFillSystemContext(&networker_spool_CC, "CitNetSpool");
548                 CtdlRegisterDebugFlagHook(HKEY("networkqueue"), SetNetQDebugEnabled, &NetQDebugEnabled);
549                 CtdlRegisterSessionHook(network_cleanup_function, EVT_STOP, PRIO_STOP + 30);
550                 CtdlRegisterSessionHook(network_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 10);
551                 CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node");
552                 CtdlRegisterRoomHook(network_room_handler);
553                 CtdlRegisterCleanupHook(destroy_network_queue_room_locked);
554                 CtdlRegisterSessionHook(network_do_queue, EVT_TIMER, PRIO_QUEUE + 10);
555         }
556         return "network";
557 }