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