chiark / gitweb /
Internal code review complete.
[userv.git] / parser.c
1 /*
2  * userv - parser.c
3  * configuration file parser; this file is actually #included from
4  * lexer.c, which is generated using flex from lexer.l, in turn from
5  * lexer.l.m4.  It's in a separate file so that we don't have to worry
6  * about m4 quoting &c., but we have to #include it so that the C
7  * objects from the lexer are available.
8  *
9  * Copyright (C)1996-1997 Ian Jackson
10  *
11  * This is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with userv; if not, write to the Free Software
23  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24  */
25
26 static int parse_file(const char *string, int *didexist);
27 static int parser(int allowce);
28 int parse_string(const char *string, const char *descrip, int isinternal);
29
30 /*
31  * Error-handling routines
32  */
33
34 static void closeerrorfile(void) {
35   if (eh.file && !eh.filekeep) {
36     if (fclose(eh.file)) {
37       eh.handling= tokv_word_errorstostderr;
38       parseerrprint("error writing to error log file `%s': %s",
39                     eh.filename,strerror(errno));
40     }
41     free(eh.filename);
42   }
43   eh.handling= 0;
44   eh.file= 0; eh.filename= 0; eh.filekeep= 0;
45 }
46
47 static void senderrmsg(const char *errmsg, int useehandling) {
48   char suberrmsg[MAX_ERRMSG_LEN];
49   int e;
50   time_t now;
51   struct tm *lt;
52
53   switch (useehandling) {
54   case tokv_word_errorstostderr:
55     senderrmsgstderr(errmsg);
56     break;
57   case tokv_word_errorstosyslog:
58     ensurelogopen(eh.logfacility);
59     syslog(eh.loglevel,"%s",errmsg);
60     break;
61   case tokv_word_errorstofile:
62     if (time(&now)==-1) syscallerror("get current time");
63     lt= localtime(&now); if (!lt) syscallerror("convert current time");
64     if (fprintf(eh.file,"%d-%02d-%02d %02d:%02d:%02d: uservd: %s\n",
65                 lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday,
66                 lt->tm_hour, lt->tm_min, lt->tm_sec,
67                 errmsg) == EOF) {
68       e= errno;
69       closeerrorfile(); eh.handling= tokv_word_errorstofile;
70       snyprintf(suberrmsg,sizeof(suberrmsg),
71                 "error writing to error log file `%.*s': %s;"
72                 " reverting to errors-to-stderr",
73                 (int)(sizeof(suberrmsg)>>1),eh.filename,strerror(e));
74       senderrmsg(suberrmsg,eh.handling);
75       senderrmsg(errmsg,eh.handling);
76     }
77     break;
78   default:
79     abort();
80   }
81 }
82
83 static void errwhere(struct parser_state *tstate, char *bufput, int bufputlen) {
84   static const char suffix[]= "references ...";
85   char errmsg[MAX_ERRMSG_LEN];
86   
87   if (!tstate) {
88     strnycpy(bufput,"<initialisation>: ",bufputlen);
89     return;
90   }
91   if (!tstate->notedreferer && tstate->upstate && !tstate->upstate->isinternal) {
92     errwhere(tstate->upstate,errmsg,sizeof(errmsg)-sizeof(suffix));
93     strcat(errmsg,suffix);
94     senderrmsg(errmsg,eh.handling);
95     tstate->notedreferer= 1;
96   }
97   snyprintf(bufput,bufputlen,"%.*s:%d: ",bufputlen-10,
98             tstate->filename,tstate->reportlineno);
99 }
100
101 void parseerrprint(const char *fmt, ...) {
102   va_list al;
103   char errmsg[MAX_ERRMSG_LEN];
104
105   va_start(al,fmt);
106   errwhere(cstate,errmsg,sizeof(errmsg)>>1);
107   vsnytprintfcat(errmsg,sizeof(errmsg),fmt,al);
108   senderrmsg(errmsg,eh.handling);
109   va_end(al);
110 }
111
112 static int unexpected(int found, int wanted, const char *wantedstr) {
113   /* pass wanted==-1 if you know it's not what you wanted;
114    * otherwise this function will check it for you.
115    */
116   if (found == wanted) return 0;
117   if (found == tokv_error) return found;
118   parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
119   return tokv_error;
120 }
121
122 static int stringoverflow(const char *where) {
123   parseerrprint("string buffer became far too large building %s",where);
124   return tokv_error;
125 }
126
127 /*
128  * General assistance functions
129  */
130
131 static void freecharparray(char **array) {
132   char **pp;
133
134   if (!array) return;
135   for (pp=array; *pp; pp++) free(*pp);
136   free(array);
137 }
138
139 static int dequote(char *inplace) {
140   char *p, *q, buf[4], *bep;
141   int v;
142   
143   p=q=inplace;
144   assert(*p++ = '"');
145   while (*p && *p != '"') {
146     if (*p != '\\') { *q++= *p++; continue; }
147     switch (*++p) {
148     case 'n': *q++= '\n'; continue;
149     case 'r': *q++= '\r'; continue;
150     case 't': *q++= '\t'; continue;
151     case 'x':
152       assert(buf[0]= *++p); assert(buf[1]= *++p); buf[2]= 0;
153       v= strtoul(buf,&bep,16); assert(bep == buf+2);
154       assert(!(v & ~0xff)); *q++= v; p++; continue;
155     default:
156       if (isalpha(*p)) {
157         parseerrprint("unknown \\<letter> sequence \\%c in quoted string",*p);
158         return tokv_error;
159       } else if (isdigit(*p)) {
160         assert(buf[0]= *++p); assert(buf[1]= *++p); assert(buf[2]= *++p);
161         buf[3]= 0; v= strtoul(buf,&bep,8);
162         if (bep != buf+3 || (v & ~0xff)); {
163           parseerrprint("invalid \\<octal> sequence \\%s in quoted string",buf);
164           return tokv_error;
165         }
166         *q++= v; p++; continue;
167       } else if (ispunct(*p)) {
168         *q++= *p++; continue;
169       } else {
170         while (*p==' ' || *p=='\t') p++;
171         assert(*p=='\n');
172       }
173     }
174   }
175   assert(*p); assert(!*++p);
176   return tokv_quotedstring;
177 }
178
179 const char *printtoken(int token) {
180   /* Returns pointer to static buffer, overwritten by next call. */
181   static const char keywordfmt[]= "keyword `%s'";
182   static const char operatorfmt[]= "operator `%s'";
183   
184   static char buf[250];
185   
186   char *q;
187   const char *p;
188   int i, c, l;
189   
190   if ((token & tokm_repres) == tokr_word) {
191     assert(strlen(yytext)+sizeof(keywordfmt)<sizeof(buf));
192     snyprintf(buf,sizeof(buf),keywordfmt,yytext);
193     return buf;
194   } else if (token & tokt_number) {
195     snyprintf(buf,sizeof(buf),"number %d",lr_min); return buf;
196   } else if (token & tokt_fdrange && lr_max==-1) {
197     snyprintf(buf,sizeof(buf),"fdrange %d-",lr_min); return buf;
198   } else if (token & tokt_fdrange) {
199     snyprintf(buf,sizeof(buf),"fdrange %d-%d",lr_min,lr_max); return buf;
200   } else if ((token & tokm_repres) == tokr_punct) {
201     assert(strlen(yytext)+sizeof(operatorfmt)<sizeof(buf));
202     snyprintf(buf,sizeof(buf),operatorfmt,yytext);
203     return buf;
204   } else if (token & tokt_string) {
205     switch (token) {
206     case tokv_barestring: strcpy(buf,"unquoted string (bare word)"); break;
207     case tokv_quotedstring: strcpy(buf,"quoted string"); break;
208     default: abort();
209     }
210     strcat(buf," `");
211     l= strlen(buf); i= sizeof(buf)-l-2; p= yytext; q= buf+l;
212     while ((c= *p++)) {
213       if (i-- <= 0) { q--; strcpy(q-3,"..."); break; }
214       if (isspace(c)) c= ' ';
215       else if (!isprint(c) || iscntrl(c)) c= '?';
216       else *q++= c;
217     }
218     strcpy(q,"'");
219     return buf;
220   } else {
221     switch (token) {
222     case tokv_lwsp:    return "linear whitespace";
223     case tokv_newline: return "newline (or comment followed by newline)";
224     case tokv_eof:     return "end of input file";
225     case tokv_error:   return "syntax error token";
226     default:
227       sprintf(buf,"token#0%o",token); return buf;
228     }
229   }
230 }
231
232 static const char *string2path(const char *in) {
233   /* Returned pointers become invalid on next call.
234    * May return 0, having printed an error message.
235    */
236   static char *p;
237   static int pl;
238   
239   if (strncmp(in,"~/",2)) return in;
240   if (makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1)) {
241     stringoverflow("pathname");
242     return 0;
243   }
244   snyprintf(p,pl,"%s/%s",serviceuser_dir,in+2);
245   return p;
246 }
247
248 /*
249  * Parser component functions for parsing parameters to directives
250  *
251  * Functions pa_... parse just one parameter,
252  *  and return tokv_error or 0, having scanned exactly what they were expecting
253  * Functions paa_... parse complete parameter lists, including the leading lwsp,
254  *  and return tokv_error or 0, having scanned up to and including the newline
255  *
256  * For any function which takes `const char **rv' the
257  * value returned in *rv is overwritten by repeated calls to
258  * any of those functions (whether the same or another).
259  */
260
261 static int pa_mnl(void) {
262   return unexpected(yylex(),tokv_newline,"newline");
263 }
264 static int pa_mwsp(void) {
265   return unexpected(yylex(),tokv_lwsp,"linear whitespace");
266 }
267
268 static int pa_string(const char **rv) {
269   static char *p= 0;
270   static int pl= 0;
271
272   int r, l;
273
274   r= pa_mwsp(); if (r) return r;
275   r= yylex(); if (r == tokv_error) return r;
276   if ((r & tokm_repres) == tokr_nonstring) return unexpected(r,-1,"string");
277   l= strlen(yytext)+1;
278   if (makeroom(&p,&pl,l)) return stringoverflow("string argument");
279   strcpy(p,yytext); *rv= p;
280
281   return 0;
282 }  
283
284 static int pa_numberdollar(int *value_r) {
285   /* Also parses the whitespace beforehand. */
286   int r;
287
288   r= pa_mwsp(); if (r) return r;
289   r= yylex(); if (r == tokv_error || r == tokv_dollar) return r;
290   if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error;
291   *value_r= lr_min;
292   return r;
293 }
294
295 static int paa_1string(const char **rv) {
296   int r;
297
298   r= pa_string(rv); if (r) return r;
299   return pa_mnl();
300 }  
301
302 static int paa_1path(const char **rv) {
303   const char *cp;
304   int r;
305   
306   r= paa_1string(&cp); if (r) return r;
307   *rv= string2path(cp); if (!*rv) return tokv_error;
308   return 0;
309 }
310
311 static int paa_pathargs(const char **path_r, char ***newargs_r) {
312   /* Repeated calls do _not_ overwrite newargs_r; caller must free.
313    * Repeated calls _do_ overwrite string returned in path_r.
314    * Any call to this function invalidates previous returns in *rv.
315    */
316   char **newargs;
317   const char *string;
318   int used, size, r;
319
320   r= pa_string(&string); if (r == tokv_error) return r;
321   *path_r= string2path(string); if (!*path_r) return tokv_error;
322   
323   used=0; size=0;
324   newargs= xmalloc(sizeof(char*)*(size+1));
325
326   for (;;) {
327     r= yylex(); if (r == tokv_error) goto error;
328     if (r==tokv_newline) break;
329     if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) {
330       r= tokv_error; goto error;
331     }
332     r= yylex(); if (r==tokv_error) goto error;
333     if ((r & tokm_repres) == tokr_nonstring) {
334       r= unexpected(r,-1,"string for command argument");
335       goto error;
336     }
337     if (used>=size) {
338       if (used >= MAX_ARGSDEFVAR) {
339         parseerrprint("far too many arguments to service program");
340         r= tokv_error; goto error;
341       }
342       size= (used+5)<<1;
343       newargs= xrealloc(newargs,sizeof(char*)*(size+1));
344     }
345     newargs[used++]= xstrsave(yytext);
346   }
347   newargs[used]= 0;
348   *newargs_r= newargs;
349   return 0;
350   
351 error:
352   newargs[used]=0;
353   freecharparray(newargs);
354   return r;
355 }
356
357 static int paa_message(const char **message_r) {
358   /* Returned value is invalidated by repeated calls. */
359   static char *buildbuf;
360   static int buildbuflen;
361
362   int r, tl;
363
364   r= pa_mwsp(); if (r) return r;
365   tl= 1;
366   if (makeroom(&buildbuf,&buildbuflen,50)) return stringoverflow("start of message");
367   buildbuf[0]= 0;
368   for (;;) {
369     r= yylex(); if (r == tokv_error) return r;
370     if (r == tokv_eof) {
371       parseerrprint("unexpected end of file in message text");
372       return tokv_error;
373     }
374     if (r == tokv_newline) break;
375     tl+= strlen(yytext);
376     if (makeroom(&buildbuf,&buildbuflen,tl)) return stringoverflow("message");
377     strcat(buildbuf,yytext);
378   }
379   *message_r= buildbuf;
380   return 0;
381 }
382
383 /*
384  * Skipping routines (used by e.g. the `if' code).
385  */
386
387 static int skiptoeol(void) {
388   /* Returns 0 if OK, having just parsed the newline
389    * or tokv_error if an error occurs, leaving the position at the error
390    * (which may be EOF or a syntax error token).
391    */
392   int token;
393
394   do { token= yylex(); }
395   while (token != tokv_newline && !(token & tokt_exception));
396   if (token == tokv_newline) return 0;
397   if (token == tokv_error) return token;
398   assert(token == tokv_eof);
399   parseerrprint("unexpected end of file while looking for end of line");
400   return tokv_error;
401 }
402
403 static int skip(int allowce) {
404   /* Scans a piece of config without executing it.  Returns
405    * tokv_error (leaving the parsing state at the error),
406    * or the tokt_controlend token with type allowce if one
407    * was found (in which case rest of line, including any whitespace
408    * after tokt_controlend, has not been parsed), or tokv_eof.
409    */
410   int token, r;
411
412   for (;;) { /* loop over lines */
413     cstate->reportlineno= cstate->lineno;
414     do { token= yylex(); } while (token == tokv_lwsp);
415     if (token & tokt_exception) {
416       return token;
417     } else if (token & tokt_controlend) {
418       if (allowce == lr_controlend) return token;
419       else return unexpected(token,-1,"control structure end of a different kind");
420     } else if (token & tokt_controlstart) {
421       r= token;
422       while (r & tokt_controlstart) {
423         r= skiptoeol(); if (r) return r;
424         cstate->reportlineno= cstate->lineno;
425         r= skip(token); if (r & tokt_exception) return r;
426       }
427     } else if (!(token & tokt_directive) && !(token & tokt_condop)) {
428       parseerrprint("not a directive (or conditional operator) "
429                     "while looking for control structure end");
430       return tokv_error;
431     }
432     r= skiptoeol(); if (r) return r;
433   }
434 }
435
436 /*
437  * Routines for parsing and getting the values of parameters
438  */
439
440 static void parm_1string(char ***rvalues, const char *tocopy) {
441   char **a;
442   a= xmalloc(sizeof(char*)*2);
443   a[0]= xstrsave(tocopy);
444   a[1]= 0;
445   *rvalues= a;
446 }
447
448 static char *parm_ulong(unsigned long ul) {
449   char *p;
450   int l;
451
452   l= CHAR_BIT*sizeof(unsigned long)/3+4;
453   p= xmalloc(l);
454   snyprintf(p,l,"%lu",ul);
455   return p;
456 }
457
458 static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) {
459   char **a;
460   
461   a= xmalloc(sizeof(char*)*3);
462   a[0]= xstrsave(name);
463   a[1]= parm_ulong(id);
464   a[2]= 0;
465   *rvalues= a;
466   return 0;
467 }
468
469 static int parm_groups(char ***rvalues, int size,
470                        gid_t *gids, const char *const *groups) {
471   char **a;
472   int i;
473   
474   if (size >= 2 && gids[0] == gids[1]) { size--; gids++; groups++; }
475   a= xmalloc(sizeof(char*)*(size+1)*2);
476   for (i=0; i<size; i++) {
477     a[i]= xstrsave(groups[i]);
478     a[size+i]= parm_ulong(gids[i]);
479   }
480   a[size*2]= 0;
481   *rvalues= a;
482   return 0;
483 }  
484
485 static int pf_service(int ptoken, char ***rvalues) {
486   parm_1string(rvalues,service); return 0;
487 }
488
489 static int pf_callinguser(int ptoken, char ***rvalues) {
490   return parm_usernameuid(rvalues,logname,request_mbuf.callinguid);
491 }
492
493 static int pf_serviceuser(int ptoken, char ***rvalues) {
494   return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
495 }
496
497 static int pf_callinggroup(int ptoken, char ***rvalues) {
498   return parm_groups(rvalues,request_mbuf.ngids,calling_gids,calling_groups);
499 }
500
501 static int pf_servicegroup(int ptoken, char ***rvalues) {
502   return parm_groups(rvalues,service_ngids,service_gids,service_groups);
503 }
504
505 static int pf_callingusershell(int ptoken, char ***rvalues) {
506   parm_1string(rvalues,callinguser_shell); return 0;
507 }
508
509 static int pf_serviceusershell(int ptoken, char ***rvalues) {
510   parm_1string(rvalues,serviceuser_shell); return 0;
511 }
512
513 static int pa_parameter(char ***rvalues, char **rname) {
514   /* Scans a single parameter token and calls the appropriate parameter
515    * function, returning tokv_error (leaving the parser state wherever),
516    * or 0 on success (having just parsed the parameter name).
517    * If rname is non-null then the name of the parameter (malloc'd) will
518    * be stored in it.
519    *
520    * Caller must free *rvalues using freecharparray.
521    */
522   int token, r, i;
523   char *name;
524
525   token= yylex(); if (token == tokv_error) return token;
526   name= xstrsave(yytext);
527   if ((token & tokm_repres) != tokr_nonstring &&
528       !memcmp(yytext,"u-",2) && strlen(yytext)>=3) {
529     for (i=0;
530          i<request_mbuf.nvars && strcmp(yytext+2,defvararray[i].key);
531          i++);
532     if (i>=request_mbuf.nvars) {
533       *rvalues= xmalloc(sizeof(char*));
534       **rvalues= 0;
535     } else {
536       parm_1string(rvalues,defvararray[i].value);
537     }
538   } else if (token & tokt_parameter) {
539     r= (lr_parameter)(token,rvalues);
540     if (r) { free(name); return r; }
541   } else {
542     free(name);
543     return unexpected(token,-1,"parameter name");
544   }
545   debug_dumpparameter(name,*rvalues);
546   if (rname) *rname= name;
547   else free(name);
548   return 0;
549 }
550
551 /*
552  * Routines for parsing conditions, including
553  * parameter-based conditional functions (parmcondition's).
554  */
555
556 int pcf_glob(int ctoken, char *const *pv, int *rtrue) {
557   int token, actrue, r;
558   char *const *pp;
559
560   actrue= 0;
561   r= pa_mwsp(); if (r) return r;
562   for (;;) {
563     token= yylex();
564     if ((token & tokm_repres) == tokr_nonstring)
565       return unexpected(token,-1,"glob pattern");
566     for (pp= pv; !actrue && *pp; pp++) if (!fnmatch(yytext,*pp,0)) actrue= 1;
567     token= yylex(); 
568     if (token == tokv_newline) break;
569     if (unexpected(token,tokv_lwsp,"newline after or whitespace between glob patterns"))
570       return tokv_error;
571   }
572   *rtrue= actrue;
573   return 0;
574 }
575
576 int pcf_range(int ctoken, char *const *pv, int *rtrue) {
577   int mintoken, min, maxtoken, max, r;
578   char *const *pp;
579   char *ep;
580   unsigned long v;
581
582   r= pa_mwsp(); if (r) return r;
583   mintoken= pa_numberdollar(&min); if (mintoken == tokv_error) return mintoken;
584   r= pa_mwsp(); if (r) return r;
585   maxtoken= pa_numberdollar(&max); if (maxtoken == tokv_error) return maxtoken;
586   r= pa_mnl(); if (r) return r;
587   for (pp= pv; *pp; pp++) {
588     v= strtoul(*pp,&ep,10); if (*ep) continue;
589     if (mintoken != tokv_dollar && v < min) continue;
590     if (maxtoken != tokv_dollar && v > max) continue;
591     *rtrue= 1; return 0;
592   }
593   *rtrue= 0; return 0;
594 }
595
596 int pcf_grep(int ctoken, char *const *pv, int *rtrue) {
597   FILE *file;
598   const char *cp;
599   char *const *pp;
600   char *buf, *p;
601   int r, maxlen, l, c, actrue, posstrue;
602   
603   r= paa_1path(&cp); if (r) return r;
604   file= fopen(cp,"r");
605   if (!file) {
606     parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno));
607     return tokv_error;
608   }
609   maxlen= 0;
610   for (pp= pv; *pp; pp++) { l= strlen(*pp); if (l > maxlen) maxlen= l; }
611   buf= xmalloc(maxlen+2); actrue= 0; c= 0;
612   while (!actrue && c!=EOF) {
613     c= getc(file); if (c==EOF) break;
614     if (isspace(c)) continue;
615     l= maxlen+1; p= buf;
616     while (l>0 && c!='\n' && c!=EOF) { *p++= c; l--; c= getc(file); } 
617     if (c=='\n' || c==EOF || isspace(c)) {
618       while (p>buf && isspace(p[-1])) --p;
619       *p= 0; posstrue= 0;
620       for (pp= pv; !posstrue && *pp; pp++)
621         if (!strcmp(*pp,buf)) posstrue= 1;
622     } else {
623       posstrue= 0;
624     }
625     if (c!='\n' && c!=EOF) {
626       for (;;) {
627         c= getc(file);
628         if (c==EOF || c=='\n') break;
629         if (!isspace(c)) posstrue= 0;
630       }
631     }
632     if (posstrue) actrue= 1;
633   }
634   if (ferror(file)) {
635     parseerrprint("error while reading `%s' for grep: %s",cp,strerror(errno));
636     fclose(file); free(buf); return tokv_error;
637   }
638   assert(actrue || feof(file));
639   fclose(file); free(buf); *rtrue= actrue;
640   return 0;
641
642
643 static int pa_condition(int *rtrue) {
644   /* Scans up to and including the newline following the condition;
645    * may scan more than one line if the condition is a multi-line
646    * one.  Returns 0 (setting *rtrue, and parsing up to and including
647    * the last newline) or tokv_error.
648    * Expects to scan the whitespace before its condition.
649    */
650   int r, token, andor, ctrue, actrue;
651   char **parmvalues;
652
653   r= pa_mwsp(); if (r) return r;
654   token= yylex();
655   if (token == tokv_error) {
656     return token;
657   } else if (token == tokv_not) {
658     r= pa_condition(&ctrue); if (r) return r;
659     *rtrue= !ctrue; return 0;
660   } else if (token == tokv_openparen) {
661     andor= 0; actrue= -1;
662     for (;;) {
663       cstate->reportlineno= cstate->lineno;
664       r= pa_condition(&ctrue); if (r) return r;
665       switch (andor) {
666       case 0:         assert(actrue==-1); actrue= ctrue;           break;
667       case tokv_and:  assert(actrue>=0); actrue= actrue && ctrue;  break;
668       case tokv_or:   assert(actrue>=0); actrue= actrue || ctrue;  break;
669       default:        abort();
670       }
671       do { token= yylex(); } while (token == tokv_lwsp);
672       if (token == tokv_error) return token;
673       if (token == tokv_closeparen) break;
674       if (andor) {
675         r= unexpected(token,andor,"same conjunction as before"); if (r) return r;
676       } else {
677         if (token != tokv_and && token != tokv_or)
678           return unexpected(token,-1,"first conjunction inside connective");
679         andor= token;
680       }
681     }
682     r= pa_mnl(); if (r) return r;
683     *rtrue= actrue; return 0;
684   } else if (token & tokt_parmcondition) {
685     r= pa_mwsp(); if (r) return r;
686     r= pa_parameter(&parmvalues,0); if (r) return r;
687     r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
688     return r;
689   } else {
690     return unexpected(token,-1,"condition");
691   }
692 }
693
694 /*
695  * Directive functions and associated `common code' functions
696  */
697
698 /* Directives specifying the service program (execute-... and reset) */
699
700 static void execreset(void) {
701   execute= 0;
702   execbuiltin= 0;
703   free(execpath); execpath= 0;
704   freecharparray(execargs); execargs= 0;
705 }
706
707 int df_reject(int dtoken) {
708   int r;
709   
710   r= pa_mnl(); if (r) return r;
711   execreset();
712   execute= tokv_word_reject;
713   return 0;
714 }
715
716 int df_executefrompath(int dtoken) {
717   int r;
718   
719   r= pa_mnl(); if (r) return r;
720   execreset();
721   execute= tokv_word_executefrompath;
722   return 0;
723 }
724
725 int df_execute(int dtoken) {
726   const char *rv;
727   char **newargs;
728   int r;
729
730   r= paa_pathargs(&rv,&newargs); if (r) return r;
731   execreset();
732   execute= tokv_word_execute;
733   execargs= newargs;
734   execpath= xstrsave(rv);
735   return 0;
736 }
737
738 int df_executefromdirectory(int dtoken) {
739   const char *p, *q, *rv;
740   struct stat stab;
741   char *fn, **newargs;
742   int l, r;
743
744   r= paa_pathargs(&rv,&newargs); if (r) return r;
745   p= strrchr(service,'/'); if (p) p++; else p= service;
746   if (!*p || !isalnum(*p)) {
747     parseerrprint("execute-from-directory requires initial char of service "
748                   "portion to be alphanumeric (service portion was `%s')",
749                   p);
750     freecharparray(newargs);
751     return tokv_error;
752   }
753   for (q=p+1; *q; q++) {
754     if (!isalnum(*q) && *q != '-') {
755       parseerrprint("execute-from-directory requires service portion to "
756                     "contain only alphanumerics and hyphens (was `%s')",
757                     p);
758       freecharparray(newargs);
759       return tokv_error;
760     }
761   }
762   l= strlen(rv)+1+strlen(p)+1;
763   fn= xmalloc(l);
764   snyprintf(fn,l,"%s/%s",rv,p);
765   if (stat(fn,&stab)) {
766     if (errno == ENOENT) { free(fn); freecharparray(newargs); return 0; }
767     parseerrprint("failed to stat `%s' for execute-from-directory: %s",
768                   fn,strerror(errno));
769     free(fn); freecharparray(newargs); return tokv_error;
770   }
771   if (!S_ISREG(stab.st_mode)) {
772     parseerrprint("file `%s' in execute-from-directory is not an ordinary file"
773                   " or link to one (mode=0%lo)",fn,(unsigned long)stab.st_mode);
774     free(fn); freecharparray(newargs); return tokv_error;
775   }
776   execreset();
777   execute= tokv_word_executefromdirectory;
778   execargs= newargs;
779   execpath= fn;
780   return 0;
781 }
782
783 /* Parsing builtin service requests (execute-builtin) */
784
785 static int bispa_none(char ***rnewargs) {
786   return pa_mnl();
787 }
788
789 static int bispa_parameter(char ***rnewargs) {
790   int r, i;
791   char **parmvalues, *name, **newargs;
792   
793   r= pa_mwsp(); if (r) return r;
794   r= pa_parameter(&parmvalues,&name); if (r) return r;
795   for (i=0; parmvalues[i]; i++);
796   newargs= xmalloc(sizeof(char*)*(i+2));
797   newargs[0]= name;
798   memcpy(newargs+1,parmvalues,sizeof(char*)*(i+1));
799   free(parmvalues);
800   r= pa_mnl(); if (r) { free(newargs); return r; }
801   *rnewargs= newargs;
802   return 0;
803 }
804
805 int df_executebuiltin(int dtoken) {
806   int r;
807   builtinserviceexec_fnt *bisexec;
808   char *newpath, **newargs;
809
810   r= pa_mwsp(); if (r) return r;
811   r= yylex(); if (r == tokv_error) return r;
812   if (!(r & tokt_builtinservice)) return unexpected(r,-1,"builtin service name");
813   bisexec= lr_bisexec;
814   newpath= xstrsave(yytext);
815   newargs= 0;
816   r= lr_bispa(&newargs); if (r) { free(newpath); return r; }
817
818   execreset();
819   execute= tokv_word_executebuiltin;
820   execbuiltin= bisexec;
821   execpath= newpath;
822   execargs= newargs;
823   return 0;
824 }
825
826 /* Directives for changing other execution parameters */
827
828 int dfg_setflag(int dtoken) {
829   int r;
830
831   r= pa_mnl(); if (r) return r;
832   *lr_flag= lr_flagval;
833   return 0;
834 }
835
836 int df_reset(int dtoken) {
837   int r;
838
839   r= pa_mnl(); if (r) return r;
840   r= parse_string(RESET_CONFIGURATION,"<builtin reset configuration>",1);
841   assert(!r);
842   return 0;  
843 }
844
845 int dfg_fdwant(int dtoken) {
846   int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;
847
848   needreadwrite= lr_fdwant_readwrite;
849   r= pa_mwsp(); if (r) return r;
850   r= yylex(); if (r == tokv_error) return r;
851   if (!(r & tokt_fdrange)) return unexpected(r,-1,"file descriptor range");
852   fdmin= lr_min; fdmax= lr_max;
853   if (fdmin<0 || fdmin>MAX_ALLOW_FD ||
854       (fdmax != -1 && fdmax<0) || fdmax>MAX_ALLOW_FD) {
855     parseerrprint("file descriptor in range is negative or far too large");
856     return tokv_error;
857   }
858   r= yylex(); if (r == tokv_error) return r;
859   if (r == tokv_newline) {
860     if (needreadwrite > 0) {
861       parseerrprint("read or write is required");
862       return tokv_error;
863     }
864     havereadwrite= 0;
865   } else if (r == tokv_lwsp) {
866     if (needreadwrite < 0) {
867       parseerrprint("read or write not allowed"); return tokv_error;
868     }
869     r= yylex(); if (r == tokv_error) return r;
870     if (!(r & tokt_readwrite))
871       return unexpected(r,-1,"read or write (or perhaps newline)");
872     havereadwrite= r;
873     r= pa_mnl(); if (r) return r;
874   } else {
875     return unexpected(r,-1,"whitespace before read or write or newline");
876   }
877   ensurefdarray(fdmin);
878   if (fdmax == -1) {
879     if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd))
880       parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd");
881     fdmax= fdarrayused-1;
882     restfdwantstate= dtoken;
883     restfdwantrw= havereadwrite;
884   }
885   ensurefdarray(fdmax);
886   for (fd=fdmin; fd<=fdmax; fd++) {
887     fdarray[fd].wantstate= dtoken;
888     fdarray[fd].wantrw= havereadwrite;
889   }
890   return 0;
891 }
892
893 /* Directives for changing error handling */
894
895 int df_errorstostderr(int dtoken) {
896   int r;
897   
898   r= pa_mnl(); if (r) return r;
899   closeerrorfile(); eh.handling= dtoken;
900   return 0;
901 }
902
903 int df_errorstosyslog(int dtoken) {
904   int token, level, facility;
905
906   facility= DEFUSERLOGFACILITY;
907   level= DEFUSERLOGLEVEL;
908   token= yylex();
909   if (token == tokv_lwsp) {
910     token= yylex(); if (token == tokv_error) return token;
911     if (!(token & tokt_logfacility))
912       return unexpected(token,-1,"syslog facility (or end of line)");
913     facility= lr_logfacility;
914     token= yylex();
915   }    
916   if (token == tokv_lwsp) {
917     token= yylex(); if (token == tokv_error) return token;
918     if (!(token & tokt_loglevel))
919       return unexpected(token,-1,"syslog level (or end of line) after facility");
920     level= lr_loglevel;
921     token= yylex();
922   }
923   if (unexpected(token,tokv_newline,"end of line after errors-to-syslog"))
924     return tokv_error;
925   closeerrorfile(); eh.handling= tokv_word_errorstosyslog;
926   eh.logfacility= facility; eh.loglevel= level;
927   return 0;
928 }
929
930 int df_errorstofile(int dtoken) {
931   const char *cp;
932   FILE *file;
933   int r;
934   
935   r= paa_1path(&cp); if (r) return r;
936   file= fopen(cp,"a");
937   if (!file) {
938     parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
939     return tokv_error;
940   }
941   if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) {
942     parseerrprint("unable to set line buffering on errors file: %s",strerror(errno));
943     fclose(file); return tokv_error;
944   }
945   closeerrorfile(); eh.handling= tokv_word_errorstofile;
946   eh.file= file; eh.filename= xstrsave(cp);
947   return 0;
948 }
949
950 /* Directives for including other files or configuration data */
951
952 int dfi_includeuserrcfile(int dtoken) {
953   int r;
954
955   r= pa_mnl(); if (r) return r;
956   assert(userrcfile);
957   return parse_file(userrcfile,0);
958 }
959
960 int dfi_includeclientconfig(int dtoken) {
961   int r;
962
963   r= pa_mnl(); if (r) return r;
964   assert(overridedata);
965   return parse_string(overridedata,"<configuration override data>",0);
966 }
967
968 int df_include(int dtoken) {
969   const char *cp;
970   int r, found;
971
972   r= paa_1path(&cp); if (r) return r;
973   r= parse_file(cp,&found); if (r) return r;
974   if (found || dtoken == tokv_word_includeifexist) return 0;
975   parseerrprint(dtoken == tokv_word_includesysconfig ?
976                 "system configuration file `%s' does not exist" :
977                 "included file `%s' does not exist",
978                 cp);
979   return tokv_error;
980 }
981
982 int df_includedirectory(int dtoken) {
983   static char *buildbuf=0;
984   static int buildbuflen=0;
985   
986   int r, cpl, tel, c, found;
987   DIR *d;
988   struct dirent *de;
989   const char *p, *cp;
990   
991   r= paa_1path(&cp); if (r) return r;
992   d= opendir(cp);
993   if (!d) {
994     parseerrprint("unable to open directory `%s': %s",cp,strerror(errno));
995     return tokv_error;
996   }
997   cpl= strlen(cp);
998   while ((de= readdir(d))) {
999     tel= strlen(de->d_name);
1000     if (!tel) continue;
1001     p= de->d_name;
1002     if (!*p || !isalnum(*p)) continue;
1003     while ((c= *++p)) if (!(isalnum(c) || c=='-')) break;
1004     if (c) continue;
1005     if (makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1))
1006       return stringoverflow("pathname in directory");
1007     snyprintf(buildbuf,buildbuflen,"%s/%s",cp,de->d_name);
1008     r= parse_file(buildbuf,&found); if (r) { closedir(d); return r; }
1009     if (!found) {
1010       parseerrprint("unable to open file `%s' in included directory `%s': %s",
1011                     de->d_name,cp,strerror(errno));
1012       closedir(d);
1013       return tokv_error;
1014     }
1015   }
1016   if (closedir(d)) {
1017     parseerrprint("error closing directory `%s': %s",cp,strerror(errno));
1018     return tokv_error;
1019   }
1020   return 0;
1021 }
1022
1023 int df_includelookup(int dtoken) {
1024   static char *buildbuf=0;
1025   int buildbuflen=0;
1026   
1027   char **parmvalues, **pp, *p, *q;
1028   const char *cp;
1029   struct stat stab;
1030   int r, done, thisdone, cpl, c;
1031
1032   r= pa_mwsp(); if (r) return r;
1033   r= pa_parameter(&parmvalues,0); if (r) return r;
1034   r= paa_1path(&cp); if (r) { freecharparray(parmvalues); return r; }
1035   if (stat(cp,&stab)) {
1036     parseerrprint("unable to access directory `%s': %s",cp,strerror(errno));
1037     freecharparray(parmvalues); return tokv_error;
1038   }
1039   if (!S_ISDIR(stab.st_mode)) {
1040     parseerrprint("object `%s' is not a directory or link to one",cp);
1041     freecharparray(parmvalues); return tokv_error;
1042   }
1043   done= 0;
1044   cpl= strlen(cp);
1045   if (!parmvalues[0]) {
1046     if (makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP)))
1047       return stringoverflow("pathname in directory for lookup of undefined parameter");
1048     snyprintf(buildbuf,buildbuflen,"%s/" NONEINCLUDELOOKUP,cp);
1049     r= parse_file(buildbuf,&thisdone);
1050     if (r) { freecharparray(parmvalues); return r; }
1051     if (thisdone) done= 1;
1052   } else {
1053     for (pp=parmvalues;
1054          *pp && (!done || dtoken == tokv_word_includelookupall);
1055          pp++) {
1056       if (makeroom(&buildbuf,&buildbuflen,
1057                    cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1))
1058         return stringoverflow("pathname in directory for lookup");
1059       strcpy(buildbuf,cp);
1060       p= *pp; q= buildbuf+cpl;
1061       *q++= '/';
1062       if (!*p) {
1063         strcpy(q,EMPTYINCLUDELOOKUP);
1064       } else {
1065         if (*p=='.') *q++= ':';
1066         while ((c= *p++)) {
1067           if (c=='/') { *q++= ':'; c='-'; }
1068           else if (c==':') { *q++= ':'; }
1069           *q++= c;
1070         }
1071         *q++= 0;
1072       }
1073       r= parse_file(buildbuf,&thisdone);
1074       if (r) { freecharparray(parmvalues); return r; }
1075       if (thisdone) done= 1;
1076     }
1077   }
1078   freecharparray(parmvalues);
1079   if (!done) {
1080     if (makeroom(&buildbuf,&buildbuflen,
1081                  cpl+1+sizeof(DEFAULTINCLUDELOOKUP)))
1082       return stringoverflow("pathname in directory for lookup of default");
1083     snyprintf(buildbuf,buildbuflen,"%s/" DEFAULTINCLUDELOOKUP,cp);
1084     r= parse_file(buildbuf,0); if (r) return r;
1085   }
1086   return 0;
1087 }
1088
1089 /* Control constructs */
1090
1091 int df_catchquit(int dtoken) {
1092   int r;
1093
1094   r= pa_mnl(); if (r) return r;
1095   r= parser(tokv_word_catchquit);
1096   if (r == tokv_quit || r == tokv_error) {
1097     if (r == tokv_error) {
1098       r= parse_string(RESET_CONFIGURATION,
1099                       "<builtin reset configuration (caught error)>",1);
1100       assert(!r);
1101     }
1102     r= skip(tokv_word_catchquit);
1103   }
1104   if (r & tokt_controlend) {
1105     assert(r == tokv_word_hctac);
1106     r= pa_mnl();
1107   }
1108   return r;
1109 }
1110
1111 int df_if(int dtoken) {
1112   int r, true, done;
1113   
1114   done= 0;
1115   do {
1116     r= pa_condition(&true); if (r) return r;
1117     if (!done && true) { r= parser(tokv_word_if); done= 1; }
1118     else { r= skip(tokv_word_if); }
1119     if (!(r & tokt_controlend)) return r;
1120   } while (r == tokv_word_elif);
1121   if (r == tokv_word_else) {
1122     r= pa_mnl(); if (r) return r;
1123     cstate->reportlineno= cstate->lineno;
1124     if (done) r= skip(tokv_word_if);
1125     else r= parser(tokv_word_if);
1126     if (!(r & tokt_controlend)) return r;
1127   }
1128   if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error;
1129   return pa_mnl();
1130 }
1131
1132 int df_errorspush(int dt) {
1133   struct error_handling save;
1134   int r;
1135
1136   r= pa_mnl(); if (r) return r;
1137
1138   save= eh;
1139   eh.filekeep= 1;
1140
1141   r= parser(tokv_word_errorspush);
1142
1143   closeerrorfile();
1144   eh= save;
1145
1146   if (r & tokt_controlend) {
1147     assert(r == tokv_word_srorre);
1148     r= pa_mnl();
1149   }
1150   return r;
1151 }
1152
1153 /* Miscelleanous directives */
1154
1155 int df_cd(int dtoken) {
1156   const char *cp;
1157   int r;
1158
1159   r= paa_1path(&cp); if (r) return r;
1160   if (!chdir(cp)) return 0;
1161   parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
1162   return tokv_error;
1163 }
1164
1165 int df_userrcfile(int dtoken) {
1166   const char *cp;
1167   int r;
1168
1169   r= paa_1path(&cp); if (r) return r;
1170   free(userrcfile); userrcfile= xstrsave(cp);
1171   return 0;
1172 }
1173
1174 int df_message(int dtoken) {
1175   const char *mp;
1176   int r;
1177
1178   r= paa_message(&mp); if (r) return r;
1179   parseerrprint("`message' directive: %s",mp);
1180   return 0;
1181 }
1182
1183 int df_error(int dtoken) {
1184   const char *mp;
1185   int r;
1186
1187   r= paa_message(&mp); if (r) return r;
1188   parseerrprint("`error' directive: %s",mp);
1189   return tokv_error;
1190 }
1191
1192 int df_eof(int dtoken) {
1193   int r;
1194
1195   r= pa_mnl(); if (r) return r;
1196   return tokv_eof;
1197 }
1198
1199 int df_quit(int dtoken) {
1200   int r;
1201
1202   r= pa_mnl(); if (r) return r;
1203   return tokv_quit;
1204 }
1205
1206 /*
1207  * Main parser routines
1208  */
1209       
1210 static void parser_push(struct parser_state *usestate,
1211                         const char *newfile,
1212                         const struct stat *newfilestab,
1213                         YY_BUFFER_STATE ybuf,
1214                         int isinternal) {
1215   usestate->lineno= 1;
1216   usestate->reportlineno= 1;
1217   usestate->filename= newfile;
1218   usestate->filestab= *newfilestab;
1219   usestate->notedreferer= 0;
1220   usestate->isinternal= isinternal;
1221   usestate->ybuf= ybuf;
1222   usestate->upstate= cstate;
1223
1224   cstate= usestate;
1225   yy_switch_to_buffer(ybuf);
1226 }
1227
1228 static void parser_pop(void) {
1229   struct parser_state *oldstate;
1230
1231   oldstate= cstate;
1232   cstate= cstate->upstate;
1233   if (cstate) yy_switch_to_buffer(cstate->ybuf);
1234   yy_delete_buffer(oldstate->ybuf);
1235 }
1236
1237 int parse_string(const char *string, const char *descrip, int isinternal) {
1238   /* Returns the same things as parser, except that tokv_eof
1239    * is turned into 0.
1240    */
1241   static const struct stat blankstab;
1242   
1243   struct parser_state usestate;
1244   YY_BUFFER_STATE ybuf;
1245   int r;
1246
1247   ybuf= yy_scan_string(string);
1248   if (!ybuf) syscallerror("unable to create flex buffer for internal string");
1249   parser_push(&usestate,descrip,&blankstab,ybuf,isinternal);
1250   
1251   r= parser(0);
1252
1253   parser_pop();
1254   if (r == tokv_eof) r= 0;
1255   return r;
1256 }
1257
1258 static int parse_file(const char *string, int *didexist) {
1259   /* Returns the same things as parser, except that tokv_eof
1260    * is turned into 0.  If *didexist is 0 then errno will
1261    * have been set.
1262    */
1263   static int fileparselevel= 0;
1264   
1265   struct parser_state usestate, *checkrecurse;
1266   YY_BUFFER_STATE ybuf;
1267   int r;
1268   FILE *file;
1269   struct stat newstab;
1270
1271   if (fileparselevel >= MAX_INCLUDE_NEST) {
1272     parseerrprint("too many nested levels of included files");
1273     return tokv_error;
1274   }
1275   file= fopen(string,"r");
1276   if (!file) {
1277     if (errno == ENOENT) {
1278       if (didexist) *didexist= 0;
1279       return 0;
1280     }
1281     parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
1282     return tokv_error;
1283   }
1284   r= fstat(fileno(file),&newstab); if (r) syscallerror("unable to fstat new file");
1285   for (checkrecurse= cstate; checkrecurse; checkrecurse= checkrecurse->upstate) {
1286     if (!checkrecurse->filestab.st_mode) continue;
1287     if (newstab.st_dev==checkrecurse->filestab.st_dev &&
1288         newstab.st_ino==checkrecurse->filestab.st_ino) {
1289       parseerrprint("recursion detected - config file `%s' calls itself",string);
1290       fclose(file);
1291       return tokv_error;
1292     }
1293   }
1294   
1295   if (didexist) *didexist= 1;
1296
1297   ybuf= yy_create_buffer(file,YY_BUF_SIZE);
1298   if (!ybuf) syscallerror("unable to create flex buffer for file");
1299   parser_push(&usestate,string,&newstab,ybuf,0);
1300   fileparselevel++;
1301   
1302   r= parser(0);
1303   if (ferror(file)) {
1304     parseerrprint("error reading configuration file `%s'",string);
1305     r= tokv_error;
1306   }
1307   
1308   fileparselevel--;
1309   parser_pop();
1310   fclose(file);
1311   if (r == tokv_eof) r= 0;
1312   return r;
1313 }
1314
1315 static int parser(int allowce) {
1316   /* Returns:
1317    *  an exception (error, eof or quit)
1318    *   then rest of `file' is uninteresting
1319    * or
1320    *  token if allowce was !0 and equal to token's controlend
1321    *   then rest of `file' (including rest of line with the
1322    *   controlend - even the whitespace) not scanned yet
1323    */
1324   int token, r;
1325
1326   for (;;) { /* loop over lines */
1327     cstate->reportlineno= cstate->lineno;
1328     do { token= yylex(); } while (token == tokv_lwsp);
1329     if (token & tokt_exception) {
1330       return token;
1331     } else if (token & tokt_controlend) {
1332       if (lr_controlend == allowce) return token;
1333       else return unexpected(token,-1,"directive (not this kind of"
1334                              " control structure end)");
1335     } else if (token & tokt_directive) {
1336       if ((token & tokt_internal) && !cstate->isinternal)
1337         return unexpected(token,-1,"published directive, not internal-use-only one");
1338       r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
1339     } else if (token == tokv_newline) {
1340       /* ignore blank lines (and comment-only lines) */
1341     } else {
1342       return unexpected(token,-1,"directive");
1343     }
1344   }
1345 }