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