dc6f9d3f07577a835ceaa818343c1ba12cc528ab
[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
351         if (!IsEmptyStr(argbuf))
352         {
353                 if (CtdlAccessCheck(ac_aide)) return;
354                 if (strcmp(argbuf, FILE_MAILALIAS))
355                 {
356                         cprintf("%d No such file or directory\n",
357                                 ERROR + INTERNAL_ERROR);
358                         return;
359                 }
360                 safestrncpy(filename, file_mail_aliases, sizeof(filename));
361                 cprintf("%d Settings for <%s>\n",
362                         LISTING_FOLLOWS,
363                         filename);
364         }
365         else
366         {
367                 if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
368                         /* users can edit the netconfigs for their own mailbox rooms */
369                 }
370                 else if (CtdlAccessCheck(ac_room_aide)) return;
371                 
372                 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
373                 cprintf("%d Network settings for room #%ld <%s>\n",
374                         LISTING_FOLLOWS,
375                         CC->room.QRnumber, CC->room.QRname);
376         }
377
378         fp = fopen(filename, "r");
379         if (fp != NULL) {
380                 while (fgets(buf, sizeof buf, fp) != NULL) {
381                         buf[strlen(buf)-1] = 0;
382                         cprintf("%s\n", buf);
383                 }
384                 fclose(fp);
385         }
386
387         cprintf("000\n");
388 }
389
390 #define nForceAliases 5
391 const ConstStr ForceAliases[nForceAliases] = {
392         {HKEY("bbs,")},
393         {HKEY("root,")},
394         {HKEY("Auto,")},
395         {HKEY("postmaster,")},
396         {HKEY("abuse,")}
397 };
398
399 void cmd_snet(char *argbuf) {
400         char tempfilename[PATH_MAX];
401         char filename[PATH_MAX];
402         int TmpFD;
403         StrBuf *Line;
404         struct stat StatBuf;
405         long len;
406         int rc;
407         int IsMailAlias = 0;
408         int MailAliasesFound[nForceAliases];
409
410         unbuffer_output();
411
412         if (!IsEmptyStr(argbuf))
413         {
414                 if (CtdlAccessCheck(ac_aide)) return;
415                 if (strcmp(argbuf, FILE_MAILALIAS))
416                 {
417                         cprintf("%d No such file or directory\n",
418                                 ERROR + INTERNAL_ERROR);
419                         return;
420                 }
421                 safestrncpy(filename, file_mail_aliases, sizeof(filename));
422                 memset(MailAliasesFound, 0, sizeof(MailAliasesFound));
423                 IsMailAlias = 1;
424         }
425         else
426         {
427                 if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
428                         /* users can edit the netconfigs for their own mailbox rooms */
429                 }
430                 else if (CtdlAccessCheck(ac_room_aide)) return;
431                 
432                 len = assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
433                 memcpy(tempfilename, filename, len + 1);
434         }
435         memset(&StatBuf, 0, sizeof(struct stat));
436         if ((stat(filename, &StatBuf)  == -1) || (StatBuf.st_size == 0))
437                 StatBuf.st_size = 80; /* Not there or empty? guess 80 chars line. */
438
439         sprintf(tempfilename + len, ".%d", CC->cs_pid);
440         errno = 0;
441         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
442
443         if ((TmpFD > 0) && (errno == 0))
444         {
445                 char *tmp = malloc(StatBuf.st_size * 2);
446                 memset(tmp, ' ', StatBuf.st_size * 2);
447                 rc = write(TmpFD, tmp, StatBuf.st_size * 2);
448                 free(tmp);
449                 if ((rc <= 0) || (rc != StatBuf.st_size * 2))
450                 {
451                         close(TmpFD);
452                         cprintf("%d Unable to allocate the space required for %s: %s\n",
453                                 ERROR + INTERNAL_ERROR,
454                                 tempfilename,
455                                 strerror(errno));
456                         unlink(tempfilename);
457                         return;
458                 }       
459                 lseek(TmpFD, SEEK_SET, 0);
460         }
461         else {
462                 cprintf("%d Unable to allocate the space required for %s: %s\n",
463                         ERROR + INTERNAL_ERROR,
464                         tempfilename,
465                         strerror(errno));
466                 unlink(tempfilename);
467                 return;
468         }
469         Line = NewStrBuf();
470
471         cprintf("%d %s\n", SEND_LISTING, tempfilename);
472
473         len = 0;
474         while (rc = CtdlClientGetLine(Line), 
475                (rc >= 0))
476         {
477                 if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
478                         break;
479                 if (IsMailAlias)
480                 {
481                         int i;
482
483                         for (i = 0; i < nForceAliases; i++)
484                         {
485                                 if ((!MailAliasesFound[i]) && 
486                                     (strncmp(ForceAliases[i].Key, 
487                                              ChrPtr(Line),
488                                              ForceAliases[i].len) == 0)
489                                         )
490                                     {
491                                             MailAliasesFound[i] = 1;
492                                             break;
493                                     }
494                         }
495                 }
496
497                 StrBufAppendBufPlain(Line, HKEY("\n"), 0);
498                 write(TmpFD, ChrPtr(Line), StrLength(Line));
499                 len += StrLength(Line);
500         }
501         FreeStrBuf(&Line);
502         ftruncate(TmpFD, len);
503         close(TmpFD);
504
505         if (IsMailAlias)
506         {
507                 int i, state;
508                 /*
509                  * Sanity check whether all aliases required by the RFCs were set
510                  * else bail out.
511                  */
512                 state = 1;
513                 for (i = 0; i < nForceAliases; i++)
514                 {
515                         if (!MailAliasesFound[i]) 
516                                 state = 0;
517                 }
518                 if (state == 0)
519                 {
520                         cprintf("%d won't do this - you're missing an RFC required alias.\n",
521                                 ERROR + INTERNAL_ERROR);
522                         unlink(tempfilename);
523                         return;
524                 }
525         }
526
527         /* Now copy the temp file to its permanent location.
528          * (We copy instead of link because they may be on different filesystems)
529          */
530         begin_critical_section(S_NETCONFIGS);
531         rename(tempfilename, filename);
532         end_critical_section(S_NETCONFIGS);
533 }
534
535 /*
536  * cmd_netp() - authenticate to the server as another Citadel node polling
537  *            for network traffic
538  */
539 void cmd_netp(char *cmdbuf)
540 {
541         struct CitContext *CCC = CC;
542         HashList *working_ignetcfg;
543         char *node;
544         StrBuf *NodeStr;
545         long nodelen;
546         int v;
547         long lens[2];
548         const char *strs[2];
549
550         const StrBuf *secret = NULL;
551         const StrBuf *nexthop = NULL;
552         char err_buf[SIZ] = "";
553
554         /* Authenticate */
555         node = CCC->curr_user;
556         nodelen = extract_token(CCC->curr_user, cmdbuf, 0, '|', sizeof CCC->curr_user);
557         NodeStr = NewStrBufPlain(node, nodelen);
558         /* load the IGnet Configuration to check node validity */
559         working_ignetcfg = load_ignetcfg();
560         v = is_valid_node(&nexthop, &secret, NodeStr, working_ignetcfg, NULL);
561         if (v != 0) {
562                 snprintf(err_buf, sizeof err_buf,
563                         "An unknown Citadel server called \"%s\" attempted to connect from %s [%s].\n",
564                         node, CCC->cs_host, CCC->cs_addr
565                 );
566                 syslog(LOG_WARNING, "%s", err_buf);
567                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
568
569                 strs[0] = CCC->cs_addr;
570                 lens[0] = strlen(CCC->cs_addr);
571                 
572                 strs[1] = "SRV_UNKNOWN";
573                 lens[1] = sizeof("SRV_UNKNOWN" - 1);
574
575                 CtdlAideFPMessage(
576                         err_buf,
577                         "IGNet Networking.",
578                         2, strs, (long*) &lens);
579
580                 DeleteHash(&working_ignetcfg);
581                 FreeStrBuf(&NodeStr);
582                 return;
583         }
584
585         extract_token(CCC->user.password, cmdbuf, 1, '|', sizeof CCC->user.password);
586         if (strcasecmp(CCC->user.password, ChrPtr(secret))) {
587                 snprintf(err_buf, sizeof err_buf,
588                         "A Citadel server at %s [%s] failed to authenticate as network node \"%s\".\n",
589                         CCC->cs_host, CCC->cs_addr, node
590                 );
591                 syslog(LOG_WARNING, "%s", err_buf);
592                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
593
594                 strs[0] = CCC->cs_addr;
595                 lens[0] = strlen(CCC->cs_addr);
596                 
597                 strs[1] = "SRV_PW";
598                 lens[1] = sizeof("SRV_PW" - 1);
599
600                 CtdlAideFPMessage(
601                         err_buf,
602                         "IGNet Networking.",
603                         2, strs, (long*) &lens);
604
605                 DeleteHash(&working_ignetcfg);
606                 FreeStrBuf(&NodeStr);
607                 return;
608         }
609
610         if (network_talking_to(node, nodelen, NTT_CHECK)) {
611                 syslog(LOG_WARNING, "Duplicate session for network node <%s>", node);
612                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
613                 DeleteHash(&working_ignetcfg);
614                 FreeStrBuf(&NodeStr);
615                 return;
616         }
617         nodelen = safestrncpy(CCC->net_node, node, sizeof CCC->net_node);
618         network_talking_to(CCC->net_node, nodelen, NTT_ADD);
619         syslog(LOG_NOTICE, "Network node <%s> logged in from %s [%s]\n",
620                 CCC->net_node, CCC->cs_host, CCC->cs_addr
621         );
622         cprintf("%d authenticated as network node '%s'\n", CIT_OK, CCC->net_node);
623         DeleteHash(&working_ignetcfg);
624         FreeStrBuf(&NodeStr);
625 }
626
627 int netconfig_check_roomaccess(
628         char *errmsgbuf, 
629         size_t n,
630         const char* RemoteIdentifier)
631 {
632         SpoolControl *sc;
633         char filename[SIZ];
634         int found;
635
636         if (RemoteIdentifier == NULL)
637         {
638                 snprintf(errmsgbuf, n, "Need sender to permit access.");
639                 return (ERROR + USERNAME_REQUIRED);
640         }
641
642         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
643         begin_critical_section(S_NETCONFIGS);
644         if (!read_spoolcontrol_file(&sc, filename))
645         {
646                 end_critical_section(S_NETCONFIGS);
647                 snprintf(errmsgbuf, n,
648                          "This mailing list only accepts posts from subscribers.");
649                 return (ERROR + NO_SUCH_USER);
650         }
651         end_critical_section(S_NETCONFIGS);
652         found = is_recipient (sc, RemoteIdentifier);
653         free_spoolcontrol_struct(&sc);
654         if (found) {
655                 return (0);
656         }
657         else {
658                 snprintf(errmsgbuf, n,
659                          "This mailing list only accepts posts from subscribers.");
660                 return (ERROR + NO_SUCH_USER);
661         }
662 }
663 /*
664  * Module entry point
665  */
666 CTDL_MODULE_INIT(netconfig)
667 {
668         if (!threading)
669         {
670                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
671                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
672                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
673         }
674         return "netconfig";
675 }