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