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