1 /* $Id$
2 ******************************************************************************
3 * FIDOCONFIG --- library for fidonet configs
4 ******************************************************************************
5 * Copyright (C) 2000-2002
6 *
7 * Max Levenkov
8 * Husky development team
9 * http://husky.sourceforge.net/team.html
10 *
11 * This file is part of FIDOCONFIG.
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Library General Public
15 * License as published by the Free Software Foundation; either
16 * version 2 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Library General Public License for more details.
22 *
23 * You should have received a copy of the GNU Library General Public
24 * License along with this library; see file COPYING. If not, write to the Free
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 *
27 * See also http://www.gnu.org
28 *****************************************************************************
29 */
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <ctype.h>
40
41 #include <huskylib/huskylib.h>
42
43 #ifdef HAS_STRINGS_H
44 # include <strings.h>
45 #endif /* HAS_STRINGS_H */
46
47 #if defined (__OS2__)
48 # define INCL_DOSFILEMGR
49 # include <os2.h>
50 #endif
51
52 /* export functions from DLL */
53 #define DLLEXPORT
54 #include <huskylib/huskyext.h>
55
56 #include "fidoconf.h"
57 #include "common.h"
58
59 #define setcond for (i=0, condition=1; i<=iflevel; condition=ifstack[i++].state && condition);
60
61 #define DEFAULT_MODULE " " /* Default value if variable [module] is not defined. This is necessary to prevent warning about undefined [module] */
62
63 static char *curconfname=NULL;
64 static long curconfpos=0;
65 static FILE *hcfg=NULL;
66 static short condition;
67 static int iflevel, nvars, sp;
68 static int maxnvars, maxsp, maxif;
69 static struct { short state, inelse, wastrue;
70 } *ifstack=NULL;
71 static struct { char *var, *value;
72 } *set=NULL;
73 static struct {
74 FILE *farr;
75 int curline;
76 char *confname;
77 } *incstack=NULL;
78
79 static unsigned int cfgNamesCount;
80 static char **cfgNames=NULL;
81
init_conf(const char * conf_name)82 int init_conf(const char *conf_name)
83 {
84 if( conf_name==NULL || conf_name[0]==0 )
85 {
86 w_log(LL_ERR, __FILE__ "::init_conf(): config name %s", conf_name?"has null length":"is NULL pointer");
87 return -1;
88 }
89 iflevel=-1;
90 condition=1;
91 sp=0;
92 cfgNamesCount=0;
93 hcfg=fopen(conf_name, "rb");
94 if (hcfg==NULL)
95 {
96 fprintf(stderr, "Can't open config file %s: %s!\n",
97 conf_name, strerror(errno));
98 wasError = 1;
99 return -1;
100 }
101 curconfname=sstrdup(conf_name);
102 actualLineNr=0;
103 #if defined(__UNIX__)
104 setvar("OS", "UNIX");
105 #elif defined(__OS2__)
106 setvar("OS", "OS/2");
107 #elif defined(__NT__)
108 setvar("OS", "WIN");
109 #elif defined(__DOS__)
110 setvar("OS", "MSDOS");
111 #endif
112 /* Variables for special symbols escaping */
113 setvar("[", "[");
114 setvar("`", "`");
115 setvar("\"", "\"");
116 setvar("'", "'");
117 setvar("#", "#");
118 { /* default value for the [module] */
119 char *module = getvar("module");
120 if (!module)
121 setvar("module", DEFAULT_MODULE);
122 }
123 /* Reinit CommentChar to the default value */
124 CommentChar='#';
125 return 0;
126 }
127
getvar(char * name)128 char *getvar(char *name)
129 { int i;
130
131 for (i=0; i<nvars; i++)
132 if (sstricmp(name, set[i].var)==0)
133 { if (set[i].value[0]==0)
134 return NULL;
135 return set[i].value;
136 }
137 return getenv(name);
138 }
139
setvar(char * name,char * value)140 void setvar(char *name, char *value)
141 { int i, j;
142
143 /* find var */
144 for (i=0; i<nvars; i++)
145 if (sstricmp(set[i].var, name)==0)
146 break;
147 if (i<nvars)
148 { /* remove var */
149 nfree(set[i].var);
150 for (j=i; j<nvars-1; j++)
151 { set[j].var=set[j+1].var;
152 set[j].value=set[j+1].value;
153 }
154 nvars--;
155 }
156 if (value==NULL) value="";
157 if (value[0]==0)
158 if (getvar(value)==NULL)
159 return;
160 if (nvars==maxnvars)
161 set = srealloc(set, (maxnvars+=10)*sizeof(*set));
162 set[nvars].var=smalloc(sstrlen(name)+sstrlen(value)+2);
163 sstrcpy(set[nvars].var, name);
164 set[nvars].value=set[nvars].var+sstrlen(name)+1;
165 sstrcpy(set[nvars].value, value);
166 nvars++;
167 return;
168 }
169
free_vars(void)170 void free_vars(void)
171 {
172 int i;
173 for(i=0; i<nvars; i++)
174 nfree(set[i].var);
175 maxnvars=nvars=0;
176 nfree(set);
177 }
close_conf(void)178 void close_conf(void)
179 {
180 int i;
181 char *module;
182
183 module = getvar("module");
184 if (module) module = sstrdup(module);
185 free_vars();
186 if (module)
187 { setvar("module", module);
188 nfree(module);
189 }
190 else
191 setvar("module", DEFAULT_MODULE);
192 nfree(ifstack);
193 maxif=0;
194 if (hcfg) fclose(hcfg);
195 hcfg=NULL;
196 for (i=0; i<sp; i++) {
197 fclose(incstack[i].farr);
198 nfree(incstack[i].confname);
199 }
200 nfree(curconfname);
201 nfree(incstack);
202 sp=maxsp=0;
203 for (i=0; i<(int)cfgNamesCount; i++) nfree(cfgNames[i]);
204 nfree(cfgNames);
205 cfgNamesCount=0;
206 }
207
_configline(void)208 static char *_configline(void)
209 {
210 char *line;
211
212 curconfpos = ftell(hcfg);
213 line = readLine(hcfg);
214 if (line == NULL)
215 return NULL;
216 actualLineNr++;
217 return line;
218 }
219
vars_expand(char * line)220 char *vars_expand(char *line)
221 {
222 int curlen;
223 char *parsed, *src, *dest, *p, *p1, *newparsed;
224 #if defined(__UNIX__) || (defined(__OS2__) && defined(__EMX__))
225 FILE *f;
226 int i;
227 #endif
228
229 #if defined(__UNIX__) || (defined(__OS2__) && defined(__EMX__))
230 if (strpbrk(line, "[`")==NULL)
231 #else
232 if (strchr(line, '[')==NULL)
233 #endif
234 return line;
235 curlen = sstrlen(line)+1;
236 parsed = dest = smalloc(curlen);
237 for (src = line; *src; src++)
238 {
239 if (dest-parsed >= curlen-2)
240 {
241 size_t offset = (size_t) (dest - parsed);
242 /* we need this to fake around boundary checking */
243
244 newparsed = srealloc(parsed, curlen+=80);
245 dest = newparsed + offset;
246 parsed = newparsed;
247 }
248 switch (*src)
249 {
250 #if defined(__UNIX__) || (defined(__OS2__) && defined(__EMX__))
251 case '`':
252 p = strchr(src+1, '`');
253 if (p == NULL)
254 {
255 *dest++ = *src;
256 continue;
257 }
258 *p = '\0';
259 src++;
260 f = popen(src, "r");
261 if (f)
262 {
263 w_log (LL_EXEC, "Run command `%s` (specified in config file %s)", src, curconfname);
264 *p = '`';
265 src = p;
266 while ((i = fgetc(f)) != EOF)
267 {
268 if (dest-parsed >= curlen-2)
269 {
270 newparsed = srealloc(parsed, curlen+=80);
271 dest = newparsed+(unsigned)(dest-parsed);
272 parsed = newparsed;
273 }
274 if (i!='\n') *dest++ = (char)i;
275 }
276 pclose(f);
277 }
278 else
279 w_log (LL_ERR, "Can't run command `%s` (specified in config file %s)", src, curconfname);
280 continue;
281 #endif
282 case '[':
283 p = strchr(src, ']');
284 if (p)
285 {
286 src++;
287 *p = '\0';
288 if ((p1 = getvar(src)))
289 {
290 if (sstrlen(p1) > sstrlen(src)+2)
291 {
292 newparsed = srealloc(parsed, curlen += sstrlen(p1)-sstrlen(src)-2);
293 dest = newparsed+(unsigned)(dest-parsed);
294 parsed = newparsed;
295 }
296 sstrcpy(dest, p1);
297 dest += sstrlen(p1);
298 }
299 else
300 {
301 w_log(LL_WARN, "Enviroment variable [%s] (used in config file %s) is not defined and is replaced with empty string", src, curconfname);
302 }
303 *p = ']';
304 src = p;
305 continue;
306 }
307 default:
308 *dest++ = *src;
309 continue;
310 }
311 }
312 *dest++ = '\0';
313 if (curlen != dest-parsed)
314 parsed = srealloc(parsed, (unsigned)(dest-parsed));
315 nfree(line);
316 return parsed;
317 }
318
boolexpr(char * str)319 static short boolexpr(char *str)
320 { char *p, *p1, *p2;
321 short ret, inquote, relax;
322
323 ret=1;
324 for (p=str; isspace(*p); p++);
325 if (strncasecmp(p, "not ", 4)==0)
326 { ret=0;
327 for (p+=4; isspace(*p); p++);
328 }
329 inquote=0;
330 for (p1=p; *p1; p1++)
331 {
332 if (p1[0]=='\\' && (p1[1]=='\\' || p1[1]=='\"'))
333 { p1++;
334 continue;
335 }
336 if (*p1=='\"')
337 { inquote = !inquote;
338 continue;
339 }
340 if (!inquote)
341 if ((p1[0] == '=' || p1[0] == '!') && (p1[1] == '=' || p1[1] == '~'))
342 break;
343 }
344 if (*p1==0)
345 { fprintf(stderr, "Bad 'if' expression in config %s, line %d: '%s'\n",
346 curconfname, actualLineNr, str);
347 wasError = 1;
348 return ret;
349 }
350 if (p1[0]=='!') ret=!ret;
351 relax=(p1[1]=='~');
352 *p1=0;
353 for (p2=p1-1; isspace(*p2); *p2--=0);
354 for (p1+=2; isspace(*p1); p1++);
355 for (p2=p1+sstrlen(p1)-1; isspace(*p2); *p2--=0);
356 if (relax ? patimat(p, p1) : sstricmp(p, p1))
357 ret=!ret;
358 return ret;
359 }
360
configline(void)361 char *configline(void)
362 { int i;
363 char *p, *p1, *p2, *str, *line=NULL;
364
365 for (;;) {
366 nfree(line);
367 line=str=_configline();
368 if (str==NULL) {
369 /* save parsed config name */
370 cfgNames = srealloc(cfgNames, sizeof(char*)*(cfgNamesCount+1));
371 cfgNames[cfgNamesCount] = NULL;
372 xstrcat(&cfgNames[cfgNamesCount], curconfname);
373 cfgNamesCount++;
374 if (sp) {
375 fclose(hcfg);
376 nfree(curconfname);
377 hcfg=incstack[--sp].farr;
378 actualLineNr=incstack[sp].curline;
379 curconfname=incstack[sp].confname;
380 continue;
381 }
382 return NULL;
383 }
384 while (*str && isspace(*str)) str++;
385 stripComment(str);
386 if (strncasecmp(str, "if ", 3)==0)
387 {
388 p=vars_expand(line); str+=(p-line); line=p;
389 iflevel++;
390 if (iflevel==maxif)
391 ifstack=srealloc(ifstack, (maxif+=10)*sizeof(*ifstack));
392 ifstack[iflevel].inelse=0;
393 ifstack[iflevel].state=ifstack[iflevel].wastrue=boolexpr(str+3);
394 condition = condition && ifstack[iflevel].state;
395 continue;
396 }
397 if ((strncasecmp(str, "ifdef ", 6)==0) ||
398 (strncasecmp(str, "ifndef ", 7)==0))
399 {
400 p=vars_expand(line); str+=(p-line); line=p;
401 for (p1=str+sstrlen(str)-1; isspace(*p1); *p1--='\0');
402 for (p=str+6; isspace(*p); p++);
403 if (*p=='\0')
404 { fprintf(stderr, "Bad %s in config %s line %d!\n",
405 str, curconfname, actualLineNr);
406 wasError = 1;
407 continue;
408 }
409 iflevel++;
410 if (iflevel==maxif)
411 ifstack=srealloc(ifstack, (maxif+=10)*sizeof(*ifstack));
412 ifstack[iflevel].inelse=0;
413 ifstack[iflevel].state=(getvar(p)!=NULL);
414 if (tolower(str[2])=='n') /* ifndef */
415 ifstack[iflevel].state=!ifstack[iflevel].state;
416 ifstack[iflevel].wastrue=ifstack[iflevel].state;
417 condition = condition && ifstack[iflevel].state;
418 continue;
419 }
420 if (strncasecmp(str, "elseif ", 7)==0 || strncasecmp(str, "elif ", 5) == 0)
421 {
422 if ((iflevel==-1) || ifstack[iflevel].inelse)
423 { fprintf(stderr, "Misplaced elseif in config %s line %d ignored!\n",
424 curconfname, actualLineNr);
425 wasError = 1;
426 continue;
427 }
428 p=vars_expand(line); str+=(p-line); line=p;
429 if (ifstack[iflevel].wastrue)
430 ifstack[iflevel].state=0;
431 else
432 ifstack[iflevel].state=ifstack[iflevel].wastrue=boolexpr(strchr(str, ' '));
433 setcond;
434 continue;
435 }
436 if (strncasecmp(str, "else", 4)==0)
437 {
438 if ((iflevel==-1) || ifstack[iflevel].inelse)
439 { fprintf(stderr, "Misplaced else in config %s line %d ignored!\n",
440 curconfname, actualLineNr);
441 wasError = 1;
442 continue;
443 }
444 ifstack[iflevel].inelse=1;
445 ifstack[iflevel].state=!ifstack[iflevel].wastrue;
446 setcond;
447 continue;
448 }
449 if (strncasecmp(str, "endif", 5)==0)
450 {
451 if (iflevel==-1)
452 { fprintf(stderr, "Misplaced endif in config %s line %d ignored!\n",
453 curconfname, actualLineNr);
454 wasError = 1;
455 continue;
456 }
457 iflevel--;
458 setcond;
459 continue;
460 }
461 if (!condition)
462 continue;
463 if (strncasecmp(str, "set ", 4)==0)
464 {
465 p=vars_expand(line); str+=(p-line); line=p;
466 p=strchr(str, '\n');
467 if (p) *p=0;
468 p1=strchr(str+4, '=');
469 if (p1==NULL)
470 { fprintf(stderr, "Incorrect set in config %s line %d!\n",
471 curconfname, actualLineNr);
472 wasError = 1;
473 continue;
474 }
475 *p1=0;
476 for (p=p1-1; isspace(*p); *p--='\0');
477 for (p=str+4; isspace(*p); p++);
478 /* now p - name of var */
479 for (p1++; isspace(*p1); p1++);
480 if (*p1=='\"')
481 { /* remove quote chars */
482 for (p2=p1; (p2=strchr(p2+1, '\"'))!=NULL;)
483 if (*(p2-1)!='\\')
484 *p2--='\0';
485 p1++;
486 }
487 setvar(p, p1);
488 continue;
489 }
490 if (strncasecmp(str, "include", 7)==0)
491 {
492 p=vars_expand(line); str+=(p-line); line=p;
493 for (p=str+7; (*p==' ') || (*p=='\t'); p++);
494 for (p1=p+sstrlen(p)-1; isspace(*p1); *p1--=0);
495 for (i=0; i<sp; i++)
496 if (sstrcmp(incstack[i].confname, p) == 0)
497 { fprintf(stderr, "Line %d: WARNING: recursive include of file %s detected and fixed!\n", actualLineNr, p);
498 continue;
499 }
500 if (sp==maxsp)
501 incstack=srealloc(incstack, (maxsp+=10)*sizeof(*incstack));
502 incstack[sp].farr=hcfg;
503 hcfg=fopen(p, "rb");
504 if (hcfg==NULL)
505 { fprintf(stderr, "Can't open include file %s: %s!\n", p, strerror(errno));
506 hcfg=incstack[sp].farr;
507 wasError = 1;
508 continue;
509 }
510 incstack[sp].confname=curconfname;
511 incstack[sp].curline=actualLineNr;
512 sp++;
513 curconfname=sstrdup(p);
514 actualLineNr=0;
515 continue;
516 }
517 if ((strncasecmp(str, "commentchar", 11) == 0) && isspace(str[11]))
518 {
519 for (p=str+11; isspace(*p); p++);
520 if (!*p)
521 { printf("\"%s\", line %d: a comment character after CommentChar is missing!\n", curconfname, actualLineNr);
522 continue;
523 }
524 if (!strchr(TRUE_COMMENT, *p))
525 { printf("\"%s\", line %d: CommentChar - '%c' is not a valid comment character!\n", curconfname, actualLineNr, *p);
526 } else
527 { char buf2[2]="\0";
528 buf2[0]=CommentChar;
529 setvar(buf2,"");
530 CommentChar = *p;
531 buf2[0]=CommentChar;
532 setvar(buf2,buf2);
533 }
534 continue;
535 }
536 return line;
537 }
538 }
539
checkIncludeLogic(ps_fidoconfig config)540 void checkIncludeLogic(ps_fidoconfig config)
541 {
542 UINT i, j;
543
544 for (j=0; j<config->linkCount; j++) {
545 if (config->links[j]->areafix.autoCreateFile==NULL) continue;
546 for (i=0; i<cfgNamesCount; i++) {
547 if (cmpfnames(cfgNames[i],config->links[j]->areafix.autoCreateFile)==0)
548 break;
549 }
550 /* if not found include file - return error */
551 if (i==cfgNamesCount) {
552 printf("areafixAutoCreateFile %s has never been included in config!\n",
553 config->links[j]->areafix.autoCreateFile);
554 exit(EX_CONFIG);
555 }
556 }
557
558 for (j=0; j<config->linkCount; j++) {
559 if (config->links[j]->filefix.autoCreateFile==NULL) continue;
560 for (i=0; i<cfgNamesCount; i++) {
561 if (cmpfnames(cfgNames[i],config->links[j]->filefix.autoCreateFile)==0) break;
562 }
563 /* if not found include file - return error */
564 if (i==cfgNamesCount) {
565 printf("filefixAutoCreateFile %s has never been included in config!\n",
566 config->links[j]->filefix.autoCreateFile);
567 exit(EX_CONFIG);
568 }
569 }
570 /* check for duplicate includes */
571 for( i = 0; i < cfgNamesCount - 1; i++ )
572 for ( j = i+1; j < cfgNamesCount; j++ )
573 if (cmpfnames(cfgNames[i],cfgNames[j])==0)
574 {
575 printf("File %s is included in config more than once!\n",cfgNames[i]);
576 exit(EX_CONFIG);
577 }
578
579 }
580
getCurConfName()581 const char* getCurConfName()
582 {
583 return curconfname;
584 }
585
getCurConfPos()586 long getCurConfPos()
587 {
588 return curconfpos;
589 }
590
get_hcfgPos()591 long get_hcfgPos()
592 {
593 return ftell(hcfg);
594 }
595
get_hcfg()596 FILE *get_hcfg()
597 {
598 return hcfg;
599 }
600