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