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