Lists: don't overwrite from: header. this only needs to be done for participates.
[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         }
843         return 0;
844 }
845
846
847
848 int CtdlNetconfigCheckRoomaccess(
849         char *errmsgbuf, 
850         size_t n,
851         const char* RemoteIdentifier)
852 {
853         OneRoomNetCfg *RNCfg;
854         int found;
855
856         if (RemoteIdentifier == NULL)
857         {
858                 snprintf(errmsgbuf, n, "Need sender to permit access.");
859                 return (ERROR + USERNAME_REQUIRED);
860         }
861
862         begin_critical_section(S_NETCONFIGS);
863         RNCfg = CtdlGetNetCfgForRoom (CC->room.QRnumber);
864         if (RNCfg == NULL)
865         {
866                 end_critical_section(S_NETCONFIGS);
867                 snprintf(errmsgbuf, n,
868                          "This mailing list only accepts posts from subscribers.");
869                 return (ERROR + NO_SUCH_USER);
870         }
871         found = is_recipient (RNCfg, RemoteIdentifier);
872         end_critical_section(S_NETCONFIGS);
873
874         if (found) {
875                 return (0);
876         }
877         else {
878                 snprintf(errmsgbuf, n,
879                          "This mailing list only accepts posts from subscribers.");
880                 return (ERROR + NO_SUCH_USER);
881         }
882 }
883
884
885
886 /*
887  * cmd_netp() - authenticate to the server as another Citadel node polling
888  *            for network traffic
889  */
890 void cmd_netp(char *cmdbuf)
891 {
892         struct CitContext *CCC = CC;
893         HashList *working_ignetcfg;
894         char *node;
895         StrBuf *NodeStr;
896         long nodelen;
897         int v;
898         long lens[2];
899         const char *strs[2];
900
901         const StrBuf *secret = NULL;
902         const StrBuf *nexthop = NULL;
903         char err_buf[SIZ] = "";
904
905         /* Authenticate */
906         node = CCC->curr_user;
907         nodelen = extract_token(CCC->curr_user, cmdbuf, 0, '|', sizeof CCC->curr_user);
908         NodeStr = NewStrBufPlain(node, nodelen);
909         /* load the IGnet Configuration to check node validity */
910         working_ignetcfg = CtdlLoadIgNetCfg();
911         v = CtdlIsValidNode(&nexthop, &secret, NodeStr, working_ignetcfg, NULL);
912         if (v != 0) {
913                 snprintf(err_buf, sizeof err_buf,
914                         "An unknown Citadel server called \"%s\" attempted to connect from %s [%s].\n",
915                         node, CCC->cs_host, CCC->cs_addr
916                 );
917                 syslog(LOG_WARNING, "%s", err_buf);
918                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
919
920                 strs[0] = CCC->cs_addr;
921                 lens[0] = strlen(CCC->cs_addr);
922                 
923                 strs[1] = "SRV_UNKNOWN";
924                 lens[1] = sizeof("SRV_UNKNOWN" - 1);
925
926                 CtdlAideFPMessage(
927                         err_buf,
928                         "IGNet Networking.",
929                         2, strs, (long*) &lens);
930
931                 DeleteHash(&working_ignetcfg);
932                 FreeStrBuf(&NodeStr);
933                 return;
934         }
935
936         extract_token(CCC->user.password, cmdbuf, 1, '|', sizeof CCC->user.password);
937         if (strcasecmp(CCC->user.password, ChrPtr(secret))) {
938                 snprintf(err_buf, sizeof err_buf,
939                         "A Citadel server at %s [%s] failed to authenticate as network node \"%s\".\n",
940                         CCC->cs_host, CCC->cs_addr, node
941                 );
942                 syslog(LOG_WARNING, "%s", err_buf);
943                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
944
945                 strs[0] = CCC->cs_addr;
946                 lens[0] = strlen(CCC->cs_addr);
947                 
948                 strs[1] = "SRV_PW";
949                 lens[1] = sizeof("SRV_PW" - 1);
950
951                 CtdlAideFPMessage(
952                         err_buf,
953                         "IGNet Networking.",
954                         2, strs, (long*) &lens);
955
956                 DeleteHash(&working_ignetcfg);
957                 FreeStrBuf(&NodeStr);
958                 return;
959         }
960
961         if (CtdlNetworkTalkingTo(node, nodelen, NTT_CHECK)) {
962                 syslog(LOG_WARNING, "Duplicate session for network node <%s>", node);
963                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
964                 DeleteHash(&working_ignetcfg);
965                 FreeStrBuf(&NodeStr);
966                 return;
967         }
968         nodelen = safestrncpy(CCC->net_node, node, sizeof CCC->net_node);
969         CtdlNetworkTalkingTo(CCC->net_node, nodelen, NTT_ADD);
970         syslog(LOG_NOTICE, "Network node <%s> logged in from %s [%s]\n",
971                 CCC->net_node, CCC->cs_host, CCC->cs_addr
972         );
973         cprintf("%d authenticated as network node '%s'\n", CIT_OK, CCC->net_node);
974         DeleteHash(&working_ignetcfg);
975         FreeStrBuf(&NodeStr);
976 }
977
978
979 /*-----------------------------------------------------------------------------*
980  *                 Network maps: evaluate other nodes                          *
981  *-----------------------------------------------------------------------------*/
982
983 void DeleteNetMap(void *vNetMap)
984 {
985         CtdlNetMap *TheNetMap = (CtdlNetMap*) vNetMap;
986         FreeStrBuf(&TheNetMap->NodeName);
987         FreeStrBuf(&TheNetMap->NextHop);
988         free(TheNetMap);
989 }
990
991 CtdlNetMap *NewNetMap(StrBuf *SerializedNetMap)
992 {
993         const char *Pos = NULL;
994         CtdlNetMap *NM;
995
996         /* we need at least 3 pipes and some other text so its invalid. */
997         if (StrLength(SerializedNetMap) < 6)
998                 return NULL;
999         NM = (CtdlNetMap *) malloc(sizeof(CtdlNetMap));
1000
1001         NM->NodeName=NewStrBuf();
1002         StrBufExtract_NextToken(NM->NodeName, SerializedNetMap, &Pos, '|');
1003
1004         NM->lastcontact = StrBufExtractNext_long(SerializedNetMap, &Pos, '|');
1005
1006         NM->NextHop=NewStrBuf();
1007         StrBufExtract_NextToken(NM->NextHop, SerializedNetMap, &Pos, '|');
1008
1009         return NM;
1010 }
1011
1012 HashList* CtdlReadNetworkMap(void)
1013 {
1014         const char *LinePos;
1015         char       *Cfg;
1016         StrBuf     *Buf;
1017         StrBuf     *LineBuf;
1018         HashList   *Hash;
1019         CtdlNetMap     *TheNetMap;
1020
1021         Hash = NewHash(1, NULL);
1022         Cfg =  CtdlGetSysConfig(IGNETMAP);
1023         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
1024                 if (Cfg != NULL)
1025                         free(Cfg);
1026                 return Hash;
1027         }
1028
1029         Buf = NewStrBufPlain(Cfg, -1);
1030         free(Cfg);
1031         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
1032         LinePos = NULL;
1033         while (StrBufSipLine(Buf, LineBuf, &LinePos))
1034         {
1035                 TheNetMap = NewNetMap(LineBuf);
1036                 if (TheNetMap != NULL) { /* TODO: is the NodeName Uniq? */
1037                         Put(Hash, SKEY(TheNetMap->NodeName), TheNetMap, DeleteNetMap);
1038                 }
1039         }
1040         FreeStrBuf(&Buf);
1041         FreeStrBuf(&LineBuf);
1042         return Hash;
1043 }
1044
1045 StrBuf *CtdlSerializeNetworkMap(HashList *Map)
1046 {
1047         void *vMap;
1048         const char *key;
1049         long len;
1050         StrBuf *Ret = NewStrBuf();
1051         HashPos *Pos = GetNewHashPos(Map, 0);
1052
1053         while (GetNextHashPos(Map, Pos, &len, &key, &vMap))
1054         {
1055                 CtdlNetMap *pMap = (CtdlNetMap*) vMap;
1056                 StrBufAppendBuf(Ret, pMap->NodeName, 0);
1057                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
1058
1059                 StrBufAppendPrintf(Ret, "%ld", pMap->lastcontact, 0);
1060                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
1061
1062                 StrBufAppendBuf(Ret, pMap->NextHop, 0);
1063                 StrBufAppendBufPlain(Ret, HKEY("\n"), 0);
1064         }
1065         DeleteHashPos(&Pos);
1066         return Ret;
1067 }
1068
1069
1070 /*
1071  * Learn topology from path fields
1072  */
1073 void NetworkLearnTopology(char *node, char *path, HashList *the_netmap, int *netmap_changed)
1074 {
1075         CtdlNetMap *pNM = NULL;
1076         void *vptr;
1077         char nexthop[256];
1078         CtdlNetMap *nmptr;
1079
1080         if (GetHash(the_netmap, node, strlen(node), &vptr) && 
1081             (vptr != NULL))/* TODO: is the NodeName Uniq? */
1082         {
1083                 pNM = (CtdlNetMap*)vptr;
1084                 extract_token(nexthop, path, 0, '!', sizeof nexthop);
1085                 if (!strcmp(nexthop, ChrPtr(pNM->NextHop))) {
1086                         pNM->lastcontact = time(NULL);
1087                         (*netmap_changed) ++;
1088                         return;
1089                 }
1090         }
1091
1092         /* If we got here then it's not in the map, so add it. */
1093         nmptr = (CtdlNetMap *) malloc(sizeof (CtdlNetMap));
1094         nmptr->NodeName = NewStrBufPlain(node, -1);
1095         nmptr->lastcontact = time(NULL);
1096         nmptr->NextHop = NewStrBuf ();
1097         StrBufExtract_tokenFromStr(nmptr->NextHop, path, strlen(path), 0, '!');
1098         /* TODO: is the NodeName Uniq? */
1099         Put(the_netmap, SKEY(nmptr->NodeName), nmptr, DeleteNetMap);
1100         (*netmap_changed) ++;
1101 }
1102
1103
1104 /*
1105  * Check the network map and determine whether the supplied node name is
1106  * valid.  If it is not a neighbor node, supply the name of a neighbor node
1107  * which is the next hop.  If it *is* a neighbor node, we also fill in the
1108  * shared secret.
1109  */
1110 int CtdlIsValidNode(const StrBuf **nexthop,
1111                     const StrBuf **secret,
1112                     StrBuf *node,
1113                     HashList *IgnetCfg,
1114                     HashList *the_netmap)
1115 {
1116         void *vNetMap;
1117         void *vNodeConf;
1118         CtdlNodeConf *TheNode;
1119         CtdlNetMap *TheNetMap;
1120
1121         if (StrLength(node) == 0) {
1122                 return(-1);
1123         }
1124
1125         /*
1126          * First try the neighbor nodes
1127          */
1128         if (GetCount(IgnetCfg) == 0) {
1129                 syslog(LOG_INFO, "IgnetCfg is empty!\n");
1130                 if (nexthop != NULL) {
1131                         *nexthop = NULL;
1132                 }
1133                 return(-1);
1134         }
1135
1136         /* try to find a neigbour with the name 'node' */
1137         if (GetHash(IgnetCfg, SKEY(node), &vNodeConf) && 
1138             (vNodeConf != NULL))
1139         {
1140                 TheNode = (CtdlNodeConf*)vNodeConf;
1141                 if (secret != NULL)
1142                         *secret = TheNode->Secret;
1143                 return 0;               /* yup, it's a direct neighbor */
1144         }
1145
1146         /*
1147          * If we get to this point we have to see if we know the next hop
1148          *//* TODO: is the NodeName Uniq? */
1149         if ((GetCount(the_netmap) > 0) &&
1150             (GetHash(the_netmap, SKEY(node), &vNetMap)))
1151         {
1152                 TheNetMap = (CtdlNetMap*)vNetMap;
1153                 if (nexthop != NULL)
1154                         *nexthop = TheNetMap->NextHop;
1155                 return(0);
1156         }
1157
1158         /*
1159          * If we get to this point, the supplied node name is bogus.
1160          */
1161         syslog(LOG_ERR, "Invalid node name <%s>\n", ChrPtr(node));
1162         return(-1);
1163 }
1164
1165
1166 void destroy_network_cfgs(void)
1167 {
1168         HashList *pCfgTypeHash = CfgTypeHash;
1169         HashList *pRoomConfigs = RoomConfigs;
1170
1171         CfgTypeHash = NULL;
1172         RoomConfigs = NULL;
1173         
1174         DeleteHash(&pCfgTypeHash);
1175         DeleteHash(&pRoomConfigs);
1176 }
1177
1178 /*
1179  * Module entry point
1180  */
1181 CTDL_MODULE_INIT(netconfig)
1182 {
1183         if (!threading)
1184         {
1185                 CtdlRegisterCleanupHook(destroy_network_cfgs);
1186                 LoadAllNetConfigs ();
1187                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1188                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1189                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1190         }
1191         return "netconfig";
1192 }