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