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