getting with the times, we dont need to wrap snprintf anymore
[citadel.git] / citadel / modules / network / serv_netconfig.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2012 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 "file_ops.h"
76 #include "citadel_dirs.h"
77 #include "threads.h"
78 #include "context.h"
79 #include "netconfig.h"
80 #include "netspool.h"
81 #include "ctdl_module.h"
82
83
84
85 void DeleteNodeConf(void *vNode)
86 {
87         NodeConf *Node = (NodeConf*) vNode;
88         FreeStrBuf(&Node->NodeName);
89         FreeStrBuf(&Node->Secret);
90         FreeStrBuf(&Node->Host);
91         FreeStrBuf(&Node->Port);
92         free(Node);
93 }
94
95 NodeConf *NewNode(StrBuf *SerializedNode)
96 {
97         const char *Pos = NULL;
98         NodeConf *Node;
99
100         /* we need at least 4 pipes and some other text so its invalid. */
101         if (StrLength(SerializedNode) < 8)
102                 return NULL;
103         Node = (NodeConf *) malloc(sizeof(NodeConf));
104
105         Node->DeleteMe = 0;
106
107         Node->NodeName=NewStrBuf();
108         StrBufExtract_NextToken(Node->NodeName, SerializedNode, &Pos, '|');
109
110         Node->Secret=NewStrBuf();
111         StrBufExtract_NextToken(Node->Secret, SerializedNode, &Pos, '|');
112
113         Node->Host=NewStrBuf();
114         StrBufExtract_NextToken(Node->Host, SerializedNode, &Pos, '|');
115
116         Node->Port=NewStrBuf();
117         StrBufExtract_NextToken(Node->Port, SerializedNode, &Pos, '|');
118         return Node;
119 }
120
121
122 /*
123  * Load or refresh the Citadel network (IGnet) configuration for this node.
124  */
125 HashList* load_ignetcfg(void)
126 {
127         const char *LinePos;
128         char       *Cfg;
129         StrBuf     *Buf;
130         StrBuf     *LineBuf;
131         HashList   *Hash;
132         NodeConf   *Node;
133
134         Cfg =  CtdlGetSysConfig(IGNETCFG);
135         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
136                 if (Cfg != NULL)
137                         free(Cfg);
138                 return NULL;
139         }
140
141         Hash = NewHash(1, NULL);
142         Buf = NewStrBufPlain(Cfg, -1);
143         free(Cfg);
144         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
145         LinePos = NULL;
146         do
147         {
148                 StrBufSipLine(LineBuf, Buf, &LinePos);
149                 if (StrLength(LineBuf) != 0) {
150                         Node = NewNode(LineBuf);
151                         if (Node != NULL) {
152                                 Put(Hash, SKEY(Node->NodeName), Node, DeleteNodeConf);
153                         }
154                 }
155         } while (LinePos != StrBufNOTNULL);
156         FreeStrBuf(&Buf);
157         FreeStrBuf(&LineBuf);
158         return Hash;
159 }
160
161 void DeleteNetMap(void *vNetMap)
162 {
163         NetMap *TheNetMap = (NetMap*) vNetMap;
164         FreeStrBuf(&TheNetMap->NodeName);
165         FreeStrBuf(&TheNetMap->NextHop);
166         free(TheNetMap);
167 }
168
169 NetMap *NewNetMap(StrBuf *SerializedNetMap)
170 {
171         const char *Pos = NULL;
172         NetMap *NM;
173
174         /* we need at least 3 pipes and some other text so its invalid. */
175         if (StrLength(SerializedNetMap) < 6)
176                 return NULL;
177         NM = (NetMap *) malloc(sizeof(NetMap));
178
179         NM->NodeName=NewStrBuf();
180         StrBufExtract_NextToken(NM->NodeName, SerializedNetMap, &Pos, '|');
181
182         NM->lastcontact = StrBufExtractNext_long(SerializedNetMap, &Pos, '|');
183
184         NM->NextHop=NewStrBuf();
185         StrBufExtract_NextToken(NM->NextHop, SerializedNetMap, &Pos, '|');
186
187         return NM;
188 }
189
190 HashList* read_network_map(void)
191 {
192         const char *LinePos;
193         char       *Cfg;
194         StrBuf     *Buf;
195         StrBuf     *LineBuf;
196         HashList   *Hash;
197         NetMap     *TheNetMap;
198
199         Cfg =  CtdlGetSysConfig(IGNETMAP);
200         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
201                 if (Cfg != NULL)
202                         free(Cfg);
203                 return NULL;
204         }
205
206         Hash = NewHash(1, NULL);
207         Buf = NewStrBufPlain(Cfg, -1);
208         free(Cfg);
209         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
210         LinePos = NULL;
211         while (StrBufSipLine(Buf, LineBuf, &LinePos))
212         {
213                 TheNetMap = NewNetMap(LineBuf);
214                 if (TheNetMap != NULL) { /* TODO: is the NodeName Uniq? */
215                         Put(Hash, SKEY(TheNetMap->NodeName), TheNetMap, DeleteNetMap);
216                 }
217         }
218         FreeStrBuf(&Buf);
219         FreeStrBuf(&LineBuf);
220         return Hash;
221 }
222
223 StrBuf *SerializeNetworkMap(HashList *Map)
224 {
225         void *vMap;
226         const char *key;
227         long len;
228         StrBuf *Ret = NewStrBuf();
229         HashPos *Pos = GetNewHashPos(Map, 0);
230
231         while (GetNextHashPos(Map, Pos, &len, &key, &vMap))
232         {
233                 NetMap *pMap = (NetMap*) vMap;
234                 StrBufAppendBuf(Ret, pMap->NodeName, 0);
235                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
236
237                 StrBufAppendPrintf(Ret, "%ld", pMap->lastcontact, 0);
238                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
239
240                 StrBufAppendBuf(Ret, pMap->NextHop, 0);
241                 StrBufAppendBufPlain(Ret, HKEY("\n"), 0);
242         }
243         DeleteHashPos(&Pos);
244         return Ret;
245 }
246
247
248 /*
249  * Learn topology from path fields
250  */
251 void network_learn_topology(char *node, char *path, HashList *the_netmap, int *netmap_changed)
252 {
253         NetMap *pNM = NULL;
254         void *vptr;
255         char nexthop[256];
256         NetMap *nmptr;
257
258         if (GetHash(the_netmap, node, strlen(node), &vptr) && 
259             (vptr != NULL))/* TODO: is the NodeName Uniq? */
260         {
261                 pNM = (NetMap*)vptr;
262                 extract_token(nexthop, path, 0, '!', sizeof nexthop);
263                 if (!strcmp(nexthop, ChrPtr(pNM->NextHop))) {
264                         pNM->lastcontact = time(NULL);
265                         (*netmap_changed) ++;
266                         return;
267                 }
268         }
269
270         /* If we got here then it's not in the map, so add it. */
271         nmptr = (NetMap *) malloc(sizeof (NetMap));
272         nmptr->NodeName = NewStrBufPlain(node, -1);
273         nmptr->lastcontact = time(NULL);
274         nmptr->NextHop = NewStrBuf ();
275         StrBufExtract_tokenFromStr(nmptr->NextHop, path, strlen(path), 0, '!');
276         /* TODO: is the NodeName Uniq? */
277         Put(the_netmap, SKEY(nmptr->NodeName), nmptr, DeleteNetMap);
278         (*netmap_changed) ++;
279 }
280
281
282 /*
283  * Check the network map and determine whether the supplied node name is
284  * valid.  If it is not a neighbor node, supply the name of a neighbor node
285  * which is the next hop.  If it *is* a neighbor node, we also fill in the
286  * shared secret.
287  */
288 int is_valid_node(const StrBuf **nexthop,
289                   const StrBuf **secret,
290                   StrBuf *node,
291                   HashList *IgnetCfg,
292                   HashList *the_netmap)
293 {
294         void *vNetMap;
295         void *vNodeConf;
296         NodeConf *TheNode;
297         NetMap *TheNetMap;
298
299         if (StrLength(node) == 0) {
300                 return(-1);
301         }
302
303         /*
304          * First try the neighbor nodes
305          */
306         if (GetCount(IgnetCfg) == 0) {
307                 syslog(LOG_INFO, "IgnetCfg is empty!\n");
308                 if (nexthop != NULL) {
309                         *nexthop = NULL;
310                 }
311                 return(-1);
312         }
313
314         /* try to find a neigbour with the name 'node' */
315         if (GetHash(IgnetCfg, SKEY(node), &vNodeConf) && 
316             (vNodeConf != NULL))
317         {
318                 TheNode = (NodeConf*)vNodeConf;
319                 if (secret != NULL)
320                         *secret = TheNode->Secret;
321                 return 0;               /* yup, it's a direct neighbor */
322         }
323
324         /*
325          * If we get to this point we have to see if we know the next hop
326          *//* TODO: is the NodeName Uniq? */
327         if ((GetCount(the_netmap) > 0) &&
328             (GetHash(the_netmap, SKEY(node), &vNetMap)))
329         {
330                 TheNetMap = (NetMap*)vNetMap;
331                 if (nexthop != NULL)
332                         *nexthop = TheNetMap->NextHop;
333                 return(0);
334         }
335
336         /*
337          * If we get to this point, the supplied node name is bogus.
338          */
339         syslog(LOG_ERR, "Invalid node name <%s>\n", ChrPtr(node));
340         return(-1);
341 }
342
343
344 void cmd_gnet(char *argbuf)
345 {
346         char filename[PATH_MAX];
347         char buf[SIZ];
348         FILE *fp;
349
350         if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
351                 /* users can edit the netconfigs for their own mailbox rooms */
352         }
353         else if (CtdlAccessCheck(ac_room_aide)) return;
354
355         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
356         cprintf("%d Network settings for room #%ld <%s>\n",
357                 LISTING_FOLLOWS,
358                 CC->room.QRnumber, CC->room.QRname);
359
360         fp = fopen(filename, "r");
361         if (fp != NULL) {
362                 while (fgets(buf, sizeof buf, fp) != NULL) {
363                         buf[strlen(buf)-1] = 0;
364                         cprintf("%s\n", buf);
365                 }
366                 fclose(fp);
367         }
368
369         cprintf("000\n");
370 }
371
372
373 void cmd_snet(char *argbuf) {
374         char tempfilename[PATH_MAX];
375         char filename[PATH_MAX];
376         int TmpFD;
377         StrBuf *Line;
378         struct stat StatBuf;
379         long len;
380         int rc;
381
382         unbuffer_output();
383
384         if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
385                 /* users can edit the netconfigs for their own mailbox rooms */
386         }
387         else if (CtdlAccessCheck(ac_room_aide)) return;
388
389         len = assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
390         memcpy(tempfilename, filename, len + 1);
391
392         memset(&StatBuf, 0, sizeof(struct stat));
393         if ((stat(filename, &StatBuf)  == -1) || (StatBuf.st_size == 0))
394                 StatBuf.st_size = 80; /* Not there or empty? guess 80 chars line. */
395
396         sprintf(tempfilename + len, ".%d", CC->cs_pid);
397         errno = 0;
398         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
399
400         if ((TmpFD > 0) && (errno == 0))
401         {
402                 char *tmp = malloc(StatBuf.st_size * 2);
403                 memset(tmp, ' ', StatBuf.st_size * 2);
404                 rc = write(TmpFD, tmp, StatBuf.st_size * 2);
405                 free(tmp);
406                 if ((rc <= 0) || (rc != StatBuf.st_size * 2))
407                 {
408                         close(TmpFD);
409                         cprintf("%d Unable to allocate the space required for %s: %s\n",
410                                 ERROR + INTERNAL_ERROR,
411                                 tempfilename,
412                                 strerror(errno));
413                         unlink(tempfilename);
414                         return;
415                 }       
416                 lseek(TmpFD, SEEK_SET, 0);
417         }
418         else {
419                 cprintf("%d Unable to allocate the space required for %s: %s\n",
420                         ERROR + INTERNAL_ERROR,
421                         tempfilename,
422                         strerror(errno));
423                 unlink(tempfilename);
424                 return;
425         }
426         Line = NewStrBuf();
427
428         cprintf("%d %s\n", SEND_LISTING, tempfilename);
429
430         len = 0;
431         while (rc = CtdlClientGetLine(Line), 
432                (rc >= 0))
433         {
434                 if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
435                         break;
436                 StrBufAppendBufPlain(Line, HKEY("\n"), 0);
437                 write(TmpFD, ChrPtr(Line), StrLength(Line));
438                 len += StrLength(Line);
439         }
440         FreeStrBuf(&Line);
441         ftruncate(TmpFD, len);
442         close(TmpFD);
443
444         /* Now copy the temp file to its permanent location.
445          * (We copy instead of link because they may be on different filesystems)
446          */
447         begin_critical_section(S_NETCONFIGS);
448         rename(tempfilename, filename);
449         end_critical_section(S_NETCONFIGS);
450 }
451
452 /*
453  * cmd_netp() - authenticate to the server as another Citadel node polling
454  *            for network traffic
455  */
456 void cmd_netp(char *cmdbuf)
457 {
458         struct CitContext *CCC = CC;
459         HashList *working_ignetcfg;
460         char *node;
461         StrBuf *NodeStr;
462         long nodelen;
463         int v;
464         long lens[2];
465         const char *strs[2];
466
467         const StrBuf *secret = NULL;
468         const StrBuf *nexthop = NULL;
469         char err_buf[SIZ] = "";
470
471         /* Authenticate */
472         node = CCC->curr_user;
473         nodelen = extract_token(CCC->curr_user, cmdbuf, 0, '|', sizeof CCC->curr_user);
474         NodeStr = NewStrBufPlain(node, nodelen);
475         /* load the IGnet Configuration to check node validity */
476         working_ignetcfg = load_ignetcfg();
477         v = is_valid_node(&nexthop, &secret, NodeStr, working_ignetcfg, NULL);
478         if (v != 0) {
479                 snprintf(err_buf, sizeof err_buf,
480                         "An unknown Citadel server called \"%s\" attempted to connect from %s [%s].\n",
481                         node, CCC->cs_host, CCC->cs_addr
482                 );
483                 syslog(LOG_WARNING, "%s", err_buf);
484                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
485
486                 strs[0] = CCC->cs_addr;
487                 lens[0] = strlen(CCC->cs_addr);
488                 
489                 strs[1] = "SRV_UNKNOWN";
490                 lens[1] = sizeof("SRV_UNKNOWN" - 1);
491
492                 CtdlAideFPMessage(
493                         err_buf,
494                         "IGNet Networking.",
495                         2, strs, (long*) &lens);
496
497                 DeleteHash(&working_ignetcfg);
498                 FreeStrBuf(&NodeStr);
499                 return;
500         }
501
502         extract_token(CCC->user.password, cmdbuf, 1, '|', sizeof CCC->user.password);
503         if (strcasecmp(CCC->user.password, ChrPtr(secret))) {
504                 snprintf(err_buf, sizeof err_buf,
505                         "A Citadel server at %s [%s] failed to authenticate as network node \"%s\".\n",
506                         CCC->cs_host, CCC->cs_addr, node
507                 );
508                 syslog(LOG_WARNING, "%s", err_buf);
509                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
510
511                 strs[0] = CCC->cs_addr;
512                 lens[0] = strlen(CCC->cs_addr);
513                 
514                 strs[1] = "SRV_PW";
515                 lens[1] = sizeof("SRV_PW" - 1);
516
517                 CtdlAideFPMessage(
518                         err_buf,
519                         "IGNet Networking.",
520                         2, strs, (long*) &lens);
521
522                 DeleteHash(&working_ignetcfg);
523                 FreeStrBuf(&NodeStr);
524                 return;
525         }
526
527         if (network_talking_to(node, nodelen, NTT_CHECK)) {
528                 syslog(LOG_WARNING, "Duplicate session for network node <%s>", node);
529                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
530                 DeleteHash(&working_ignetcfg);
531                 FreeStrBuf(&NodeStr);
532                 return;
533         }
534         nodelen = safestrncpy(CCC->net_node, node, sizeof CCC->net_node);
535         network_talking_to(CCC->net_node, nodelen, NTT_ADD);
536         syslog(LOG_NOTICE, "Network node <%s> logged in from %s [%s]\n",
537                 CCC->net_node, CCC->cs_host, CCC->cs_addr
538         );
539         cprintf("%d authenticated as network node '%s'\n", CIT_OK, CCC->net_node);
540         DeleteHash(&working_ignetcfg);
541         FreeStrBuf(&NodeStr);
542 }
543
544 int netconfig_check_roomaccess(
545         char *errmsgbuf, 
546         size_t n,
547         const char* RemoteIdentifier)
548 {
549         SpoolControl *sc;
550         char filename[SIZ];
551         int found;
552
553         if (RemoteIdentifier == NULL)
554         {
555                 snprintf(errmsgbuf, n, "Need sender to permit access.");
556                 return (ERROR + USERNAME_REQUIRED);
557         }
558
559         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
560         begin_critical_section(S_NETCONFIGS);
561         if (!read_spoolcontrol_file(&sc, filename))
562         {
563                 end_critical_section(S_NETCONFIGS);
564                 snprintf(errmsgbuf, n,
565                          "This mailing list only accepts posts from subscribers.");
566                 return (ERROR + NO_SUCH_USER);
567         }
568         end_critical_section(S_NETCONFIGS);
569         found = is_recipient (sc, RemoteIdentifier);
570         free_spoolcontrol_struct(&sc);
571         if (found) {
572                 return (0);
573         }
574         else {
575                 snprintf(errmsgbuf, n,
576                          "This mailing list only accepts posts from subscribers.");
577                 return (ERROR + NO_SUCH_USER);
578         }
579 }
580 /*
581  * Module entry point
582  */
583 CTDL_MODULE_INIT(netconfig)
584 {
585         if (!threading)
586         {
587                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
588                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
589                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
590         }
591         return "netconfig";
592 }