a086fea44a92da0cd401b1f9b15a7f97c571223a
[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
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
580 void cmd_snet(char *argbuf)
581 {
582         struct CitContext *CCC = CC;
583         char tempfilename[PATH_MAX];
584         char filename[PATH_MAX];
585         int TmpFD;
586         StrBuf *Line;
587         struct stat StatBuf;
588         long len;
589         int rc;
590         int IsMailAlias = 0;
591         int MailAliasesFound[nForceAliases];
592
593         unbuffer_output();
594
595         if (!IsEmptyStr(argbuf))
596         {
597                 if (CtdlAccessCheck(ac_aide)) return;
598                 if (strcmp(argbuf, FILE_MAILALIAS))
599                 {
600                         cprintf("%d No such file or directory\n",
601                                 ERROR + INTERNAL_ERROR);
602                         return;
603                 }
604                 len = safestrncpy(filename, file_mail_aliases, sizeof(filename));
605                 memset(MailAliasesFound, 0, sizeof(MailAliasesFound));
606                 memcpy(tempfilename, filename, len + 1);
607                 IsMailAlias = 1;
608         }
609         else
610         {
611                 if ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->user.usernum == atol(CCC->room.QRname)) ) {
612                         /* users can edit the netconfigs for their own mailbox rooms */
613                 }
614                 else if (CtdlAccessCheck(ac_room_aide)) return;
615                 
616                 len = assoc_file_name(filename, sizeof filename, &CCC->room, ctdl_netcfg_dir);
617                 memcpy(tempfilename, filename, len + 1);
618         }
619         memset(&StatBuf, 0, sizeof(struct stat));
620         if ((stat(filename, &StatBuf)  == -1) || (StatBuf.st_size == 0))
621                 StatBuf.st_size = 80; /* Not there or empty? guess 80 chars line. */
622
623         sprintf(tempfilename + len, ".%d", CCC->cs_pid);
624         errno = 0;
625         TmpFD = open(tempfilename, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR);
626
627         if ((TmpFD > 0) && (errno == 0))
628         {
629                 char *tmp = malloc(StatBuf.st_size * 2);
630                 memset(tmp, ' ', StatBuf.st_size * 2);
631                 rc = write(TmpFD, tmp, StatBuf.st_size * 2);
632                 free(tmp);
633                 if ((rc <= 0) || (rc != StatBuf.st_size * 2))
634                 {
635                         close(TmpFD);
636                         cprintf("%d Unable to allocate the space required for %s: %s\n",
637                                 ERROR + INTERNAL_ERROR,
638                                 tempfilename,
639                                 strerror(errno));
640                         unlink(tempfilename);
641                         return;
642                 }       
643                 lseek(TmpFD, SEEK_SET, 0);
644         }
645         else {
646                 cprintf("%d Unable to allocate the space required for %s: %s\n",
647                         ERROR + INTERNAL_ERROR,
648                         tempfilename,
649                         strerror(errno));
650                 unlink(tempfilename);
651                 return;
652         }
653         Line = NewStrBuf();
654
655         cprintf("%d %s\n", SEND_LISTING, tempfilename);
656
657         len = 0;
658         while (rc = CtdlClientGetLine(Line), 
659                (rc >= 0))
660         {
661                 if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
662                         break;
663                 if (IsMailAlias)
664                 {
665                         int i;
666
667                         for (i = 0; i < nForceAliases; i++)
668                         {
669                                 if ((!MailAliasesFound[i]) && 
670                                     (strncmp(ForceAliases[i].Key, 
671                                              ChrPtr(Line),
672                                              ForceAliases[i].len) == 0)
673                                         )
674                                     {
675                                             MailAliasesFound[i] = 1;
676                                             break;
677                                     }
678                         }
679                 }
680
681                 StrBufAppendBufPlain(Line, HKEY("\n"), 0);
682                 write(TmpFD, ChrPtr(Line), StrLength(Line));
683                 len += StrLength(Line);
684         }
685         FreeStrBuf(&Line);
686         ftruncate(TmpFD, len);
687         close(TmpFD);
688
689         if (IsMailAlias)
690         {
691                 int i, state;
692                 /*
693                  * Sanity check whether all aliases required by the RFCs were set
694                  * else bail out.
695                  */
696                 state = 1;
697                 for (i = 0; i < nForceAliases; i++)
698                 {
699                         if (!MailAliasesFound[i]) 
700                                 state = 0;
701                 }
702                 if (state == 0)
703                 {
704                         cprintf("%d won't do this - you're missing an RFC required alias.\n",
705                                 ERROR + INTERNAL_ERROR);
706                         unlink(tempfilename);
707                         return;
708                 }
709         }
710
711         /* Now copy the temp file to its permanent location.
712          * (We copy instead of link because they may be on different filesystems)
713          */
714         begin_critical_section(S_NETCONFIGS);
715         rename(tempfilename, filename);
716         if (!IsMailAlias)
717         {
718                 OneRoomNetCfg *RNCfg;
719                 RNCfg = CtdlGetNetCfgForRoom(CCC->room.QRnumber);
720                 if (RNCfg != NULL)
721                 {
722                         ReadRoomNetConfigFile(&RNCfg, filename);
723                 }
724                 else
725                 {
726                         ReadRoomNetConfigFile(&RNCfg, filename);
727                         Put(RoomConfigs, LKEY(CCC->room.QRnumber), RNCfg, vFreeRoomNetworkStruct);
728                 }
729         }
730         end_critical_section(S_NETCONFIGS);
731 }
732
733
734 /*-----------------------------------------------------------------------------*
735  *                       Per node network configs                              *
736  *-----------------------------------------------------------------------------*/
737 void DeleteCtdlNodeConf(void *vNode)
738 {
739         CtdlNodeConf *Node = (CtdlNodeConf*) vNode;
740         FreeStrBuf(&Node->NodeName);
741         FreeStrBuf(&Node->Secret);
742         FreeStrBuf(&Node->Host);
743         FreeStrBuf(&Node->Port);
744         free(Node);
745 }
746
747 CtdlNodeConf *NewNode(StrBuf *SerializedNode)
748 {
749         const char *Pos = NULL;
750         CtdlNodeConf *Node;
751
752         /* we need at least 4 pipes and some other text so its invalid. */
753         if (StrLength(SerializedNode) < 8)
754                 return NULL;
755         Node = (CtdlNodeConf *) malloc(sizeof(CtdlNodeConf));
756
757         Node->DeleteMe = 0;
758
759         Node->NodeName=NewStrBuf();
760         StrBufExtract_NextToken(Node->NodeName, SerializedNode, &Pos, '|');
761
762         Node->Secret=NewStrBuf();
763         StrBufExtract_NextToken(Node->Secret, SerializedNode, &Pos, '|');
764
765         Node->Host=NewStrBuf();
766         StrBufExtract_NextToken(Node->Host, SerializedNode, &Pos, '|');
767
768         Node->Port=NewStrBuf();
769         StrBufExtract_NextToken(Node->Port, SerializedNode, &Pos, '|');
770         return Node;
771 }
772
773
774 /*
775  * Load or refresh the Citadel network (IGnet) configuration for this node.
776  */
777 HashList* CtdlLoadIgNetCfg(void)
778 {
779         const char *LinePos;
780         char       *Cfg;
781         StrBuf     *Buf;
782         StrBuf     *LineBuf;
783         HashList   *Hash;
784         CtdlNodeConf   *Node;
785
786         Cfg =  CtdlGetSysConfig(IGNETCFG);
787         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
788                 if (Cfg != NULL)
789                         free(Cfg);
790                 return NULL;
791         }
792
793         Hash = NewHash(1, NULL);
794         Buf = NewStrBufPlain(Cfg, -1);
795         free(Cfg);
796         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
797         LinePos = NULL;
798         do
799         {
800                 StrBufSipLine(LineBuf, Buf, &LinePos);
801                 if (StrLength(LineBuf) != 0) {
802                         Node = NewNode(LineBuf);
803                         if (Node != NULL) {
804                                 Put(Hash, SKEY(Node->NodeName), Node, DeleteCtdlNodeConf);
805                         }
806                 }
807         } while (LinePos != StrBufNOTNULL);
808         FreeStrBuf(&Buf);
809         FreeStrBuf(&LineBuf);
810         return Hash;
811 }
812
813
814 int is_recipient(OneRoomNetCfg *RNCfg, const char *Name)
815 {
816         const RoomNetCfg RecipientCfgs[] = {
817                 listrecp,
818                 digestrecp,
819                 participate,
820                 maxRoomNetCfg
821         };
822         int i;
823         RoomNetCfgLine *nptr;
824         size_t len;
825         
826         len = strlen(Name);
827         i = 0;
828         while (RecipientCfgs[i] != maxRoomNetCfg)
829         {
830                 nptr = RNCfg->NetConfigs[RecipientCfgs[i]];
831                 
832                 while (nptr != NULL)
833                 {
834                         if ((StrLength(nptr->Value[0]) == len) && 
835                             (!strcmp(Name, ChrPtr(nptr->Value[0]))))
836                         {
837                                 return 1;
838                         }
839                         nptr = nptr->next;
840                 }
841         }
842         return 0;
843 }
844
845
846
847 int CtdlNetconfigCheckRoomaccess(
848         char *errmsgbuf, 
849         size_t n,
850         const char* RemoteIdentifier)
851 {
852         OneRoomNetCfg *RNCfg;
853         int found;
854
855         if (RemoteIdentifier == NULL)
856         {
857                 snprintf(errmsgbuf, n, "Need sender to permit access.");
858                 return (ERROR + USERNAME_REQUIRED);
859         }
860
861         begin_critical_section(S_NETCONFIGS);
862         RNCfg = CtdlGetNetCfgForRoom (CC->room.QRnumber);
863         if (RNCfg == NULL)
864         {
865                 end_critical_section(S_NETCONFIGS);
866                 snprintf(errmsgbuf, n,
867                          "This mailing list only accepts posts from subscribers.");
868                 return (ERROR + NO_SUCH_USER);
869         }
870         found = is_recipient (RNCfg, RemoteIdentifier);
871         end_critical_section(S_NETCONFIGS);
872
873         if (found) {
874                 return (0);
875         }
876         else {
877                 snprintf(errmsgbuf, n,
878                          "This mailing list only accepts posts from subscribers.");
879                 return (ERROR + NO_SUCH_USER);
880         }
881 }
882
883
884
885 /*
886  * cmd_netp() - authenticate to the server as another Citadel node polling
887  *            for network traffic
888  */
889 void cmd_netp(char *cmdbuf)
890 {
891         struct CitContext *CCC = CC;
892         HashList *working_ignetcfg;
893         char *node;
894         StrBuf *NodeStr;
895         long nodelen;
896         int v;
897         long lens[2];
898         const char *strs[2];
899
900         const StrBuf *secret = NULL;
901         const StrBuf *nexthop = NULL;
902         char err_buf[SIZ] = "";
903
904         /* Authenticate */
905         node = CCC->curr_user;
906         nodelen = extract_token(CCC->curr_user, cmdbuf, 0, '|', sizeof CCC->curr_user);
907         NodeStr = NewStrBufPlain(node, nodelen);
908         /* load the IGnet Configuration to check node validity */
909         working_ignetcfg = CtdlLoadIgNetCfg();
910         v = CtdlIsValidNode(&nexthop, &secret, NodeStr, working_ignetcfg, NULL);
911         if (v != 0) {
912                 snprintf(err_buf, sizeof err_buf,
913                         "An unknown Citadel server called \"%s\" attempted to connect from %s [%s].\n",
914                         node, CCC->cs_host, CCC->cs_addr
915                 );
916                 syslog(LOG_WARNING, "%s", err_buf);
917                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
918
919                 strs[0] = CCC->cs_addr;
920                 lens[0] = strlen(CCC->cs_addr);
921                 
922                 strs[1] = "SRV_UNKNOWN";
923                 lens[1] = sizeof("SRV_UNKNOWN" - 1);
924
925                 CtdlAideFPMessage(
926                         err_buf,
927                         "IGNet Networking.",
928                         2, strs, (long*) &lens);
929
930                 DeleteHash(&working_ignetcfg);
931                 FreeStrBuf(&NodeStr);
932                 return;
933         }
934
935         extract_token(CCC->user.password, cmdbuf, 1, '|', sizeof CCC->user.password);
936         if (strcasecmp(CCC->user.password, ChrPtr(secret))) {
937                 snprintf(err_buf, sizeof err_buf,
938                         "A Citadel server at %s [%s] failed to authenticate as network node \"%s\".\n",
939                         CCC->cs_host, CCC->cs_addr, node
940                 );
941                 syslog(LOG_WARNING, "%s", err_buf);
942                 cprintf("%d authentication failed\n", ERROR + PASSWORD_REQUIRED);
943
944                 strs[0] = CCC->cs_addr;
945                 lens[0] = strlen(CCC->cs_addr);
946                 
947                 strs[1] = "SRV_PW";
948                 lens[1] = sizeof("SRV_PW" - 1);
949
950                 CtdlAideFPMessage(
951                         err_buf,
952                         "IGNet Networking.",
953                         2, strs, (long*) &lens);
954
955                 DeleteHash(&working_ignetcfg);
956                 FreeStrBuf(&NodeStr);
957                 return;
958         }
959
960         if (CtdlNetworkTalkingTo(node, nodelen, NTT_CHECK)) {
961                 syslog(LOG_WARNING, "Duplicate session for network node <%s>", node);
962                 cprintf("%d Already talking to %s right now\n", ERROR + RESOURCE_BUSY, node);
963                 DeleteHash(&working_ignetcfg);
964                 FreeStrBuf(&NodeStr);
965                 return;
966         }
967         nodelen = safestrncpy(CCC->net_node, node, sizeof CCC->net_node);
968         CtdlNetworkTalkingTo(CCC->net_node, nodelen, NTT_ADD);
969         syslog(LOG_NOTICE, "Network node <%s> logged in from %s [%s]\n",
970                 CCC->net_node, CCC->cs_host, CCC->cs_addr
971         );
972         cprintf("%d authenticated as network node '%s'\n", CIT_OK, CCC->net_node);
973         DeleteHash(&working_ignetcfg);
974         FreeStrBuf(&NodeStr);
975 }
976
977
978 /*-----------------------------------------------------------------------------*
979  *                 Network maps: evaluate other nodes                          *
980  *-----------------------------------------------------------------------------*/
981
982 void DeleteNetMap(void *vNetMap)
983 {
984         CtdlNetMap *TheNetMap = (CtdlNetMap*) vNetMap;
985         FreeStrBuf(&TheNetMap->NodeName);
986         FreeStrBuf(&TheNetMap->NextHop);
987         free(TheNetMap);
988 }
989
990 CtdlNetMap *NewNetMap(StrBuf *SerializedNetMap)
991 {
992         const char *Pos = NULL;
993         CtdlNetMap *NM;
994
995         /* we need at least 3 pipes and some other text so its invalid. */
996         if (StrLength(SerializedNetMap) < 6)
997                 return NULL;
998         NM = (CtdlNetMap *) malloc(sizeof(CtdlNetMap));
999
1000         NM->NodeName=NewStrBuf();
1001         StrBufExtract_NextToken(NM->NodeName, SerializedNetMap, &Pos, '|');
1002
1003         NM->lastcontact = StrBufExtractNext_long(SerializedNetMap, &Pos, '|');
1004
1005         NM->NextHop=NewStrBuf();
1006         StrBufExtract_NextToken(NM->NextHop, SerializedNetMap, &Pos, '|');
1007
1008         return NM;
1009 }
1010
1011 HashList* CtdlReadNetworkMap(void)
1012 {
1013         const char *LinePos;
1014         char       *Cfg;
1015         StrBuf     *Buf;
1016         StrBuf     *LineBuf;
1017         HashList   *Hash;
1018         CtdlNetMap     *TheNetMap;
1019
1020         Hash = NewHash(1, NULL);
1021         Cfg =  CtdlGetSysConfig(IGNETMAP);
1022         if ((Cfg == NULL) || IsEmptyStr(Cfg)) {
1023                 if (Cfg != NULL)
1024                         free(Cfg);
1025                 return Hash;
1026         }
1027
1028         Buf = NewStrBufPlain(Cfg, -1);
1029         free(Cfg);
1030         LineBuf = NewStrBufPlain(NULL, StrLength(Buf));
1031         LinePos = NULL;
1032         while (StrBufSipLine(Buf, LineBuf, &LinePos))
1033         {
1034                 TheNetMap = NewNetMap(LineBuf);
1035                 if (TheNetMap != NULL) { /* TODO: is the NodeName Uniq? */
1036                         Put(Hash, SKEY(TheNetMap->NodeName), TheNetMap, DeleteNetMap);
1037                 }
1038         }
1039         FreeStrBuf(&Buf);
1040         FreeStrBuf(&LineBuf);
1041         return Hash;
1042 }
1043
1044 StrBuf *CtdlSerializeNetworkMap(HashList *Map)
1045 {
1046         void *vMap;
1047         const char *key;
1048         long len;
1049         StrBuf *Ret = NewStrBuf();
1050         HashPos *Pos = GetNewHashPos(Map, 0);
1051
1052         while (GetNextHashPos(Map, Pos, &len, &key, &vMap))
1053         {
1054                 CtdlNetMap *pMap = (CtdlNetMap*) vMap;
1055                 StrBufAppendBuf(Ret, pMap->NodeName, 0);
1056                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
1057
1058                 StrBufAppendPrintf(Ret, "%ld", pMap->lastcontact, 0);
1059                 StrBufAppendBufPlain(Ret, HKEY("|"), 0);
1060
1061                 StrBufAppendBuf(Ret, pMap->NextHop, 0);
1062                 StrBufAppendBufPlain(Ret, HKEY("\n"), 0);
1063         }
1064         DeleteHashPos(&Pos);
1065         return Ret;
1066 }
1067
1068
1069 /*
1070  * Learn topology from path fields
1071  */
1072 void NetworkLearnTopology(char *node, char *path, HashList *the_netmap, int *netmap_changed)
1073 {
1074         CtdlNetMap *pNM = NULL;
1075         void *vptr;
1076         char nexthop[256];
1077         CtdlNetMap *nmptr;
1078
1079         if (GetHash(the_netmap, node, strlen(node), &vptr) && 
1080             (vptr != NULL))/* TODO: is the NodeName Uniq? */
1081         {
1082                 pNM = (CtdlNetMap*)vptr;
1083                 extract_token(nexthop, path, 0, '!', sizeof nexthop);
1084                 if (!strcmp(nexthop, ChrPtr(pNM->NextHop))) {
1085                         pNM->lastcontact = time(NULL);
1086                         (*netmap_changed) ++;
1087                         return;
1088                 }
1089         }
1090
1091         /* If we got here then it's not in the map, so add it. */
1092         nmptr = (CtdlNetMap *) malloc(sizeof (CtdlNetMap));
1093         nmptr->NodeName = NewStrBufPlain(node, -1);
1094         nmptr->lastcontact = time(NULL);
1095         nmptr->NextHop = NewStrBuf ();
1096         StrBufExtract_tokenFromStr(nmptr->NextHop, path, strlen(path), 0, '!');
1097         /* TODO: is the NodeName Uniq? */
1098         Put(the_netmap, SKEY(nmptr->NodeName), nmptr, DeleteNetMap);
1099         (*netmap_changed) ++;
1100 }
1101
1102
1103 /*
1104  * Check the network map and determine whether the supplied node name is
1105  * valid.  If it is not a neighbor node, supply the name of a neighbor node
1106  * which is the next hop.  If it *is* a neighbor node, we also fill in the
1107  * shared secret.
1108  */
1109 int CtdlIsValidNode(const StrBuf **nexthop,
1110                     const StrBuf **secret,
1111                     StrBuf *node,
1112                     HashList *IgnetCfg,
1113                     HashList *the_netmap)
1114 {
1115         void *vNetMap;
1116         void *vNodeConf;
1117         CtdlNodeConf *TheNode;
1118         CtdlNetMap *TheNetMap;
1119
1120         if (StrLength(node) == 0) {
1121                 return(-1);
1122         }
1123
1124         /*
1125          * First try the neighbor nodes
1126          */
1127         if (GetCount(IgnetCfg) == 0) {
1128                 syslog(LOG_INFO, "IgnetCfg is empty!\n");
1129                 if (nexthop != NULL) {
1130                         *nexthop = NULL;
1131                 }
1132                 return(-1);
1133         }
1134
1135         /* try to find a neigbour with the name 'node' */
1136         if (GetHash(IgnetCfg, SKEY(node), &vNodeConf) && 
1137             (vNodeConf != NULL))
1138         {
1139                 TheNode = (CtdlNodeConf*)vNodeConf;
1140                 if (secret != NULL)
1141                         *secret = TheNode->Secret;
1142                 return 0;               /* yup, it's a direct neighbor */
1143         }
1144
1145         /*
1146          * If we get to this point we have to see if we know the next hop
1147          *//* TODO: is the NodeName Uniq? */
1148         if ((GetCount(the_netmap) > 0) &&
1149             (GetHash(the_netmap, SKEY(node), &vNetMap)))
1150         {
1151                 TheNetMap = (CtdlNetMap*)vNetMap;
1152                 if (nexthop != NULL)
1153                         *nexthop = TheNetMap->NextHop;
1154                 return(0);
1155         }
1156
1157         /*
1158          * If we get to this point, the supplied node name is bogus.
1159          */
1160         syslog(LOG_ERR, "Invalid node name <%s>\n", ChrPtr(node));
1161         return(-1);
1162 }
1163
1164
1165 void destroy_network_cfgs(void)
1166 {
1167         HashList *pCfgTypeHash = CfgTypeHash;
1168         HashList *pRoomConfigs = RoomConfigs;
1169
1170         CfgTypeHash = NULL;
1171         RoomConfigs = NULL;
1172         
1173         DeleteHash(&pCfgTypeHash);
1174         DeleteHash(&pRoomConfigs);
1175 }
1176
1177 /*
1178  * Module entry point
1179  */
1180 CTDL_MODULE_INIT(netconfig)
1181 {
1182         if (!threading)
1183         {
1184                 CtdlRegisterCleanupHook(destroy_network_cfgs);
1185                 LoadAllNetConfigs ();
1186                 CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
1187                 CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
1188                 CtdlRegisterProtoHook(cmd_netp, "NETP", "Identify as network poller");
1189         }
1190         return "netconfig";
1191 }