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