e6e0ea8a2efba772e90317c95e957de93f98a3bc
[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 const OneRoomNetCfg* CtdlGetNetCfgForRoom(long QRNumber)
311 {
312         void *pv;
313         GetHash(RoomConfigs, LKEY(QRNumber), &pv);
314         return (OneRoomNetCfg*)pv;
315 }
316
317
318 void LoadAllNetConfigs(void)
319 {
320         DIR *filedir = NULL;
321         struct dirent *d;
322         struct dirent *filedir_entry;
323         int d_type = 0;
324         int d_namelen;
325         long RoomNumber;
326         OneRoomNetCfg *OneRNCFG;
327         int IsNumOnly;
328         const char *pch;
329         char path[PATH_MAX];
330
331         RoomConfigs = NewHash(1, NULL);
332         filedir = opendir (ctdl_netcfg_dir);
333         if (filedir == NULL) {
334                 return ; /// todo: panic!
335         }
336
337         d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 1);
338         if (d == NULL) {
339                 closedir(filedir);
340                 return ;
341         }
342
343         while ((readdir_r(filedir, d, &filedir_entry) == 0) &&
344                (filedir_entry != NULL))
345         {
346 #ifdef _DIRENT_HAVE_D_NAMLEN
347                 d_namelen = filedir_entry->d_namelen;
348 #else
349                 d_namelen = strlen(filedir_entry->d_name);
350 #endif
351
352 #ifdef _DIRENT_HAVE_D_TYPE
353                 d_type = filedir_entry->d_type;
354 #else
355
356 #ifndef DT_UNKNOWN
357 #define DT_UNKNOWN     0
358 #define DT_DIR         4
359 #define DT_REG         8
360 #define DT_LNK         10
361
362 #define IFTODT(mode)   (((mode) & 0170000) >> 12)
363 #define DTTOIF(dirtype)        ((dirtype) << 12)
364 #endif
365                 d_type = DT_UNKNOWN;
366 #endif
367                 if ((d_namelen > 1) && filedir_entry->d_name[d_namelen - 1] == '~')
368                         continue; /* Ignore backup files... */
369
370                 if ((d_namelen == 1) && 
371                     (filedir_entry->d_name[0] == '.'))
372                         continue;
373
374                 if ((d_namelen == 2) && 
375                     (filedir_entry->d_name[0] == '.') &&
376                     (filedir_entry->d_name[1] == '.'))
377                         continue;
378
379                 snprintf(path, PATH_MAX, "%s/%s", 
380                          ctdl_netcfg_dir, filedir_entry->d_name);
381
382                 if (d_type == DT_UNKNOWN) {
383                         struct stat s;
384                         if (lstat(path, &s) == 0) {
385                                 d_type = IFTODT(s.st_mode);
386                         }
387                 }
388
389                 switch (d_type)
390                 {
391                 case DT_DIR:
392                         break;
393                 case DT_LNK: /* TODO: check whether its a file or a directory */
394                 case DT_REG:
395                         IsNumOnly = 1;
396                         pch = filedir_entry->d_name;
397                         while (*pch != '\0')
398                         {
399                                 if (!isdigit(*pch))
400                                 {
401                                         IsNumOnly = 0;
402                                 }
403                                 pch ++;
404                         }
405                         if (IsNumOnly)
406                         {
407                                 RoomNumber = atol(filedir_entry->d_name);
408                                 ReadRoomNetConfigFile(&OneRNCFG, path);
409
410                                 if (OneRNCFG != NULL)
411                                         Put(RoomConfigs, LKEY(RoomNumber), OneRNCFG, vFreeRoomNetworkStruct);
412                                     
413                                 /* syslog(9, "[%s | %s]\n", ChrPtr(OneWebName), ChrPtr(FileName)); */
414                         }
415                         break;
416                 default:
417                         break;
418                 }
419
420
421         }
422         free(d);
423         closedir(filedir);
424 }
425
426
427 /*-----------------------------------------------------------------------------*
428  *              Per room network configs : exchange with client                *
429  *-----------------------------------------------------------------------------*/
430 void cmd_gnet(char *argbuf)
431 {
432         char filename[PATH_MAX];
433         char buf[SIZ];
434         FILE *fp;
435
436
437         if (!IsEmptyStr(argbuf))
438         {
439                 if (CtdlAccessCheck(ac_aide)) return;
440                 if (strcmp(argbuf, FILE_MAILALIAS))
441                 {
442                         cprintf("%d No such file or directory\n",
443                                 ERROR + INTERNAL_ERROR);
444                         return;
445                 }
446                 safestrncpy(filename, file_mail_aliases, sizeof(filename));
447                 cprintf("%d Settings for <%s>\n",
448                         LISTING_FOLLOWS,
449                         filename);
450         }
451         else
452         {
453                 if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
454                         /* users can edit the netconfigs for their own mailbox rooms */
455                 }
456                 else if (CtdlAccessCheck(ac_room_aide)) return;
457                 
458                 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
459                 cprintf("%d Network settings for room #%ld <%s>\n",
460                         LISTING_FOLLOWS,
461                         CC->room.QRnumber, CC->room.QRname);
462         }
463
464         fp = fopen(filename, "r");
465         if (fp != NULL) {
466                 while (fgets(buf, sizeof buf, fp) != NULL) {
467                         buf[strlen(buf)-1] = 0;
468                         cprintf("%s\n", buf);
469                 }
470                 fclose(fp);
471         }
472
473         cprintf("000\n");
474 }
475
476 #define nForceAliases 5
477 const ConstStr ForceAliases[nForceAliases] = {
478         {HKEY("bbs,")},
479         {HKEY("root,")},
480         {HKEY("Auto,")},
481         {HKEY("postmaster,")},
482         {HKEY("abuse,")}
483 };
484
485 void cmd_snet(char *argbuf) {
486         char tempfilename[PATH_MAX];
487         char filename[PATH_MAX];
488         int TmpFD;
489         StrBuf *Line;
490         struct stat StatBuf;
491         long len;
492         int rc;
493         int IsMailAlias = 0;
494         int MailAliasesFound[nForceAliases];
495
496         unbuffer_output();
497
498         if (!IsEmptyStr(argbuf))
499         {
500                 if (CtdlAccessCheck(ac_aide)) return;
501                 if (strcmp(argbuf, FILE_MAILALIAS))
502                 {
503                         cprintf("%d No such file or directory\n",
504                                 ERROR + INTERNAL_ERROR);
505                         return;
506                 }
507                 len = safestrncpy(filename, file_mail_aliases, sizeof(filename));
508                 memset(MailAliasesFound, 0, sizeof(MailAliasesFound));
509                 memcpy(tempfilename, filename, len + 1);
510                 IsMailAlias = 1;
511         }
512         else
513         {
514                 if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
515                         /* users can edit the netconfigs for their own mailbox rooms */
516                 }
517                 else if (CtdlAccessCheck(ac_room_aide)) return;
518                 
519                 len = assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
520                 memcpy(tempfilename, filename, len + 1);
521         }
522         memset(&StatBuf, 0, sizeof(struct stat));
523         if ((stat(filename, &StatBuf)  == -1) || (StatBuf.st_size == 0))
524                 StatBuf.st_size = 80; /* Not there or empty? guess 80 chars line. */
525
526         sprintf(tempfilename + len, ".%d", CC->cs_pid);
527         errno = 0;
528         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
529
530         if ((TmpFD > 0) && (errno == 0))
531         {
532                 char *tmp = malloc(StatBuf.st_size * 2);
533                 memset(tmp, ' ', StatBuf.st_size * 2);
534                 rc = write(TmpFD, tmp, StatBuf.st_size * 2);
535                 free(tmp);
536                 if ((rc <= 0) || (rc != StatBuf.st_size * 2))
537                 {
538                         close(TmpFD);
539                         cprintf("%d Unable to allocate the space required for %s: %s\n",
540                                 ERROR + INTERNAL_ERROR,
541                                 tempfilename,
542                                 strerror(errno));
543                         unlink(tempfilename);
544                         return;
545                 }       
546                 lseek(TmpFD, SEEK_SET, 0);
547         }
548         else {
549                 cprintf("%d Unable to allocate the space required for %s: %s\n",
550                         ERROR + INTERNAL_ERROR,
551                         tempfilename,
552                         strerror(errno));
553                 unlink(tempfilename);
554                 return;
555         }
556         Line = NewStrBuf();
557
558         cprintf("%d %s\n", SEND_LISTING, tempfilename);
559
560         len = 0;
561         while (rc = CtdlClientGetLine(Line), 
562                (rc >= 0))
563         {
564                 if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
565                         break;
566                 if (IsMailAlias)
567                 {
568                         int i;
569
570                         for (i = 0; i < nForceAliases; i++)
571                         {
572                                 if ((!MailAliasesFound[i]) && 
573                                     (strncmp(ForceAliases[i].Key, 
574                                              ChrPtr(Line),
575                                              ForceAliases[i].len) == 0)
576                                         )
577                                     {
578                                             MailAliasesFound[i] = 1;
579                                             break;
580                                     }
581                         }
582                 }
583
584                 StrBufAppendBufPlain(Line, HKEY("\n"), 0);
585                 write(TmpFD, ChrPtr(Line), StrLength(Line));
586                 len += StrLength(Line);
587         }
588         FreeStrBuf(&Line);
589         ftruncate(TmpFD, len);
590         close(TmpFD);
591
592         if (IsMailAlias)
593         {
594                 int i, state;
595                 /*
596                  * Sanity check whether all aliases required by the RFCs were set
597                  * else bail out.
598                  */
599                 state = 1;
600                 for (i = 0; i < nForceAliases; i++)
601                 {
602                         if (!MailAliasesFound[i]) 
603                                 state = 0;
604                 }
605                 if (state == 0)
606                 {
607                         cprintf("%d won't do this - you're missing an RFC required alias.\n",
608                                 ERROR + INTERNAL_ERROR);
609                         unlink(tempfilename);
610                         return;
611                 }
612         }
613
614         /* Now copy the temp file to its permanent location.
615          * (We copy instead of link because they may be on different filesystems)
616          */
617         begin_critical_section(S_NETCONFIGS);
618         rename(tempfilename, filename);
619         end_critical_section(S_NETCONFIGS);
620 }
621
622
623 /*-----------------------------------------------------------------------------*
624  *                       Per node network configs                              *
625  *-----------------------------------------------------------------------------*/
626 void DeleteCtdlNodeConf(void *vNode)
627 {
628         CtdlNodeConf *Node = (CtdlNodeConf*) vNode;
629         FreeStrBuf(&Node->NodeName);
630         FreeStrBuf(&Node->Secret);
631         FreeStrBuf(&Node->Host);
632         FreeStrBuf(&Node->Port);
633         free(Node);
634 }
635
636 CtdlNodeConf *NewNode(StrBuf *SerializedNode)
637 {
638         const char *Pos = NULL;
639         CtdlNodeConf *Node;
640
641         /* we need at least 4 pipes and some other text so its invalid. */
642         if (StrLength(SerializedNode) < 8)
643                 return NULL;
644         Node = (CtdlNodeConf *) malloc(sizeof(CtdlNodeConf));
645
646         Node->DeleteMe = 0;
647
648         Node->NodeName=NewStrBuf();
649         StrBufExtract_NextToken(Node->NodeName, SerializedNode, &Pos, '|');
650
651         Node->Secret=NewStrBuf();
652         StrBufExtract_NextToken(Node->Secret, SerializedNode, &Pos, '|');
653
654         Node->Host=NewStrBuf();
655         StrBufExtract_NextToken(Node->Host, SerializedNode, &Pos, '|');
656
657         Node->Port=NewStrBuf();
658         StrBufExtract_NextToken(Node->Port, SerializedNode, &Pos, '|');
659         return Node;
660 }
661
662
663 /*
664  * Load or refresh the Citadel network (IGnet) configuration for this node.
665  */
666 HashList* CtdlLoadIgNetCfg(void)
667 {
668         const char *LinePos;
669         char       *Cfg;
670         StrBuf     *Buf;
671         StrBuf     *LineBuf;
672         HashList   *Hash;
673         CtdlNodeConf   *Node;
674
675         Cfg =  CtdlGetSysConfig(IGNETCFG);
676         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
677                 if (Cfg != NULL)
678                         free(Cfg);
679                 return NULL;
680         }
681
682         Hash = NewHash(1, NULL);
683         Buf = NewStrBufPlain(Cfg, -1);
684         free(Cfg);
685         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
686         LinePos = NULL;
687         do
688         {
689                 StrBufSipLine(LineBuf, Buf, &LinePos);
690                 if (StrLength(LineBuf) != 0) {
691                         Node = NewNode(LineBuf);
692                         if (Node != NULL) {
693                                 Put(Hash, SKEY(Node->NodeName), Node, DeleteCtdlNodeConf);
694                         }
695                 }
696         } while (LinePos != StrBufNOTNULL);
697         FreeStrBuf(&Buf);
698         FreeStrBuf(&LineBuf);
699         return Hash;
700 }
701
702
703 int is_recipient(OneRoomNetCfg *RNCfg, const char *Name)
704 {
705         const RoomNetCfg RecipientCfgs[] = {
706                 listrecp,
707                 digestrecp,
708                 participate,
709                 maxRoomNetCfg
710         };
711         int i;
712         RoomNetCfgLine *nptr;
713         size_t len;
714         
715         len = strlen(Name);
716         i = 0;
717         while (RecipientCfgs[i] != maxRoomNetCfg)
718         {
719                 nptr = RNCfg->NetConfigs[RecipientCfgs[i]];
720                 
721                 while (nptr != NULL)
722                 {
723                         if ((StrLength(nptr->Value[0]) == len) && 
724                             (!strcmp(Name, ChrPtr(nptr->Value[0]))))
725                         {
726                                 return 1;
727                         }
728                         nptr = nptr->next;
729                 }
730         }
731         return 0;
732 }
733
734
735
736 int CtdlNetconfigCheckRoomaccess(
737         char *errmsgbuf, 
738         size_t n,
739         const char* RemoteIdentifier)
740 {
741         OneRoomNetCfg *RNCfg;
742         char filename[SIZ];
743         int found;
744
745         if (RemoteIdentifier == NULL)
746         {
747                 snprintf(errmsgbuf, n, "Need sender to permit access.");
748                 return (ERROR + USERNAME_REQUIRED);
749         }
750
751         assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
752         begin_critical_section(S_NETCONFIGS);
753         if (!ReadRoomNetConfigFile(&RNCfg, filename))
754         {
755                 end_critical_section(S_NETCONFIGS);
756                 snprintf(errmsgbuf, n,
757                          "This mailing list only accepts posts from subscribers.");
758                 return (ERROR + NO_SUCH_USER);
759         }
760         end_critical_section(S_NETCONFIGS);
761         found = is_recipient (RNCfg, RemoteIdentifier);
762         vFreeRoomNetworkStruct(&RNCfg);
763         if (found) {
764                 return (0);
765         }
766         else {
767                 snprintf(errmsgbuf, n,
768                          "This mailing list only accepts posts from subscribers.");
769                 return (ERROR + NO_SUCH_USER);
770         }
771 }
772
773
774
775 /*
776  * cmd_netp() - authenticate to the server as another Citadel node polling
777  *            for network traffic
778  */
779 void cmd_netp(char *cmdbuf)
780 {
781         struct CitContext *CCC = CC;
782         HashList *working_ignetcfg;
783         char *node;
784         StrBuf *NodeStr;
785         long nodelen;
786         int v;
787         long lens[2];
788         const char *strs[2];
789
790         const StrBuf *secret = NULL;
791         const StrBuf *nexthop = NULL;
792         char err_buf[SIZ] = "";
793
794         /* Authenticate */
795         node = CCC->curr_user;
796         nodelen = extract_token(CCC->curr_user, cmdbuf, 0, '|', sizeof CCC->curr_user);
797         NodeStr = NewStrBufPlain(node, nodelen);
798         /* load the IGnet Configuration to check node validity */
799         working_ignetcfg = CtdlLoadIgNetCfg();
800         v = CtdlIsValidNode(&nexthop, &secret, NodeStr, working_ignetcfg, NULL);
801         if (v != 0) {
802                 snprintf(err_buf, sizeof err_buf,
803                         "An unknown Citadel server called \"%s\" attempted to connect from %s [%s].\n",
804                         node, CCC->cs_host, CCC->cs_addr
805                 );
806                 syslog(LOG_WARNING, "%s", err_buf);
807                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
808
809                 strs[0] = CCC->cs_addr;
810                 lens[0] = strlen(CCC->cs_addr);
811                 
812                 strs[1] = "SRV_UNKNOWN";
813                 lens[1] = sizeof("SRV_UNKNOWN" - 1);
814
815                 CtdlAideFPMessage(
816                         err_buf,
817                         "IGNet Networking.",
818                         2, strs, (long*) &lens);
819
820                 DeleteHash(&working_ignetcfg);
821                 FreeStrBuf(&NodeStr);
822                 return;
823         }
824
825         extract_token(CCC->user.password, cmdbuf, 1, '|', sizeof CCC->user.password);
826         if (strcasecmp(CCC->user.password, ChrPtr(secret))) {
827                 snprintf(err_buf, sizeof err_buf,
828                         "A Citadel server at %s [%s] failed to authenticate as network node \"%s\".\n",
829                         CCC->cs_host, CCC->cs_addr, node
830                 );
831                 syslog(LOG_WARNING, "%s", err_buf);
832                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
833
834                 strs[0] = CCC->cs_addr;
835                 lens[0] = strlen(CCC->cs_addr);
836                 
837                 strs[1] = "SRV_PW";
838                 lens[1] = sizeof("SRV_PW" - 1);
839
840                 CtdlAideFPMessage(
841                         err_buf,
842                         "IGNet Networking.",
843                         2, strs, (long*) &lens);
844
845                 DeleteHash(&working_ignetcfg);
846                 FreeStrBuf(&NodeStr);
847                 return;
848         }
849
850         if (CtdlNetworkTalkingTo(node, nodelen, NTT_CHECK)) {
851                 syslog(LOG_WARNING, "Duplicate session for network node <%s>", node);
852                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
853                 DeleteHash(&working_ignetcfg);
854                 FreeStrBuf(&NodeStr);
855                 return;
856         }
857         nodelen = safestrncpy(CCC->net_node, node, sizeof CCC->net_node);
858         CtdlNetworkTalkingTo(CCC->net_node, nodelen, NTT_ADD);
859         syslog(LOG_NOTICE, "Network node <%s> logged in from %s [%s]\n",
860                 CCC->net_node, CCC->cs_host, CCC->cs_addr
861         );
862         cprintf("%d authenticated as network node '%s'\n", CIT_OK, CCC->net_node);
863         DeleteHash(&working_ignetcfg);
864         FreeStrBuf(&NodeStr);
865 }
866
867
868 /*-----------------------------------------------------------------------------*
869  *                 Network maps: evaluate other nodes                          *
870  *-----------------------------------------------------------------------------*/
871
872 void DeleteNetMap(void *vNetMap)
873 {
874         CtdlNetMap *TheNetMap = (CtdlNetMap*) vNetMap;
875         FreeStrBuf(&TheNetMap->NodeName);
876         FreeStrBuf(&TheNetMap->NextHop);
877         free(TheNetMap);
878 }
879
880 CtdlNetMap *NewNetMap(StrBuf *SerializedNetMap)
881 {
882         const char *Pos = NULL;
883         CtdlNetMap *NM;
884
885         /* we need at least 3 pipes and some other text so its invalid. */
886         if (StrLength(SerializedNetMap) < 6)
887                 return NULL;
888         NM = (CtdlNetMap *) malloc(sizeof(CtdlNetMap));
889
890         NM->NodeName=NewStrBuf();
891         StrBufExtract_NextToken(NM->NodeName, SerializedNetMap, &Pos, '|');
892
893         NM->lastcontact = StrBufExtractNext_long(SerializedNetMap, &Pos, '|');
894
895         NM->NextHop=NewStrBuf();
896         StrBufExtract_NextToken(NM->NextHop, SerializedNetMap, &Pos, '|');
897
898         return NM;
899 }
900
901 HashList* CtdlReadNetworkMap(void)
902 {
903         const char *LinePos;
904         char       *Cfg;
905         StrBuf     *Buf;
906         StrBuf     *LineBuf;
907         HashList   *Hash;
908         CtdlNetMap     *TheNetMap;
909
910         Cfg =  CtdlGetSysConfig(IGNETMAP);
911         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
912                 if (Cfg != NULL)
913                         free(Cfg);
914                 return NULL;
915         }
916
917         Hash = NewHash(1, NULL);
918         Buf = NewStrBufPlain(Cfg, -1);
919         free(Cfg);
920         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
921         LinePos = NULL;
922         while (StrBufSipLine(Buf, LineBuf, &LinePos))
923         {
924                 TheNetMap = NewNetMap(LineBuf);
925                 if (TheNetMap != NULL) { /* TODO: is the NodeName Uniq? */
926                         Put(Hash, SKEY(TheNetMap->NodeName), TheNetMap, DeleteNetMap);
927                 }
928         }
929         FreeStrBuf(&Buf);
930         FreeStrBuf(&LineBuf);
931         return Hash;
932 }
933
934 StrBuf *CtdlSerializeNetworkMap(HashList *Map)
935 {
936         void *vMap;
937         const char *key;
938         long len;
939         StrBuf *Ret = NewStrBuf();
940         HashPos *Pos = GetNewHashPos(Map, 0);
941
942         while (GetNextHashPos(Map, Pos, &len, &key, &vMap))
943         {
944                 CtdlNetMap *pMap = (CtdlNetMap*) vMap;
945                 StrBufAppendBuf(Ret, pMap->NodeName, 0);
946                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
947
948                 StrBufAppendPrintf(Ret, "%ld", pMap->lastcontact, 0);
949                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
950
951                 StrBufAppendBuf(Ret, pMap->NextHop, 0);
952                 StrBufAppendBufPlain(Ret, HKEY("\n"), 0);
953         }
954         DeleteHashPos(&Pos);
955         return Ret;
956 }
957
958
959 /*
960  * Learn topology from path fields
961  */
962 void NetworkLearnTopology(char *node, char *path, HashList *the_netmap, int *netmap_changed)
963 {
964         CtdlNetMap *pNM = NULL;
965         void *vptr;
966         char nexthop[256];
967         CtdlNetMap *nmptr;
968
969         if (GetHash(the_netmap, node, strlen(node), &vptr) && 
970             (vptr != NULL))/* TODO: is the NodeName Uniq? */
971         {
972                 pNM = (CtdlNetMap*)vptr;
973                 extract_token(nexthop, path, 0, '!', sizeof nexthop);
974                 if (!strcmp(nexthop, ChrPtr(pNM->NextHop))) {
975                         pNM->lastcontact = time(NULL);
976                         (*netmap_changed) ++;
977                         return;
978                 }
979         }
980
981         /* If we got here then it's not in the map, so add it. */
982         nmptr = (CtdlNetMap *) malloc(sizeof (CtdlNetMap));
983         nmptr->NodeName = NewStrBufPlain(node, -1);
984         nmptr->lastcontact = time(NULL);
985         nmptr->NextHop = NewStrBuf ();
986         StrBufExtract_tokenFromStr(nmptr->NextHop, path, strlen(path), 0, '!');
987         /* TODO: is the NodeName Uniq? */
988         Put(the_netmap, SKEY(nmptr->NodeName), nmptr, DeleteNetMap);
989         (*netmap_changed) ++;
990 }
991
992
993 /*
994  * Check the network map and determine whether the supplied node name is
995  * valid.  If it is not a neighbor node, supply the name of a neighbor node
996  * which is the next hop.  If it *is* a neighbor node, we also fill in the
997  * shared secret.
998  */
999 int CtdlIsValidNode(const StrBuf **nexthop,
1000                     const StrBuf **secret,
1001                     StrBuf *node,
1002                     HashList *IgnetCfg,
1003                     HashList *the_netmap)
1004 {
1005         void *vNetMap;
1006         void *vNodeConf;
1007         CtdlNodeConf *TheNode;
1008         CtdlNetMap *TheNetMap;
1009
1010         if (StrLength(node) == 0) {
1011                 return(-1);
1012         }
1013
1014         /*
1015          * First try the neighbor nodes
1016          */
1017         if (GetCount(IgnetCfg) == 0) {
1018                 syslog(LOG_INFO, "IgnetCfg is empty!\n");
1019                 if (nexthop != NULL) {
1020                         *nexthop = NULL;
1021                 }
1022                 return(-1);
1023         }
1024
1025         /* try to find a neigbour with the name 'node' */
1026         if (GetHash(IgnetCfg, SKEY(node), &vNodeConf) && 
1027             (vNodeConf != NULL))
1028         {
1029                 TheNode = (CtdlNodeConf*)vNodeConf;
1030                 if (secret != NULL)
1031                         *secret = TheNode->Secret;
1032                 return 0;               /* yup, it's a direct neighbor */
1033         }
1034
1035         /*
1036          * If we get to this point we have to see if we know the next hop
1037          *//* TODO: is the NodeName Uniq? */
1038         if ((GetCount(the_netmap) > 0) &&
1039             (GetHash(the_netmap, SKEY(node), &vNetMap)))
1040         {
1041                 TheNetMap = (CtdlNetMap*)vNetMap;
1042                 if (nexthop != NULL)
1043                         *nexthop = TheNetMap->NextHop;
1044                 return(0);
1045         }
1046
1047         /*
1048          * If we get to this point, the supplied node name is bogus.
1049          */
1050         syslog(LOG_ERR, "Invalid node name <%s>\n", ChrPtr(node));
1051         return(-1);
1052 }
1053
1054
1055
1056
1057 /*
1058  * Module entry point
1059  */
1060 CTDL_MODULE_INIT(netconfig)
1061 {
1062         if (!threading)
1063         {
1064                 LoadAllNetConfigs ();
1065                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1066                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1067                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1068         }
1069         return "netconfig";
1070 }