NETCFG: make basic parsing
[citadel.git] / citadel / 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  */
16
17 #include "sysdep.h"
18 #include <stdio.h>
19
20 #ifdef HAVE_SYSCALL_H
21 # include <syscall.h>
22 #else 
23 # if HAVE_SYS_SYSCALL_H
24 #  include <sys/syscall.h>
25 # endif
26 #endif
27 #include <dirent.h>
28
29 #include <libcitadel.h>
30
31 #include "include/ctdl_module.h"
32 HashList *CfgTypeHash = NULL;
33 HashList *RoomConfigs = NULL;
34 /*-----------------------------------------------------------------------------*
35  *                       Per room network configs                              *
36  *-----------------------------------------------------------------------------*/
37 void RegisterRoomCfgType(const char* Name, long len, RoomNetCfg eCfg, CfgLineParser p, int uniq,  int nSegments, CfgLineSerializer s, CfgLineDeAllocator d)
38 {
39         CfgLineType *pCfg;
40
41         pCfg = (CfgLineType*) malloc(sizeof(CfgLineType));
42         pCfg->Parser = p;
43         pCfg->Serializer = s;
44         pCfg->C = eCfg;
45         pCfg->Str.Key = Name;
46         pCfg->Str.len = len;
47         pCfg->IsSingleLine = uniq;
48         pCfg->nSegments = nSegments;
49         if (CfgTypeHash == NULL)
50                 CfgTypeHash = NewHash(1, NULL);
51         Put(CfgTypeHash, Name, len, pCfg, NULL);
52 }
53
54
55 const CfgLineType *GetCfgTypeByStr(const char *Key, long len)
56 {
57         void *pv;
58         
59         if (GetHash(CfgTypeHash, Key, len, &pv) && (pv != NULL))
60         {
61                 return (const CfgLineType *) pv;
62         }
63         else
64         {
65                 return NULL;
66         }
67 }
68
69 const CfgLineType *GetCfgTypeByEnum(RoomNetCfg eCfg, HashPos *It)
70 {
71         const char *Key;
72         long len;
73         void *pv;
74         CfgLineType *pCfg;
75
76         RewindHashPos(CfgTypeHash, It, 1);
77         while (GetNextHashPos(CfgTypeHash, It, &len, &Key, &pv) && (pv != NULL))
78         {
79                 pCfg = (CfgLineType*) pv;
80                 if (pCfg->C == eCfg)
81                         return pCfg;
82         }
83         return NULL;
84 }
85 void ParseGeneric(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG)
86 {
87         RoomNetCfgLine *nptr;
88         int i;
89
90         nptr = (RoomNetCfgLine *)
91                 malloc(sizeof(RoomNetCfgLine));
92         nptr->next = OneRNCFG->NetConfigs[ThisOne->C];
93         nptr->Value = malloc(sizeof(StrBuf*) * ThisOne->nSegments);
94         memset(nptr->Value, 0, sizeof(StrBuf*) * ThisOne->nSegments);
95         if (ThisOne->nSegments == 1)
96         {
97                 nptr->Value[0] = NewStrBufPlain(LinePos, StrLength(Line) - ( LinePos - ChrPtr(Line)) );
98         }
99         else for (i = 0; i < ThisOne->nSegments; i++)
100         {
101                 nptr->Value[i] = NewStrBufPlain(NULL, StrLength(Line) - ( LinePos - ChrPtr(Line)) );
102                 StrBufExtract_NextToken(nptr->Value[i], Line, &LinePos, '|');
103         }
104
105         OneRNCFG->NetConfigs[ThisOne->C] = nptr;
106 }
107
108 void SerializeGeneric(const CfgLineType *ThisOne, StrBuf *OutputBuffer, OneRoomNetCfg *OneRNCFG, RoomNetCfgLine *data)
109 {
110         int i;
111
112         StrBufAppendBufPlain(OutputBuffer, CKEY(ThisOne->Str), 0);
113         for (i = 0; i < ThisOne->nSegments; i++)
114         {
115                 StrBufAppendBuf(OutputBuffer, data->Value[i], 0);
116                 if (i + 1 < ThisOne->nSegments)
117                         StrBufAppendBufPlain(OutputBuffer, HKEY("|"), 0);
118         }
119         StrBufAppendBufPlain(OutputBuffer, HKEY("\n"), 0);
120 }
121
122 void DeleteGenericCfgLine(const CfgLineType *ThisOne, RoomNetCfgLine **data)
123 {
124         int i;
125
126         for (i = 0; i < ThisOne->nSegments; i++)
127         {
128                 FreeStrBuf(&(*data)->Value[i]);
129         }
130         free ((*data)->Value);
131         free(*data);
132         *data = NULL;
133 }
134 int ReadRoomNetConfigFile(OneRoomNetCfg **pOneRNCFG, char *filename)
135 {
136         int fd;
137         const char *ErrStr = NULL;
138         const char *Pos;
139         const CfgLineType *pCfg;
140         StrBuf *Line;
141         StrBuf *InStr;
142         OneRoomNetCfg *OneRNCFG;
143
144         fd = open(filename, O_NONBLOCK|O_RDONLY);
145         if (fd == -1) {
146                 *pOneRNCFG = NULL;
147                 return 0;
148         }
149         OneRNCFG = malloc(sizeof(OneRoomNetCfg));
150         memset(OneRNCFG, 0, sizeof(OneRoomNetCfg));
151         *pOneRNCFG = OneRNCFG;
152         Line = NewStrBuf();
153
154         while (StrBufTCP_read_line(Line, &fd, 0, &ErrStr) >= 0) {
155                 if (StrLength(Line) == 0)
156                         continue;
157                 Pos = NULL;
158                 InStr = NewStrBufPlain(NULL, StrLength(Line));
159                 StrBufExtract_NextToken(InStr, Line, &Pos, '|');
160
161                 pCfg = GetCfgTypeByStr(SKEY(InStr));
162                 if (pCfg != NULL)
163                 {
164                         pCfg->Parser(pCfg, Line, Pos, OneRNCFG);
165                 }
166                 else
167                 {
168                         if (OneRNCFG->misc == NULL)
169                         {
170                                 OneRNCFG->misc = NewStrBufDup(Line);
171                         }
172                         else
173                         {
174                                 if(StrLength(OneRNCFG->misc) > 0)
175                                         StrBufAppendBufPlain(OneRNCFG->misc, HKEY("\n"), 0);
176                                 StrBufAppendBuf(OneRNCFG->misc, Line, 0);
177                         }
178                 }
179         }
180         if (fd > 0)
181                 close(fd);
182         FreeStrBuf(&InStr);
183         FreeStrBuf(&Line);
184         return 1;
185 }
186
187 int SaveRoomNetConfigFile(OneRoomNetCfg *OneRNCFG, char *filename)
188 {
189         RoomNetCfg eCfg;
190         StrBuf *Cfg;
191         char tempfilename[PATH_MAX];
192         int TmpFD;
193         long len;
194         time_t unixtime;
195         struct timeval tv;
196         long reltid; /* if we don't have SYS_gettid, use "random" value */
197         StrBuf *OutBuffer;
198         int rc;
199         HashPos *CfgIt;
200
201         len = strlen(filename);
202         memcpy(tempfilename, filename, len + 1);
203
204 #if defined(HAVE_SYONERNCFGALL_H) && defined (SYS_gettid)
205         reltid = syOneRNCFGall(SYS_gettid);
206 #endif
207         gettimeofday(&tv, NULL);
208         /* Promote to time_t; types differ on some OSes (like darwin) */
209         unixtime = tv.tv_sec;
210
211         sprintf(tempfilename + len, ".%ld-%ld", reltid, unixtime);
212         errno = 0;
213         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
214         Cfg = NewStrBuf();
215         if ((TmpFD < 0) || (errno != 0)) {
216                 syslog(LOG_CRIT, "ERROR: cannot open %s: %s\n",
217                         filename, strerror(errno));
218                 unlink(tempfilename);
219                 return 0;
220         }
221         else {
222                 CfgIt = GetNewHashPos(CfgTypeHash, 1);
223                 fchown(TmpFD, config.c_ctdluid, 0);
224                 for (eCfg = subpending; eCfg < maxRoomNetCfg; eCfg ++)
225                 {
226                         const CfgLineType *pCfg;
227                         pCfg = GetCfgTypeByEnum(eCfg, CfgIt);
228                         if (pCfg->IsSingleLine)
229                         {
230                                 pCfg->Serializer(pCfg, OutBuffer, OneRNCFG, NULL);
231                         }
232                         else
233                         {
234                                 RoomNetCfgLine *pName = OneRNCFG->NetConfigs[pCfg->C];
235                                 while (pName != NULL)
236                                 {
237                                         pCfg->Serializer(pCfg, OutBuffer, OneRNCFG, pName);
238                                         pName = pName->next;
239                                 }
240                                 
241                                 
242                         }
243
244                 }
245                 DeleteHashPos(&CfgIt);
246
247
248                 if (OneRNCFG->misc != NULL) {
249                         StrBufAppendBuf(OutBuffer, OneRNCFG->misc, 0);
250                 }
251
252                 rc = write(TmpFD, ChrPtr(OutBuffer), StrLength(OutBuffer));
253                 if ((rc >=0 ) && (rc == StrLength(Cfg))) 
254                 {
255                         close(TmpFD);
256                         rename(tempfilename, filename);
257                         rc = 1;
258                 }
259                 else {
260                         syslog(LOG_EMERG, 
261                                       "unable to write %s; [%s]; not enough space on the disk?\n", 
262                                       tempfilename, 
263                                       strerror(errno));
264                         close(TmpFD);
265                         unlink(tempfilename);
266                         rc = 0;
267                 }
268                 FreeStrBuf(&OutBuffer);
269                 
270         }
271         return rc;
272 }
273
274
275 void vFreeRoomNetworkStruct(void *vOneRoomNetCfg)
276 {
277         RoomNetCfg eCfg;
278         HashPos *CfgIt;
279         OneRoomNetCfg *OneRNCFG;
280
281         OneRNCFG = (OneRoomNetCfg*)vOneRoomNetCfg;
282         CfgIt = GetNewHashPos(CfgTypeHash, 1);
283         for (eCfg = subpending; eCfg < maxRoomNetCfg; eCfg ++)
284         {
285                 const CfgLineType *pCfg;
286                 RoomNetCfgLine *pNext, *pName;
287                 
288                 pCfg = GetCfgTypeByEnum(eCfg, CfgIt);
289                 pName= OneRNCFG->NetConfigs[pCfg->C];
290                 while (pName != NULL)
291                 {
292                         pNext = pName->next;
293                         pCfg->DeAllocator(pCfg, &pName);
294                         pName = pNext;
295                 }
296         }
297         DeleteHashPos(&CfgIt);
298
299         FreeStrBuf(&OneRNCFG->Sender);
300         FreeStrBuf(&OneRNCFG->RoomInfo);
301         FreeStrBuf(&OneRNCFG->misc);
302         free(OneRNCFG);
303 }
304 void FreeRoomNetworkStruct(OneRoomNetCfg **pOneRNCFG)
305 {
306         vFreeRoomNetworkStruct(*pOneRNCFG);
307         *pOneRNCFG=NULL;
308 }
309
310
311 void LoadAllNetConfigs(void)
312 {
313         DIR *filedir = NULL;
314         struct dirent *d;
315         struct dirent *filedir_entry;
316         int d_type = 0;
317         int d_namelen;
318         long RoomNumber;
319         OneRoomNetCfg *OneRNCFG;
320         int IsNumOnly;
321         const char *pch;
322         char path[PATH_MAX];
323
324         RoomConfigs = NewHash(1, NULL);
325         filedir = opendir (ctdl_netcfg_dir);
326         if (filedir == NULL) {
327                 return ; /// todo: panic!
328         }
329
330         d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 1);
331         if (d == NULL) {
332                 closedir(filedir);
333                 return ;
334         }
335
336         while ((readdir_r(filedir, d, &filedir_entry) == 0) &&
337                (filedir_entry != NULL))
338         {
339 #ifdef _DIRENT_HAVE_D_NAMLEN
340                 d_namelen = filedir_entry->d_namelen;
341 #else
342                 d_namelen = strlen(filedir_entry->d_name);
343 #endif
344
345 #ifdef _DIRENT_HAVE_D_TYPE
346                 d_type = filedir_entry->d_type;
347 #else
348
349 #ifndef DT_UNKNOWN
350 #define DT_UNKNOWN     0
351 #define DT_DIR         4
352 #define DT_REG         8
353 #define DT_LNK         10
354
355 #define IFTODT(mode)   (((mode) & 0170000) >> 12)
356 #define DTTOIF(dirtype)        ((dirtype) << 12)
357 #endif
358                 d_type = DT_UNKNOWN;
359 #endif
360                 if ((d_namelen > 1) && filedir_entry->d_name[d_namelen - 1] == '~')
361                         continue; /* Ignore backup files... */
362
363                 if ((d_namelen == 1) && 
364                     (filedir_entry->d_name[0] == '.'))
365                         continue;
366
367                 if ((d_namelen == 2) && 
368                     (filedir_entry->d_name[0] == '.') &&
369                     (filedir_entry->d_name[1] == '.'))
370                         continue;
371
372                 snprintf(path, PATH_MAX, "%s/%s", 
373                          ctdl_netcfg_dir, filedir_entry->d_name);
374
375                 if (d_type == DT_UNKNOWN) {
376                         struct stat s;
377                         if (lstat(path, &s) == 0) {
378                                 d_type = IFTODT(s.st_mode);
379                         }
380                 }
381
382                 switch (d_type)
383                 {
384                 case DT_DIR:
385                         break;
386                 case DT_LNK: /* TODO: check whether its a file or a directory */
387                 case DT_REG:
388                         IsNumOnly = 1;
389                         pch = filedir_entry->d_name;
390                         while (*pch != '\0')
391                         {
392                                 if (!isdigit(*pch))
393                                 {
394                                         IsNumOnly = 0;
395                                 }
396                                 pch ++;
397                         }
398                         if (IsNumOnly)
399                         {
400                                 RoomNumber = atol(filedir_entry->d_name);
401                                 ReadRoomNetConfigFile(&OneRNCFG, path);
402
403                                 if (OneRNCFG != NULL)
404                                         Put(RoomConfigs, LKEY(RoomNumber), OneRNCFG, vFreeRoomNetworkStruct);
405                                     
406                                 /* syslog(9, "[%s | %s]\n", ChrPtr(OneWebName), ChrPtr(FileName)); */
407                         }
408                         break;
409                 default:
410                         break;
411                 }
412
413
414         }
415         free(d);
416         closedir(filedir);
417 }
418
419
420 /*-----------------------------------------------------------------------------*
421  *              Per room network configs : exchange with client                *
422  *-----------------------------------------------------------------------------*/
423 void cmd_gnet(char *argbuf)
424 {
425         char filename[PATH_MAX];
426         char buf[SIZ];
427         FILE *fp;
428
429
430         if (!IsEmptyStr(argbuf))
431         {
432                 if (CtdlAccessCheck(ac_aide)) return;
433                 if (strcmp(argbuf, FILE_MAILALIAS))
434                 {
435                         cprintf("%d No such file or directory\n",
436                                 ERROR + INTERNAL_ERROR);
437                         return;
438                 }
439                 safestrncpy(filename, file_mail_aliases, sizeof(filename));
440                 cprintf("%d Settings for <%s>\n",
441                         LISTING_FOLLOWS,
442                         filename);
443         }
444         else
445         {
446                 if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
447                         /* users can edit the netconfigs for their own mailbox rooms */
448                 }
449                 else if (CtdlAccessCheck(ac_room_aide)) return;
450                 
451                 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
452                 cprintf("%d Network settings for room #%ld <%s>\n",
453                         LISTING_FOLLOWS,
454                         CC->room.QRnumber, CC->room.QRname);
455         }
456
457         fp = fopen(filename, "r");
458         if (fp != NULL) {
459                 while (fgets(buf, sizeof buf, fp) != NULL) {
460                         buf[strlen(buf)-1] = 0;
461                         cprintf("%s\n", buf);
462                 }
463                 fclose(fp);
464         }
465
466         cprintf("000\n");
467 }
468
469 #define nForceAliases 5
470 const ConstStr ForceAliases[nForceAliases] = {
471         {HKEY("bbs,")},
472         {HKEY("root,")},
473         {HKEY("Auto,")},
474         {HKEY("postmaster,")},
475         {HKEY("abuse,")}
476 };
477
478 void cmd_snet(char *argbuf) {
479         char tempfilename[PATH_MAX];
480         char filename[PATH_MAX];
481         int TmpFD;
482         StrBuf *Line;
483         struct stat StatBuf;
484         long len;
485         int rc;
486         int IsMailAlias = 0;
487         int MailAliasesFound[nForceAliases];
488
489         unbuffer_output();
490
491         if (!IsEmptyStr(argbuf))
492         {
493                 if (CtdlAccessCheck(ac_aide)) return;
494                 if (strcmp(argbuf, FILE_MAILALIAS))
495                 {
496                         cprintf("%d No such file or directory\n",
497                                 ERROR + INTERNAL_ERROR);
498                         return;
499                 }
500                 len = safestrncpy(filename, file_mail_aliases, sizeof(filename));
501                 memset(MailAliasesFound, 0, sizeof(MailAliasesFound));
502                 memcpy(tempfilename, filename, len + 1);
503                 IsMailAlias = 1;
504         }
505         else
506         {
507                 if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
508                         /* users can edit the netconfigs for their own mailbox rooms */
509                 }
510                 else if (CtdlAccessCheck(ac_room_aide)) return;
511                 
512                 len = assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
513                 memcpy(tempfilename, filename, len + 1);
514         }
515         memset(&StatBuf, 0, sizeof(struct stat));
516         if ((stat(filename, &StatBuf)  == -1) || (StatBuf.st_size == 0))
517                 StatBuf.st_size = 80; /* Not there or empty? guess 80 chars line. */
518
519         sprintf(tempfilename + len, ".%d", CC->cs_pid);
520         errno = 0;
521         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
522
523         if ((TmpFD > 0) && (errno == 0))
524         {
525                 char *tmp = malloc(StatBuf.st_size * 2);
526                 memset(tmp, ' ', StatBuf.st_size * 2);
527                 rc = write(TmpFD, tmp, StatBuf.st_size * 2);
528                 free(tmp);
529                 if ((rc <= 0) || (rc != StatBuf.st_size * 2))
530                 {
531                         close(TmpFD);
532                         cprintf("%d Unable to allocate the space required for %s: %s\n",
533                                 ERROR + INTERNAL_ERROR,
534                                 tempfilename,
535                                 strerror(errno));
536                         unlink(tempfilename);
537                         return;
538                 }       
539                 lseek(TmpFD, SEEK_SET, 0);
540         }
541         else {
542                 cprintf("%d Unable to allocate the space required for %s: %s\n",
543                         ERROR + INTERNAL_ERROR,
544                         tempfilename,
545                         strerror(errno));
546                 unlink(tempfilename);
547                 return;
548         }
549         Line = NewStrBuf();
550
551         cprintf("%d %s\n", SEND_LISTING, tempfilename);
552
553         len = 0;
554         while (rc = CtdlClientGetLine(Line), 
555                (rc >= 0))
556         {
557                 if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
558                         break;
559                 if (IsMailAlias)
560                 {
561                         int i;
562
563                         for (i = 0; i < nForceAliases; i++)
564                         {
565                                 if ((!MailAliasesFound[i]) && 
566                                     (strncmp(ForceAliases[i].Key, 
567                                              ChrPtr(Line),
568                                              ForceAliases[i].len) == 0)
569                                         )
570                                     {
571                                             MailAliasesFound[i] = 1;
572                                             break;
573                                     }
574                         }
575                 }
576
577                 StrBufAppendBufPlain(Line, HKEY("\n"), 0);
578                 write(TmpFD, ChrPtr(Line), StrLength(Line));
579                 len += StrLength(Line);
580         }
581         FreeStrBuf(&Line);
582         ftruncate(TmpFD, len);
583         close(TmpFD);
584
585         if (IsMailAlias)
586         {
587                 int i, state;
588                 /*
589                  * Sanity check whether all aliases required by the RFCs were set
590                  * else bail out.
591                  */
592                 state = 1;
593                 for (i = 0; i < nForceAliases; i++)
594                 {
595                         if (!MailAliasesFound[i]) 
596                                 state = 0;
597                 }
598                 if (state == 0)
599                 {
600                         cprintf("%d won't do this - you're missing an RFC required alias.\n",
601                                 ERROR + INTERNAL_ERROR);
602                         unlink(tempfilename);
603                         return;
604                 }
605         }
606
607         /* Now copy the temp file to its permanent location.
608          * (We copy instead of link because they may be on different filesystems)
609          */
610         begin_critical_section(S_NETCONFIGS);
611         rename(tempfilename, filename);
612         end_critical_section(S_NETCONFIGS);
613 }
614
615
616 /*-----------------------------------------------------------------------------*
617  *                       Per node network configs                              *
618  *-----------------------------------------------------------------------------*/
619 void DeleteCtdlNodeConf(void *vNode)
620 {
621         CtdlNodeConf *Node = (CtdlNodeConf*) vNode;
622         FreeStrBuf(&Node->NodeName);
623         FreeStrBuf(&Node->Secret);
624         FreeStrBuf(&Node->Host);
625         FreeStrBuf(&Node->Port);
626         free(Node);
627 }
628
629 CtdlNodeConf *NewNode(StrBuf *SerializedNode)
630 {
631         const char *Pos = NULL;
632         CtdlNodeConf *Node;
633
634         /* we need at least 4 pipes and some other text so its invalid. */
635         if (StrLength(SerializedNode) < 8)
636                 return NULL;
637         Node = (CtdlNodeConf *) malloc(sizeof(CtdlNodeConf));
638
639         Node->DeleteMe = 0;
640
641         Node->NodeName=NewStrBuf();
642         StrBufExtract_NextToken(Node->NodeName, SerializedNode, &Pos, '|');
643
644         Node->Secret=NewStrBuf();
645         StrBufExtract_NextToken(Node->Secret, SerializedNode, &Pos, '|');
646
647         Node->Host=NewStrBuf();
648         StrBufExtract_NextToken(Node->Host, SerializedNode, &Pos, '|');
649
650         Node->Port=NewStrBuf();
651         StrBufExtract_NextToken(Node->Port, SerializedNode, &Pos, '|');
652         return Node;
653 }
654
655
656 /*
657  * Load or refresh the Citadel network (IGnet) configuration for this node.
658  */
659 HashList* CtdlLoadIgNetCfg(void)
660 {
661         const char *LinePos;
662         char       *Cfg;
663         StrBuf     *Buf;
664         StrBuf     *LineBuf;
665         HashList   *Hash;
666         CtdlNodeConf   *Node;
667
668         Cfg =  CtdlGetSysConfig(IGNETCFG);
669         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
670                 if (Cfg != NULL)
671                         free(Cfg);
672                 return NULL;
673         }
674
675         Hash = NewHash(1, NULL);
676         Buf = NewStrBufPlain(Cfg, -1);
677         free(Cfg);
678         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
679         LinePos = NULL;
680         do
681         {
682                 StrBufSipLine(LineBuf, Buf, &LinePos);
683                 if (StrLength(LineBuf) != 0) {
684                         Node = NewNode(LineBuf);
685                         if (Node != NULL) {
686                                 Put(Hash, SKEY(Node->NodeName), Node, DeleteCtdlNodeConf);
687                         }
688                 }
689         } while (LinePos != StrBufNOTNULL);
690         FreeStrBuf(&Buf);
691         FreeStrBuf(&LineBuf);
692         return Hash;
693 }
694
695
696 int is_recipient(OneRoomNetCfg *RNCfg, const char *Name)
697 {
698         const RoomNetCfg RecipientCfgs[] = {
699                 listrecp,
700                 digestrecp,
701                 participate,
702                 maxRoomNetCfg
703         };
704         int i;
705         RoomNetCfgLine *nptr;
706         size_t len;
707         
708         len = strlen(Name);
709         i = 0;
710         while (RecipientCfgs[i] != maxRoomNetCfg)
711         {
712                 nptr = RNCfg->NetConfigs[RecipientCfgs[i]];
713                 
714                 while (nptr != NULL)
715                 {
716                         if ((StrLength(nptr->Value[0]) == len) && 
717                             (!strcmp(Name, ChrPtr(nptr->Value[0]))))
718                         {
719                                 return 1;
720                         }
721                         nptr = nptr->next;
722                 }
723         }
724         return 0;
725 }
726
727
728
729 int CtdlNetconfigCheckRoomaccess(
730         char *errmsgbuf, 
731         size_t n,
732         const char* RemoteIdentifier)
733 {
734         OneRoomNetCfg *RNCfg;
735         char filename[SIZ];
736         int found;
737
738         if (RemoteIdentifier == NULL)
739         {
740                 snprintf(errmsgbuf, n, "Need sender to permit access.");
741                 return (ERROR + USERNAME_REQUIRED);
742         }
743
744         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
745         begin_critical_section(S_NETCONFIGS);
746         if (!ReadRoomNetConfigFile(&RNCfg, filename))
747         {
748                 end_critical_section(S_NETCONFIGS);
749                 snprintf(errmsgbuf, n,
750                          "This mailing list only accepts posts from subscribers.");
751                 return (ERROR + NO_SUCH_USER);
752         }
753         end_critical_section(S_NETCONFIGS);
754         found = is_recipient (RNCfg, RemoteIdentifier);
755         vFreeRoomNetworkStruct(&RNCfg);
756         if (found) {
757                 return (0);
758         }
759         else {
760                 snprintf(errmsgbuf, n,
761                          "This mailing list only accepts posts from subscribers.");
762                 return (ERROR + NO_SUCH_USER);
763         }
764 }
765
766
767
768 /*
769  * cmd_netp() - authenticate to the server as another Citadel node polling
770  *            for network traffic
771  */
772 void cmd_netp(char *cmdbuf)
773 {
774         struct CitContext *CCC = CC;
775         HashList *working_ignetcfg;
776         char *node;
777         StrBuf *NodeStr;
778         long nodelen;
779         int v;
780         long lens[2];
781         const char *strs[2];
782
783         const StrBuf *secret = NULL;
784         const StrBuf *nexthop = NULL;
785         char err_buf[SIZ] = "";
786
787         /* Authenticate */
788         node = CCC->curr_user;
789         nodelen = extract_token(CCC->curr_user, cmdbuf, 0, '|', sizeof CCC->curr_user);
790         NodeStr = NewStrBufPlain(node, nodelen);
791         /* load the IGnet Configuration to check node validity */
792         working_ignetcfg = CtdlLoadIgNetCfg();
793         v = CtdlIsValidNode(&nexthop, &secret, NodeStr, working_ignetcfg, NULL);
794         if (v != 0) {
795                 snprintf(err_buf, sizeof err_buf,
796                         "An unknown Citadel server called \"%s\" attempted to connect from %s [%s].\n",
797                         node, CCC->cs_host, CCC->cs_addr
798                 );
799                 syslog(LOG_WARNING, "%s", err_buf);
800                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
801
802                 strs[0] = CCC->cs_addr;
803                 lens[0] = strlen(CCC->cs_addr);
804                 
805                 strs[1] = "SRV_UNKNOWN";
806                 lens[1] = sizeof("SRV_UNKNOWN" - 1);
807
808                 CtdlAideFPMessage(
809                         err_buf,
810                         "IGNet Networking.",
811                         2, strs, (long*) &lens);
812
813                 DeleteHash(&working_ignetcfg);
814                 FreeStrBuf(&NodeStr);
815                 return;
816         }
817
818         extract_token(CCC->user.password, cmdbuf, 1, '|', sizeof CCC->user.password);
819         if (strcasecmp(CCC->user.password, ChrPtr(secret))) {
820                 snprintf(err_buf, sizeof err_buf,
821                         "A Citadel server at %s [%s] failed to authenticate as network node \"%s\".\n",
822                         CCC->cs_host, CCC->cs_addr, node
823                 );
824                 syslog(LOG_WARNING, "%s", err_buf);
825                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
826
827                 strs[0] = CCC->cs_addr;
828                 lens[0] = strlen(CCC->cs_addr);
829                 
830                 strs[1] = "SRV_PW";
831                 lens[1] = sizeof("SRV_PW" - 1);
832
833                 CtdlAideFPMessage(
834                         err_buf,
835                         "IGNet Networking.",
836                         2, strs, (long*) &lens);
837
838                 DeleteHash(&working_ignetcfg);
839                 FreeStrBuf(&NodeStr);
840                 return;
841         }
842
843         if (CtdlNetworkTalkingTo(node, nodelen, NTT_CHECK)) {
844                 syslog(LOG_WARNING, "Duplicate session for network node <%s>", node);
845                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
846                 DeleteHash(&working_ignetcfg);
847                 FreeStrBuf(&NodeStr);
848                 return;
849         }
850         nodelen = safestrncpy(CCC->net_node, node, sizeof CCC->net_node);
851         CtdlNetworkTalkingTo(CCC->net_node, nodelen, NTT_ADD);
852         syslog(LOG_NOTICE, "Network node <%s> logged in from %s [%s]\n",
853                 CCC->net_node, CCC->cs_host, CCC->cs_addr
854         );
855         cprintf("%d authenticated as network node '%s'\n", CIT_OK, CCC->net_node);
856         DeleteHash(&working_ignetcfg);
857         FreeStrBuf(&NodeStr);
858 }
859
860
861 /*-----------------------------------------------------------------------------*
862  *                 Network maps: evaluate other nodes                          *
863  *-----------------------------------------------------------------------------*/
864
865 void DeleteNetMap(void *vNetMap)
866 {
867         CtdlNetMap *TheNetMap = (CtdlNetMap*) vNetMap;
868         FreeStrBuf(&TheNetMap->NodeName);
869         FreeStrBuf(&TheNetMap->NextHop);
870         free(TheNetMap);
871 }
872
873 CtdlNetMap *NewNetMap(StrBuf *SerializedNetMap)
874 {
875         const char *Pos = NULL;
876         CtdlNetMap *NM;
877
878         /* we need at least 3 pipes and some other text so its invalid. */
879         if (StrLength(SerializedNetMap) < 6)
880                 return NULL;
881         NM = (CtdlNetMap *) malloc(sizeof(CtdlNetMap));
882
883         NM->NodeName=NewStrBuf();
884         StrBufExtract_NextToken(NM->NodeName, SerializedNetMap, &Pos, '|');
885
886         NM->lastcontact = StrBufExtractNext_long(SerializedNetMap, &Pos, '|');
887
888         NM->NextHop=NewStrBuf();
889         StrBufExtract_NextToken(NM->NextHop, SerializedNetMap, &Pos, '|');
890
891         return NM;
892 }
893
894 HashList* CtdlReadNetworkMap(void)
895 {
896         const char *LinePos;
897         char       *Cfg;
898         StrBuf     *Buf;
899         StrBuf     *LineBuf;
900         HashList   *Hash;
901         CtdlNetMap     *TheNetMap;
902
903         Cfg =  CtdlGetSysConfig(IGNETMAP);
904         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
905                 if (Cfg != NULL)
906                         free(Cfg);
907                 return NULL;
908         }
909
910         Hash = NewHash(1, NULL);
911         Buf = NewStrBufPlain(Cfg, -1);
912         free(Cfg);
913         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
914         LinePos = NULL;
915         while (StrBufSipLine(Buf, LineBuf, &LinePos))
916         {
917                 TheNetMap = NewNetMap(LineBuf);
918                 if (TheNetMap != NULL) { /* TODO: is the NodeName Uniq? */
919                         Put(Hash, SKEY(TheNetMap->NodeName), TheNetMap, DeleteNetMap);
920                 }
921         }
922         FreeStrBuf(&Buf);
923         FreeStrBuf(&LineBuf);
924         return Hash;
925 }
926
927 StrBuf *CtdlSerializeNetworkMap(HashList *Map)
928 {
929         void *vMap;
930         const char *key;
931         long len;
932         StrBuf *Ret = NewStrBuf();
933         HashPos *Pos = GetNewHashPos(Map, 0);
934
935         while (GetNextHashPos(Map, Pos, &len, &key, &vMap))
936         {
937                 CtdlNetMap *pMap = (CtdlNetMap*) vMap;
938                 StrBufAppendBuf(Ret, pMap->NodeName, 0);
939                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
940
941                 StrBufAppendPrintf(Ret, "%ld", pMap->lastcontact, 0);
942                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
943
944                 StrBufAppendBuf(Ret, pMap->NextHop, 0);
945                 StrBufAppendBufPlain(Ret, HKEY("\n"), 0);
946         }
947         DeleteHashPos(&Pos);
948         return Ret;
949 }
950
951
952 /*
953  * Learn topology from path fields
954  */
955 void NetworkLearnTopology(char *node, char *path, HashList *the_netmap, int *netmap_changed)
956 {
957         CtdlNetMap *pNM = NULL;
958         void *vptr;
959         char nexthop[256];
960         CtdlNetMap *nmptr;
961
962         if (GetHash(the_netmap, node, strlen(node), &vptr) && 
963             (vptr != NULL))/* TODO: is the NodeName Uniq? */
964         {
965                 pNM = (CtdlNetMap*)vptr;
966                 extract_token(nexthop, path, 0, '!', sizeof nexthop);
967                 if (!strcmp(nexthop, ChrPtr(pNM->NextHop))) {
968                         pNM->lastcontact = time(NULL);
969                         (*netmap_changed) ++;
970                         return;
971                 }
972         }
973
974         /* If we got here then it's not in the map, so add it. */
975         nmptr = (CtdlNetMap *) malloc(sizeof (CtdlNetMap));
976         nmptr->NodeName = NewStrBufPlain(node, -1);
977         nmptr->lastcontact = time(NULL);
978         nmptr->NextHop = NewStrBuf ();
979         StrBufExtract_tokenFromStr(nmptr->NextHop, path, strlen(path), 0, '!');
980         /* TODO: is the NodeName Uniq? */
981         Put(the_netmap, SKEY(nmptr->NodeName), nmptr, DeleteNetMap);
982         (*netmap_changed) ++;
983 }
984
985
986 /*
987  * Check the network map and determine whether the supplied node name is
988  * valid.  If it is not a neighbor node, supply the name of a neighbor node
989  * which is the next hop.  If it *is* a neighbor node, we also fill in the
990  * shared secret.
991  */
992 int CtdlIsValidNode(const StrBuf **nexthop,
993                     const StrBuf **secret,
994                     StrBuf *node,
995                     HashList *IgnetCfg,
996                     HashList *the_netmap)
997 {
998         void *vNetMap;
999         void *vNodeConf;
1000         CtdlNodeConf *TheNode;
1001         CtdlNetMap *TheNetMap;
1002
1003         if (StrLength(node) == 0) {
1004                 return(-1);
1005         }
1006
1007         /*
1008          * First try the neighbor nodes
1009          */
1010         if (GetCount(IgnetCfg) == 0) {
1011                 syslog(LOG_INFO, "IgnetCfg is empty!\n");
1012                 if (nexthop != NULL) {
1013                         *nexthop = NULL;
1014                 }
1015                 return(-1);
1016         }
1017
1018         /* try to find a neigbour with the name 'node' */
1019         if (GetHash(IgnetCfg, SKEY(node), &vNodeConf) && 
1020             (vNodeConf != NULL))
1021         {
1022                 TheNode = (CtdlNodeConf*)vNodeConf;
1023                 if (secret != NULL)
1024                         *secret = TheNode->Secret;
1025                 return 0;               /* yup, it's a direct neighbor */
1026         }
1027
1028         /*
1029          * If we get to this point we have to see if we know the next hop
1030          *//* TODO: is the NodeName Uniq? */
1031         if ((GetCount(the_netmap) > 0) &&
1032             (GetHash(the_netmap, SKEY(node), &vNetMap)))
1033         {
1034                 TheNetMap = (CtdlNetMap*)vNetMap;
1035                 if (nexthop != NULL)
1036                         *nexthop = TheNetMap->NextHop;
1037                 return(0);
1038         }
1039
1040         /*
1041          * If we get to this point, the supplied node name is bogus.
1042          */
1043         syslog(LOG_ERR, "Invalid node name <%s>\n", ChrPtr(node));
1044         return(-1);
1045 }
1046
1047
1048
1049
1050 /*
1051  * Module entry point
1052  */
1053 CTDL_MODULE_INIT(netconfig)
1054 {
1055         if (!threading)
1056         {
1057                 LoadAllNetConfigs ();
1058                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1059                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1060                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1061         }
1062         return "netconfig";
1063 }