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