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