chiark / gitweb /
finalise changelog prep. for cvs-buildpackage
[userv.git] / parser.c
index 0f8e195..3f40ffb 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -3,9 +3,10 @@
  * configuration file parser; this file is actually #included from
  * lexer.c, which is generated using flex from lexer.l, in turn from
  * lexer.l.m4.  It's in a separate file so that we don't have to worry
- * about m4 quoting &c.
+ * about m4 quoting &c., but we have to #include it so that the C
+ * objects from the lexer are available.
  *
- * Copyright (C)1996-1997 Ian Jackson
+ * Copyright (C)1996-1999,2001 Ian Jackson
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-static directive_fnt *lr_dir=0;
-static parmcondition_fnt *lr_parmcond=0;
-static parameter_fnt *lr_parameter=0;
-static int lr_loglevel, lr_logfacility, lr_min, lr_max, *lr_flag;
-static int lr_flagval, lr_controlend;
-static int lr_fdwant_readwrite;
+static int parse_file(const char *string, int *didexist);
+static int parser(int allowce);
+int parse_string(const char *string, const char *descrip, int isinternal);
 
-static void useless(void) { (void)yyunput; (void)useless; /* to shut up GCC ! */ }
+/*
+ * Error-handling routines
+ */
 
 static void closeerrorfile(void) {
   if (eh.file && !eh.filekeep) {
@@ -98,7 +98,7 @@ static void errwhere(struct parser_state *tstate, char *bufput, int bufputlen) {
            tstate->filename,tstate->reportlineno);
 }
 
-void parseerrprint(const char *fmt, ...) {
+int parseerrprint(const char *fmt, ...) {
   va_list al;
   char errmsg[MAX_ERRMSG_LEN];
 
@@ -107,8 +107,27 @@ void parseerrprint(const char *fmt, ...) {
   vsnytprintfcat(errmsg,sizeof(errmsg),fmt,al);
   senderrmsg(errmsg,eh.handling);
   va_end(al);
+
+  return tokv_error;
+}
+
+static int unexpected(int found, int wanted, const char *wantedstr) {
+  /* pass wanted==-1 if you know it's not what you wanted;
+   * otherwise this function will check it for you.
+   */
+  if (found == wanted) return 0;
+  if (found == tokv_error) return found;
+  return parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
+}
+
+static int stringoverflow(const char *where) {
+  return parseerrprint("string buffer became far too large building %s",where);
 }
 
+/*
+ * General assistance functions
+ */
+
 static void freecharparray(char **array) {
   char **pp;
 
@@ -117,63 +136,82 @@ static void freecharparray(char **array) {
   free(array);
 }
 
+static void countnewlines(void) {
+  char *p;
+  for (p=yytext; *p; p++)
+    if (*p == '\n')
+      cstate->lineno++;
+}
+
 static int dequote(char *inplace) {
   char *p, *q, buf[4], *bep;
   int v;
-  
+
   p=q=inplace;
   assert(*p++ = '"');
   while (*p && *p != '"') {
     if (*p != '\\') { *q++= *p++; continue; }
     switch (*++p) {
-    case 'n': *q++= '\n'; continue;
-    case 'r': *q++= '\r'; continue;
-    case 't': *q++= '\t'; continue;
+    case 'n': *q++= '\n'; p++; continue;
+    case 'r': *q++= '\r'; p++; continue;
+    case 't': *q++= '\t'; p++; continue;
     case 'x':
-      assert(buf[0]= *++p); assert(buf[1]= *++p); buf[2]= 0;
-      v= strtoul(buf,&bep,16); assert(bep == buf+2);
-      assert(!(v & ~0xff)); *q++= v; p++; continue;
+      p++;
+      if (!((buf[0]= *p++) && (buf[1]= *p++)))
+       return parseerrprint("quoted string ends inside \\x<hex> sequence");
+      buf[2]= 0;
+      v= strtoul(buf,&bep,16);
+      if (bep != buf+2)
+       return parseerrprint("invalid \\<hex> sequence \\x%s in quoted string",buf);
+      assert(!(v & ~0xff));
+      *q++= v;
+      continue;
     default:
-      if (isalpha(*p)) {
-        parseerrprint("unknown \\<letter> sequence \\%c in quoted string",*p);
-        return tokv_error;
-      } else if (isdigit(*p)) {
-        assert(buf[0]= *++p); assert(buf[1]= *++p); assert(buf[2]= *++p);
+      if (ISCHAR(isalpha,*p))
+        return parseerrprint("unknown \\<letter> sequence \\%c in quoted string",*p);
+      if (ISCHAR(isdigit,*p)) {
+        if (!((buf[0]= *p++) && (buf[1]= *p++) && (buf[2]= *p++))) abort();
         buf[3]= 0; v= strtoul(buf,&bep,8);
-        if (bep != buf+3 || (v & ~0xff)); {
-          parseerrprint("invalid \\<octal> sequence \\%s in quoted string",buf);
-          return tokv_error;
-        }
-        *q++= v; p++; continue;
-      } else if (ispunct(*p)) {
+        if (bep != buf+3 || (v & ~0xff))
+          return parseerrprint("invalid \\<octal> sequence \\%s in quoted string",buf);
+        *q++= v; continue;
+      } else if (ISCHAR(ispunct,*p)) {
         *q++= *p++; continue;
       } else {
        while (*p==' ' || *p=='\t') p++;
-       assert(*p=='\n');
+       v= *p++; assert(v=='\n');
       }
     }
   }
-  assert(*p); assert(!*++p); return tokv_quotedstring;
+  assert(*p); assert(!*++p);
+  *q++= 0;
+  return tokv_quotedstring;
 }
 
 const char *printtoken(int token) {
+  /* Returns pointer to static buffer, overwritten by next call. */
+  static const char keywordfmt[]= "keyword `%s'";
+  static const char operatorfmt[]= "operator `%s'";
+  
   static char buf[250];
   
   char *q;
   const char *p;
-  int i, c;
+  int i, c, l;
   
   if ((token & tokm_repres) == tokr_word) {
-    assert(strlen(yytext)+50<sizeof(buf));
-    sprintf(buf,"word `%s'",yytext);
+    assert(strlen(yytext)+sizeof(keywordfmt)<sizeof(buf));
+    snyprintf(buf,sizeof(buf),keywordfmt,yytext);
     return buf;
   } else if (token & tokt_number) {
-    sprintf(buf,"number %d",lr_min); return buf;
+    snyprintf(buf,sizeof(buf),"number %d",lr_min); return buf;
+  } else if (token & tokt_fdrange && lr_max==-1) {
+    snyprintf(buf,sizeof(buf),"fdrange %d-",lr_min); return buf;
   } else if (token & tokt_fdrange) {
-    sprintf(buf,"fdrange %d..%d",lr_min,lr_max); return buf;
+    snyprintf(buf,sizeof(buf),"fdrange %d-%d",lr_min,lr_max); return buf;
   } else if ((token & tokm_repres) == tokr_punct) {
-    assert(strlen(yytext)+50<sizeof(buf));
-    sprintf(buf,"operator `%s'",yytext);
+    assert(strlen(yytext)+sizeof(operatorfmt)<sizeof(buf));
+    snyprintf(buf,sizeof(buf),operatorfmt,yytext);
     return buf;
   } else if (token & tokt_string) {
     switch (token) {
@@ -182,11 +220,12 @@ const char *printtoken(int token) {
     default: abort();
     }
     strcat(buf," `");
-    i= sizeof(buf)-50; p= yytext; q= buf+strlen(buf);
-    while (i-- >0 && (c= *p++)) {
-      if (isspace(c)) c= ' ';
-      else if (!isprint(c) || iscntrl(c)) c= '?';
-      *q++= c;
+    l= strlen(buf); i= sizeof(buf)-l-2; p= yytext; q= buf+l;
+    while ((c= *p++)) {
+      if (i-- <= 0) { q--; strcpy(q-3,"..."); break; }
+      if (ISCHAR(isspace,c)) c= ' ';
+      else if (!ISCHAR(isprint,c) || ISCHAR(iscntrl,c)) c= '?';
+      else *q++= c;
     }
     strcpy(q,"'");
     return buf;
@@ -203,57 +242,34 @@ const char *printtoken(int token) {
 }
 
 static const char *string2path(const char *in) {
-  static char *p=0;
-  static int pl=0;
+  /* Returned pointers become invalid on next call.
+   * May return 0, having printed an error message.
+   */
+  static char *p;
+  static int pl;
   
   if (strncmp(in,"~/",2)) return in;
-  makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1);
+  if (makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1)) {
+    stringoverflow("pathname");
+    return 0;
+  }
   snyprintf(p,pl,"%s/%s",serviceuser_dir,in+2);
   return p;
 }
 
-static void parser_push(struct parser_state *usestate,
-                       const char *newfile,
-                       YY_BUFFER_STATE ybuf,
-                       int isinternal) {
-  usestate->lineno= 1;
-  usestate->reportlineno= 1;
-  usestate->filename= newfile;
-  usestate->notedreferer= 0;
-  usestate->isinternal= isinternal;
-  usestate->ybuf= ybuf;
-  usestate->upstate= cstate;
-
-  cstate= usestate;
-  yy_switch_to_buffer(ybuf);
-}
-
-static void parser_pop(void) {
-  struct parser_state *oldstate;
-
-  oldstate= cstate;
-  cstate= cstate->upstate;
-  if (cstate) yy_switch_to_buffer(cstate->ybuf);
-  yy_delete_buffer(oldstate->ybuf);
-}
-
-/* parser component functions pa_ return tokv_error or 0,
- *  having scanned exactly what they were expecting
- * complete argument list parsers paa_ return tokv_error or 0,
- *  having scanned up to and including the newline
+/*
+ * Parser component functions for parsing parameters to directives
+ *
+ * Functions pa_... parse just one parameter,
+ *  and return tokv_error or 0, having scanned exactly what they were expecting
+ * Functions paa_... parse complete parameter lists, including the leading lwsp,
+ *  and return tokv_error or 0, having scanned up to and including the newline
+ *
+ * For any function which takes `const char **rv' the
+ * value returned in *rv is overwritten by repeated calls to
+ * any of those functions (whether the same or another).
  */
 
-static int unexpected(int found, int wanted, const char *wantedstr) {
-  /* pass wanted==-1 if you know it's not what you wanted;
-   * otherwise this function will check it for you.
-   */
-  if (found == wanted) return 0;
-  if (found != tokv_error) {
-    parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
-  }
-  return tokv_error;
-}
-
 static int pa_mnl(void) {
   return unexpected(yylex(),tokv_newline,"newline");
 }
@@ -261,16 +277,7 @@ static int pa_mwsp(void) {
   return unexpected(yylex(),tokv_lwsp,"linear whitespace");
 }
 
-static void parm_1string(char ***rvalues, const char *tocopy) {
-  char **a;
-  a= xmalloc(sizeof(char*)*2);
-  a[0]= xstrsave(tocopy);
-  a[1]= 0;
-  *rvalues= a;
-}
-
 static int pa_string(const char **rv) {
-  /* Value returned in *rv is overwritten by repeated calls */
   static char *p= 0;
   static int pl= 0;
 
@@ -280,14 +287,24 @@ static int pa_string(const char **rv) {
   r= yylex(); if (r == tokv_error) return r;
   if ((r & tokm_repres) == tokr_nonstring) return unexpected(r,-1,"string");
   l= strlen(yytext)+1;
-  makeroom(&p,&pl,l);
+  if (makeroom(&p,&pl,l)) return stringoverflow("string argument");
   strcpy(p,yytext); *rv= p;
 
   return 0;
 }  
 
+static int pa_numberdollar(int *value_r) {
+  /* Also parses the whitespace beforehand. */
+  int r;
+
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error || r == tokv_dollar) return r;
+  if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error;
+  *value_r= lr_min;
+  return r;
+}
+
 static int paa_1string(const char **rv) {
-  /* Value returned in *rv is overwritten by repeated calls */
   int r;
 
   r= pa_string(rv); if (r) return r;
@@ -295,103 +312,95 @@ static int paa_1string(const char **rv) {
 }  
 
 static int paa_1path(const char **rv) {
-  /* Value returned in *rv is overwritten by repeated calls */
   const char *cp;
   int r;
   
   r= paa_1string(&cp); if (r) return r;
-  *rv= string2path(cp); return 0;
-}
-
-static int pa_parameter(char ***rvalues) {
-  /* Scans a single parameter token and calls the appropriate parameter
-   * function, returning tokv_error or 0 just like the parameter function.
-   */
-  int token, r, i;
-
-  token= yylex(); if (token == tokv_error) return token;
-  if ((token & tokm_repres) != tokr_nonstring &&
-      !memcmp(yytext,"u-",2) && strlen(yytext)>=3) {
-    for (i=0;
-        i<request_mbuf.nvars && strcmp(yytext+2,defvararray[i][0]);
-        i++);
-    if (i>=request_mbuf.nvars) {
-      *rvalues= xmalloc(sizeof(char*));
-      **rvalues= 0;
-    } else {
-      parm_1string(rvalues,defvararray[i][1]);
-    }
-  } else {
-    if (!(token & tokt_parameter)) return unexpected(token,-1,"parameter name");
-    r= (lr_parameter)(token,rvalues);
-    if (r) return r;
-  }
-  debug_dumpparameter(yytext,*rvalues);
+  *rv= string2path(cp); if (!*rv) return tokv_error;
   return 0;
 }
 
-static int pa_condition(int *rtrue) {
-  /* Scans up to and including the newline following the condition;
-   * may scan more than one line if the condition is a multi-line
-   * one.  Returns 0 (setting *rtrue) or tokv_error.  Expects to
-   * scan the whitespace before its condition.
+static int paa_pathargs(const char **path_r, char ***newargs_r) {
+  /* Repeated calls do _not_ overwrite newargs_r; caller must free.
+   * Repeated calls _do_ overwrite string returned in path_r.
+   * Any call to this function invalidates previous returns in *rv.
    */
-  int r, token, andor, ctrue, actrue;
-  char **parmvalues;
+  char **newargs;
+  const char *string;
+  int used, size, r;
 
-  r= pa_mwsp(); if (r) return r;
-  token= yylex();
-  if (token == tokv_error) {
-    return token;
-  } else if (token == tokv_not) {
-    r= pa_condition(&ctrue); if (r) return r;
-    *rtrue= !ctrue; return 0;
-  } else if (token == tokv_openparen) {
-    andor= 0; actrue= -1;
-    for (;;) {
-      cstate->reportlineno= cstate->lineno;
-      r= pa_condition(&ctrue); if (r) return r;
-      switch (andor) {
-      case 0:         assert(actrue==-1); actrue= ctrue;           break;
-      case tokv_and:  assert(actrue>=0); actrue= actrue && ctrue;  break;
-      case tokv_or:   assert(actrue>=0); actrue= actrue || ctrue;  break;
-      default:        abort();
-      }
-      do { token= yylex(); } while (token == tokv_lwsp);
-      if (token == tokv_error) return token;
-      if (token == tokv_closeparen) break;
-      if (andor) {
-       r= unexpected(token,andor,"same conjunction as before"); if (r) return r;
-      } else {
-        if (token != tokv_and && token != tokv_or)
-          return unexpected(token,-1,"first conjunction inside connective");
-        andor= token;
+  r= pa_string(&string); if (r == tokv_error) return r;
+  *path_r= string2path(string); if (!*path_r) return tokv_error;
+  
+  used=0; size=0;
+  newargs= xmalloc(sizeof(char*)*(size+1));
+
+  for (;;) {
+    r= yylex(); if (r == tokv_error) goto error;
+    if (r==tokv_newline) break;
+    if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) {
+      r= tokv_error; goto error;
+    }
+    r= yylex(); if (r==tokv_error) goto error;
+    if ((r & tokm_repres) == tokr_nonstring) {
+      r= unexpected(r,-1,"string for command argument");
+      goto error;
+    }
+    if (used>=size) {
+      if (used >= MAX_ARGSDEFVAR) {
+       r= parseerrprint("far too many arguments to service program");
+       goto error;
       }
+      size= (used+5)<<1;
+      newargs= xrealloc(newargs,sizeof(char*)*(size+1));
     }
-    r= pa_mnl(); if (r) return r;
-    *rtrue= actrue; return 0;
-  } else if (token & tokt_parmcondition) {
-    r= pa_mwsp(); if (r) return r;
-    r= pa_parameter(&parmvalues); if (r) return r;
-    r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
-    return r;
+    newargs[used++]= xstrsave(yytext);
   }
-  return unexpected(token,-1,"condition");
+  newargs[used]= 0;
+  *newargs_r= newargs;
+  return 0;
+  
+error:
+  newargs[used]=0;
+  freecharparray(newargs);
+  return r;
 }
 
-static int pa_numberdollar(int *rv) {
-  /* Also parses the whitespace beforehand. */
-  int r;
+static int paa_message(const char **message_r) {
+  /* Returned value is invalidated by repeated calls. */
+  static char *buildbuf;
+  static int buildbuflen;
+  const char *usetext;
+
+  int r, tl;
 
   r= pa_mwsp(); if (r) return r;
-  r= yylex(); if (r == tokv_error || r == tokv_dollar) return r;
-  if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error;
-  *rv= lr_min; return r;
+  tl= 1;
+  if (makeroom(&buildbuf,&buildbuflen,50)) return stringoverflow("start of message");
+  buildbuf[0]= 0;
+  for (;;) {
+    r= yylex(); if (r == tokv_error) return r;
+    if (r == tokv_eof)
+      return parseerrprint("unexpected end of file in message text");
+    if (r == tokv_newline) break;
+    usetext= r == tokv_lwsp ? " " : yytext;
+    tl+= strlen(usetext);
+    if (makeroom(&buildbuf,&buildbuflen,tl)) return stringoverflow("message");
+    strcat(buildbuf,usetext);
+  }
+  *message_r= buildbuf;
+  return 0;
 }
 
-/* Main parser routines */
+/*
+ * Skipping routines (used by e.g. the `if' code).
+ */
 
 static int skiptoeol(void) {
+  /* Returns 0 if OK, having just parsed the newline
+   * or tokv_error if an error occurs, leaving the position at the error
+   * (which may be EOF or a syntax error token).
+   */
   int token;
 
   do { token= yylex(); }
@@ -399,14 +408,15 @@ static int skiptoeol(void) {
   if (token == tokv_newline) return 0;
   if (token == tokv_error) return token;
   assert(token == tokv_eof);
-  parseerrprint("unexpected end of file while looking for end of line");
-  return tokv_error;
+  return parseerrprint("unexpected end of file while looking for end of line");
 }
 
 static int skip(int allowce) {
-  /* Scans a piece of config without executing it.
-   * Returns tokv_error, or the tokt_controlend token
-   * with type allowce if one was found.
+  /* Scans a piece of config without executing it.  Returns
+   * tokv_error (leaving the parsing state at the error),
+   * or the tokt_controlend token with type allowce if one
+   * was found (in which case rest of line, including any whitespace
+   * after tokt_controlend, has not been parsed), or tokv_eof.
    */
   int token, r;
 
@@ -417,7 +427,7 @@ static int skip(int allowce) {
       return token;
     } else if (token & tokt_controlend) {
       if (allowce == lr_controlend) return token;
-      return unexpected(token,-1,"control structure end of a different kind");
+      else return unexpected(token,-1,"control structure end of a different kind");
     } else if (token & tokt_controlstart) {
       r= token;
       while (r & tokt_controlstart) {
@@ -426,107 +436,136 @@ static int skip(int allowce) {
         r= skip(token); if (r & tokt_exception) return r;
       }
     } else if (!(token & tokt_directive) && !(token & tokt_condop)) {
-      parseerrprint("not a directive (or conditional operator) "
-                   "while looking for control structure end");
-      return tokv_error;
+      return parseerrprint("not a directive (or conditional operator) "
+                          "while looking for control structure end");
     }
     r= skiptoeol(); if (r) return r;
   }
 }
-      
-static int parser(int allowce) {
-  /* Returns:
-   *  an exception (error, eof or quit)
-   *   then rest of `file' is uninteresting
-   * or
-   *  token if allowce was !0 and equal to token's controlend
-   *   then rest of `file' not scanned yet
-   */
-  int token, r;
 
-  for (;;) { /* loop over lines */
-    cstate->reportlineno= cstate->lineno;
-    do { token= yylex(); } while (token == tokv_lwsp);
-    if (token & tokt_exception) {
-      return token;
-    } else if (token & tokt_controlend) {
-      if (lr_controlend == allowce) return token;
-      return unexpected(token,-1,"directive (not this kind of control structure end)");
-    } else if (token & tokt_directive) {
-      r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
-    } else if (token == tokv_newline) {
-      /* ignore blank links (and comment-only lines) */
-    } else {
-      return unexpected(token,-1,"directive");
-    }
-  }
-}
+/*
+ * Routines for parsing and getting the values of parameters
+ */
 
-int parse_string(const char *string, const char *descrip, int isinternal) {
-  /* Returns the same things as parser, except that tokv_eof
-   * is turned into 0. */
-  struct parser_state usestate;
-  YY_BUFFER_STATE ybuf;
-  int r;
+static void parm_1string(char ***rvalues, const char *tocopy) {
+  char **a;
+  a= xmalloc(sizeof(char*)*2);
+  a[0]= xstrsave(tocopy);
+  a[1]= 0;
+  *rvalues= a;
+}
 
-  ybuf= yy_scan_string(string);
-  if (!ybuf) syscallerror("unable to create flex buffer for internal string");
-  parser_push(&usestate,descrip,ybuf,isinternal);
-  
-  r= parser(0);
+static char *parm_ulong(unsigned long ul) {
+  char *p;
+  int l;
 
-  parser_pop();
-  if (r == tokv_eof) r= 0;
-  return r;
+  l= CHAR_BIT*sizeof(unsigned long)/3+4;
+  p= xmalloc(l);
+  snyprintf(p,l,"%lu",ul);
+  return p;
 }
 
-static int parse_file(const char *string, int *didexist) {
-  /* Returns the same things as parser, except that tokv_eof
-   * is turned into 0. */
-  static int fileparselevel= 0;
+static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) {
+  char **a;
   
-  struct parser_state usestate;
-  YY_BUFFER_STATE ybuf;
-  int r;
-  FILE *file;
+  a= xmalloc(sizeof(char*)*3);
+  a[0]= xstrsave(name);
+  a[1]= parm_ulong(id);
+  a[2]= 0;
+  *rvalues= a;
+  return 0;
+}
 
-  if (fileparselevel >= MAX_INCLUDE_NEST) {
-    parseerrprint("too many nested levels of included files");
-    return tokv_error;
-  }
-  file= fopen(string,"r"); if (!file) {
-    if (errno == ENOENT) {
-      if (didexist) *didexist= 0;
-      return 0;
-    }
-    parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
-    return tokv_error;
+static int parm_groups(char ***rvalues, int size,
+                      gid_t *gids, const char *const *groups) {
+  char **a;
+  int i;
+  
+  if (size >= 2 && gids[0] == gids[1]) { size--; gids++; groups++; }
+  a= xmalloc(sizeof(char*)*(size+1)*2);
+  for (i=0; i<size; i++) {
+    a[i]= xstrsave(groups[i]);
+    a[size+i]= parm_ulong(gids[i]);
   }
-  if (didexist) *didexist= 1;
+  a[size*2]= 0;
+  *rvalues= a;
+  return 0;
+}  
 
-  ybuf= yy_create_buffer(file,YY_BUF_SIZE);
-  if (!ybuf) syscallerror("unable to create flex buffer for file");
-  parser_push(&usestate,string,ybuf,0);
-  fileparselevel++;
-  
-  r= parser(0);
-  if (ferror(file)) {
-    parseerrprint("error reading configuration file `%s'",string);
-    r= tokv_error;
+static int pf_service(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,service); return 0;
+}
+
+static int pf_callinguser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,loginname,request_mbuf.callinguid);
+}
+
+static int pf_serviceuser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
+}
+
+static int pf_callinggroup(int ptoken, char ***rvalues) {
+  return parm_groups(rvalues,request_mbuf.ngids,calling_gids,calling_groups);
+}
+
+static int pf_servicegroup(int ptoken, char ***rvalues) {
+  return parm_groups(rvalues,service_ngids,service_gids,service_groups);
+}
+
+static int pf_callingusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,callinguser_shell); return 0;
+}
+
+static int pf_serviceusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,serviceuser_shell); return 0;
+}
+
+static int pa_parameter(char ***rvalues, char **rname) {
+  /* Scans a single parameter token and calls the appropriate parameter
+   * function, returning tokv_error (leaving the parser state wherever),
+   * or 0 on success (having just parsed the parameter name).
+   * If rname is non-null then the name of the parameter (malloc'd) will
+   * be stored in it.
+   *
+   * Caller must free *rvalues using freecharparray.
+   */
+  int token, r, i;
+  char *name;
+
+  token= yylex(); if (token == tokv_error) return token;
+  name= xstrsave(yytext);
+  if ((token & tokm_repres) != tokr_nonstring &&
+      !memcmp(yytext,"u-",2) && strlen(yytext)>=3) {
+    for (i=0;
+        i<request_mbuf.nvars && strcmp(yytext+2,defvararray[i].key);
+        i++);
+    if (i>=request_mbuf.nvars) {
+      *rvalues= xmalloc(sizeof(char*));
+      **rvalues= 0;
+    } else {
+      parm_1string(rvalues,defvararray[i].value);
+    }
+  } else if (token & tokt_parameter) {
+    r= (lr_parameter)(token,rvalues);
+    if (r) { free(name); return r; }
+  } else {
+    free(name);
+    return unexpected(token,-1,"parameter name");
   }
-  
-  fileparselevel--;
-  parser_pop();
-  fclose(file);
-  if (r == tokv_eof) r= 0;
-  return r;
+  debug_dumpparameter(name,*rvalues);
+  if (rname) *rname= name;
+  else free(name);
+  return 0;
 }
 
-/* Parameter-based conditional functions (parmcondition's) */
+/*
+ * Routines for parsing conditions, including
+ * parameter-based conditional functions (parmcondition's).
+ */
 
-int pcf_glob(int ctoken, char **pv, int *rtrue) {
+int pcf_glob(int ctoken, char *const *pv, int *rtrue) {
   int token, actrue, r;
-  char **pp;
+  char *const *pp;
 
   actrue= 0;
   r= pa_mwsp(); if (r) return r;
@@ -544,9 +583,10 @@ int pcf_glob(int ctoken, char **pv, int *rtrue) {
   return 0;
 }
 
-int pcf_range(int ctoken, char **pv, int *rtrue) {
+int pcf_range(int ctoken, char *const *pv, int *rtrue) {
   int mintoken, min, maxtoken, max, r;
-  char **pp, *ep;
+  char *const *pp;
+  char *ep;
   unsigned long v;
 
   r= pa_mwsp(); if (r) return r;
@@ -563,29 +603,30 @@ int pcf_range(int ctoken, char **pv, int *rtrue) {
   *rtrue= 0; return 0;
 }
 
-int pcf_grep(int ctoken, char **pv, int *rtrue) {
+int pcf_grep(int ctoken, char *const *pv, int *rtrue) {
   FILE *file;
   const char *cp;
-  char **pp, *buf, *p;
+  char *const *pp;
+  char *buf, *p;
   int r, maxlen, l, c, actrue, posstrue;
   
   r= paa_1path(&cp); if (r) return r;
-  file= fopen(cp,"r"); if (!file) {
-    parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno));
-    return tokv_error;
-  }
+  file= fopen(cp,"r");
+  if (!file)
+    return parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno));
   maxlen= 0;
   for (pp= pv; *pp; pp++) { l= strlen(*pp); if (l > maxlen) maxlen= l; }
   buf= xmalloc(maxlen+2); actrue= 0; c= 0;
   while (!actrue && c!=EOF) {
     c= getc(file); if (c==EOF) break;
-    if (isspace(c)) continue;
+    if (ISCHAR(isspace,c)) continue;
     l= maxlen+1; p= buf;
     while (l>0 && c!='\n' && c!=EOF) { *p++= c; l--; c= getc(file); } 
-    if (c=='\n' || c==EOF || isspace(c)) {
-      while (p>buf && isspace(p[-1])) --p;
+    if (c=='\n' || c==EOF || ISCHAR(isspace,c)) {
+      while (p>buf && ISCHAR(isspace,p[-1])) --p;
       *p= 0; posstrue= 0;
-      for (pp= pv; *pp; pp++) if (!strcmp(*pp,buf)) { posstrue= 1; break; }
+      for (pp= pv; !posstrue && *pp; pp++)
+       if (!strcmp(*pp,buf)) posstrue= 1;
     } else {
       posstrue= 0;
     }
@@ -593,7 +634,7 @@ int pcf_grep(int ctoken, char **pv, int *rtrue) {
       for (;;) {
         c= getc(file);
         if (c==EOF || c=='\n') break;
-        if (!isspace(c)) posstrue= 0;
+        if (!ISCHAR(isspace,c)) posstrue= 0;
       }
     }
     if (posstrue) actrue= 1;
@@ -603,94 +644,80 @@ int pcf_grep(int ctoken, char **pv, int *rtrue) {
     fclose(file); free(buf); return tokv_error;
   }
   assert(actrue || feof(file));
-  fclose(file); free(buf); *rtrue= actrue; return 0;
+  fclose(file); free(buf); *rtrue= actrue;
+  return 0;
 } 
 
-/* Parameter functions and associated `common code' functions */
-
-int pf_service(int ptoken, char ***rvalues) {
-  parm_1string(rvalues,service); return 0;
-}
-
-static char *parm_ulong(unsigned long ul) {
-  char *p;
-  int l;
-
-  l= CHAR_BIT*sizeof(unsigned long)/3+4;
-  p= xmalloc(l);
-  snyprintf(p,l,"%lu",ul);
-  return p;
-}
-
-static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) {
-  char **a;
-  a= xmalloc(sizeof(char*)*3);
-  a[0]= xstrsave(name);
-  a[1]= parm_ulong(id);
-  a[2]= 0;
-  *rvalues= a; return 0;
-}
-
-int pf_callinguser(int ptoken, char ***rvalues) {
-  return parm_usernameuid(rvalues,logname,request_mbuf.callinguid);
-}
-
-int pf_serviceuser(int ptoken, char ***rvalues) {
-  return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
-}
-
-static char *parm_gidname(gid_t id) {
-  static char ebuf[200];
-
-  struct group *ge;
-
-  ge= getgrgid(id);
-  if (!ge) {
-    sprintf(ebuf,"look up group with id %lu",(unsigned long)id);
-    syscallerror(ebuf);
-  }
-  return xstrsave(ge->gr_name);
-}
+static int pa_condition(int *rtrue) {
+  /* Scans up to and including the newline following the condition;
+   * may scan more than one line if the condition is a multi-line
+   * one.  Returns 0 (setting *rtrue, and parsing up to and including
+   * the last newline) or tokv_error.
+   * Expects to scan the whitespace before its condition.
+   */
+  int r, token, andor, ctrue, actrue;
+  char **parmvalues;
 
-static int parm_gids(char ***rvalues, int size, gid_t *list) {
-  char **a;
-  int i;
-  
-  if (size >= 2 && list[0] == list[1]) { size--; list++; }
-  a= xmalloc(sizeof(char*)*(size+1)*2);
-  for (i=0; i<size; i++) {
-    a[i]= parm_gidname(list[i]);
-    a[size+i]= parm_ulong(list[i]);
+  r= pa_mwsp(); if (r) return r;
+  token= yylex();
+  if (token == tokv_error) {
+    return token;
+  } else if (token == tokv_not) {
+    r= pa_condition(&ctrue); if (r) return r;
+    *rtrue= !ctrue; return 0;
+  } else if (token == tokv_openparen) {
+    andor= 0; actrue= -1;
+    for (;;) {
+      cstate->reportlineno= cstate->lineno;
+      r= pa_condition(&ctrue); if (r) return r;
+      switch (andor) {
+      case 0:         assert(actrue==-1); actrue= ctrue;           break;
+      case tokv_and:  assert(actrue>=0); actrue= actrue && ctrue;  break;
+      case tokv_or:   assert(actrue>=0); actrue= actrue || ctrue;  break;
+      default:        abort();
+      }
+      do { token= yylex(); } while (token == tokv_lwsp);
+      if (token == tokv_error) return token;
+      if (token == tokv_closeparen) break;
+      if (andor) {
+       r= unexpected(token,andor,"same conjunction as before"); if (r) return r;
+      } else {
+        if (token != tokv_and && token != tokv_or)
+          return unexpected(token,-1,"first conjunction inside connective");
+        andor= token;
+      }
+    }
+    r= pa_mnl(); if (r) return r;
+    *rtrue= actrue; return 0;
+  } else if (token & tokt_parmcondition) {
+    r= pa_mwsp(); if (r) return r;
+    r= pa_parameter(&parmvalues,0); if (r) return r;
+    r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
+    return r;
+  } else {
+    return unexpected(token,-1,"condition");
   }
-  a[size*2]= 0;
-  *rvalues= a; return 0;
-}  
-
-int pf_callinggroup(int ptoken, char ***rvalues) {
-  return parm_gids(rvalues,request_mbuf.ngids,calling_gids);
 }
 
-int pf_servicegroup(int ptoken, char ***rvalues) {
-  return parm_gids(rvalues,service_ngids,service_gids);
-}
+/*
+ * Directive functions and associated `common code' functions
+ */
 
-int pf_callingusershell(int ptoken, char ***rvalues) {
-  parm_1string(rvalues,callinguser_shell); return 0;
-}
+/* Directives specifying the service program (execute-... and reset) */
 
-int pf_serviceusershell(int ptoken, char ***rvalues) {
-  parm_1string(rvalues,serviceuser_shell); return 0;
+static void execreset(void) {
+  execute= 0;
+  execbuiltin= 0;
+  free(execpath); execpath= 0;
+  freecharparray(execargs); execargs= 0;
 }
 
-/* Directive functions and associated `common code' functions */
-
 int df_reject(int dtoken) {
   int r;
   
   r= pa_mnl(); if (r) return r;
+  execreset();
   execute= tokv_word_reject;
-  free(execpath); execpath= 0;
-  freecharparray(execargs); execargs= 0;
   return 0;
 }
 
@@ -698,48 +725,9 @@ int df_executefrompath(int dtoken) {
   int r;
   
   r= pa_mnl(); if (r) return r;
+  execreset();
   execute= tokv_word_executefrompath;
-  free(execpath); execpath= 0;
-  freecharparray(execargs); execargs= 0;
-  return 0;
-}
-
-static int paa_pathargs(const char **rv, char ***newargs_r) {
-  /* Repeated calls do _not_ overwrite newargs_r; caller must free.
-   * Repeated calls _do_ overwrite string returned in rv.
-   */
-  char **newargs;
-  int used, size, r;
-
-  used=0; size=0;
-  newargs= xmalloc(sizeof(char*)*(size+1));
-  r= pa_string(rv); if (r == tokv_error) return r;
-  for (;;) {
-    r= yylex(); if (r == tokv_error) goto error;
-    if (r==tokv_newline) break;
-    if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) {
-      r= tokv_error;
-      goto error;
-    }
-    r= yylex(); if (r==tokv_error) goto error;
-    if ((r & tokm_repres) == tokr_nonstring) {
-      r= unexpected(r,-1,"string for command argument");
-      goto error;
-    }
-    if (used>=size) {
-      size= (used+5)<<2;
-      newargs= xrealloc(newargs,sizeof(char*)*(size+1));
-    }
-    newargs[used++]= xstrsave(yytext);
-  }
-  newargs[used]= 0;
-  *newargs_r= newargs;
   return 0;
-  
-error:
-  newargs[used]=0;
-  freecharparray(newargs);
-  return r;
 }
 
 int df_execute(int dtoken) {
@@ -748,9 +736,10 @@ int df_execute(int dtoken) {
   int r;
 
   r= paa_pathargs(&rv,&newargs); if (r) return r;
+  execreset();
   execute= tokv_word_execute;
-  freecharparray(execargs); execargs= newargs;
-  free(execpath); execpath= xstrsave(rv);
+  execargs= newargs;
+  execpath= xstrsave(rv);
   return 0;
 }
 
@@ -762,7 +751,7 @@ int df_executefromdirectory(int dtoken) {
 
   r= paa_pathargs(&rv,&newargs); if (r) return r;
   p= strrchr(service,'/'); if (p) p++; else p= service;
-  if (!*p || !isalnum(*p)) {
+  if (!*p || !ISCHAR(isalnum,*p)) {
     parseerrprint("execute-from-directory requires initial char of service "
                  "portion to be alphanumeric (service portion was `%s')",
                  p);
@@ -770,7 +759,7 @@ int df_executefromdirectory(int dtoken) {
     return tokv_error;
   }
   for (q=p+1; *q; q++) {
-    if (!isalnum(*q) && *q != '-') {
+    if (!ISCHAR(isalnum,*q) && *q != '-') {
       parseerrprint("execute-from-directory requires service portion to "
                    "contain only alphanumerics and hyphens (was `%s')",
                    p);
@@ -789,72 +778,67 @@ int df_executefromdirectory(int dtoken) {
   }
   if (!S_ISREG(stab.st_mode)) {
     parseerrprint("file `%s' in execute-from-directory is not an ordinary file"
-                 " or link to one (mode=0%o)",fn,stab.st_mode);
+                 " or link to one (mode=0%lo)",fn,(unsigned long)stab.st_mode);
     free(fn); freecharparray(newargs); return tokv_error;
   }
+  execreset();
   execute= tokv_word_executefromdirectory;
-  freecharparray(execargs); execargs= newargs;
-  free(execpath); execpath= fn;
+  execargs= newargs;
+  execpath= fn;
   return 0;
 }
 
-int df_errorstostderr(int dtoken) {
-  int r;
-  
-  r= pa_mnl(); if (r) return r;
-  closeerrorfile(); eh.handling= dtoken; return 0;
-}
+/* Parsing builtin service requests (execute-builtin) */
 
-int df_errorstosyslog(int dtoken) {
-  int token, level, facility;
+static int bispa_none(char ***rnewargs) {
+  return pa_mnl();
+}
 
-  facility= DEFUSERLOGFACILITY;
-  level= DEFUSERLOGLEVEL;
-  token= yylex();
-  if (token == tokv_lwsp) {
-    token= yylex(); if (token == tokv_error) return token;
-    if (!(token & tokt_logfacility))
-      return unexpected(token,-1,"syslog facility (or end of line)");
-    facility= lr_logfacility;
-    token= yylex();
-  }    
-  if (token == tokv_lwsp) {
-    token= yylex(); if (token == tokv_error) return token;
-    if (!(token & tokt_loglevel))
-      return unexpected(token,-1,"syslog level (or end of line) after facility");
-    level= lr_loglevel;
-    token= yylex();
-  }
-  if (unexpected(token,tokv_newline,"end of line somewhere after errors-to-syslog"))
-    return tokv_error;
-  closeerrorfile(); eh.handling= tokv_word_errorstosyslog;
-  eh.logfacility= facility; eh.loglevel= level; return 0;
+static int bispa_parameter(char ***rnewargs) {
+  int r, i;
+  char **parmvalues, *name, **newargs;
+  
+  r= pa_mwsp(); if (r) return r;
+  r= pa_parameter(&parmvalues,&name); if (r) return r;
+  for (i=0; parmvalues[i]; i++);
+  newargs= xmalloc(sizeof(char*)*(i+2));
+  newargs[0]= name;
+  memcpy(newargs+1,parmvalues,sizeof(char*)*(i+1));
+  free(parmvalues);
+  r= pa_mnl(); if (r) { free(newargs); return r; }
+  *rnewargs= newargs;
+  return 0;
 }
 
-int df_errorstofile(int dtoken) {
-  const char *cp;
-  FILE *file;
+int df_executebuiltin(int dtoken) {
   int r;
-  
-  r= paa_1path(&cp); if (r) return r;
-  file= fopen(cp,"a");
-  if (!file) {
-    parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
-    return tokv_error;
-  }
-  if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) {
-    parseerrprint("unable to set line buffering on errors file: %s",strerror(errno));
-    fclose(file); return tokv_error;
-  }
-  closeerrorfile(); eh.handling= tokv_word_errorstofile;
-  eh.file= file; eh.filename= xstrsave(cp); return 0;
+  builtinserviceexec_fnt *bisexec;
+  char *newpath, **newargs;
+
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error) return r;
+  if (!(r & tokt_builtinservice)) return unexpected(r,-1,"builtin service name");
+  bisexec= lr_bisexec;
+  newpath= xstrsave(yytext);
+  newargs= 0;
+  r= lr_bispa(&newargs); if (r) { free(newpath); return r; }
+
+  execreset();
+  execute= tokv_word_executebuiltin;
+  execbuiltin= bisexec;
+  execpath= newpath;
+  execargs= newargs;
+  return 0;
 }
 
+/* Directives for changing other execution parameters */
+
 int dfg_setflag(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
-  *lr_flag= lr_flagval; return 0;
+  *lr_flag= lr_flagval;
+  return 0;
 }
 
 int df_reset(int dtoken) {
@@ -862,46 +846,123 @@ int df_reset(int dtoken) {
 
   r= pa_mnl(); if (r) return r;
   r= parse_string(RESET_CONFIGURATION,"<builtin reset configuration>",1);
-  assert(!r); return 0;  
+  return r;
 }
 
-int df_cd(int dtoken) {
-  const char *cp;
-  int r;
-
-  r= paa_1path(&cp); if (r) return r;
-  if (!chdir(cp)) return 0;
-  parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
-  return tokv_error;
-}
+int dfg_fdwant(int dtoken) {
+  int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;
 
-int df_userrcfile(int dtoken) {
-  const char *cp;
+  needreadwrite= lr_fdwant_readwrite;
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error) return r;
+  if (!(r & tokt_fdrange)) return unexpected(r,-1,"file descriptor range");
+  fdmin= lr_min; fdmax= lr_max;
+  if (fdmin<0 || fdmin>MAX_ALLOW_FD ||
+      (fdmax != -1 && fdmax<0) || fdmax>MAX_ALLOW_FD)
+    return parseerrprint("file descriptor in range is negative or far too large");
+  r= yylex(); if (r == tokv_error) return r;
+  if (r == tokv_newline) {
+    if (needreadwrite > 0)
+      return parseerrprint("read or write is required");
+    havereadwrite= 0;
+  } else if (r == tokv_lwsp) {
+    if (needreadwrite < 0)
+      return parseerrprint("read or write not allowed");
+    r= yylex(); if (r == tokv_error) return r;
+    if (!(r & tokt_readwrite))
+      return unexpected(r,-1,"read or write (or perhaps newline)");
+    havereadwrite= r;
+    r= pa_mnl(); if (r) return r;
+  } else {
+    return unexpected(r,-1,"whitespace before read or write or newline");
+  }
+  ensurefdarray(fdmin);
+  if (fdmax == -1) {
+    if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd))
+      return parseerrprint("unspecified maximum only allowed"
+                          " with reject-fd and ignore-fd");
+    fdmax= fdarrayused-1;
+    restfdwantstate= dtoken;
+    restfdwantrw= havereadwrite;
+  }
+  ensurefdarray(fdmax);
+  for (fd=fdmin; fd<=fdmax; fd++) {
+    fdarray[fd].wantstate= dtoken;
+    fdarray[fd].wantrw= havereadwrite;
+  }
+  return 0;
+}
+
+/* Directives for changing error handling */
+
+int df_errorstostderr(int dtoken) {
   int r;
+  
+  r= pa_mnl(); if (r) return r;
+  closeerrorfile(); eh.handling= dtoken;
+  return 0;
+}
 
+int df_errorstosyslog(int dtoken) {
+  int token, level, facility;
+
+  facility= DEFUSERLOGFACILITY;
+  level= DEFUSERLOGLEVEL;
+  token= yylex();
+  if (token == tokv_lwsp) {
+    token= yylex(); if (token == tokv_error) return token;
+    if (!(token & tokt_logfacility))
+      return unexpected(token,-1,"syslog facility (or end of line)");
+    facility= lr_logfacility;
+    token= yylex();
+  }    
+  if (token == tokv_lwsp) {
+    token= yylex(); if (token == tokv_error) return token;
+    if (!(token & tokt_loglevel))
+      return unexpected(token,-1,"syslog level (or end of line) after facility");
+    level= lr_loglevel;
+    token= yylex();
+  }
+  if (unexpected(token,tokv_newline,"end of line after errors-to-syslog"))
+    return tokv_error;
+  closeerrorfile(); eh.handling= tokv_word_errorstosyslog;
+  eh.logfacility= facility; eh.loglevel= level;
+  return 0;
+}
+
+int df_errorstofile(int dtoken) {
+  const char *cp;
+  FILE *file;
+  int r;
+  
   r= paa_1path(&cp); if (r) return r;
-  free(userrcfile); userrcfile= xstrsave(cp);
+  file= fopen(cp,"a");
+  if (!file)
+    return parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
+  if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) {
+    parseerrprint("unable to set line buffering on errors file: %s",strerror(errno));
+    fclose(file); return tokv_error;
+  }
+  closeerrorfile(); eh.handling= tokv_word_errorstofile;
+  eh.file= file; eh.filename= xstrsave(cp);
   return 0;
 }
 
+/* Directives for including other files or configuration data */
+
 int dfi_includeuserrcfile(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
-  if (userrcfile) return parse_file(userrcfile,0);
-  parseerrprint("_include-user-rcfile (for-internal-use directive) "
-               "found but user-rcfile not set"); return tokv_error;
+  assert(userrcfile);
+  return parse_file(userrcfile,0);
 }
 
 int dfi_includeclientconfig(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
-  if (!overridedata) {
-    parseerrprint("_include-client-config (for-internal-use directive) "
-                 "found but configuration not overridden");
-    return tokv_error;
-  }
+  assert(overridedata);
   return parse_string(overridedata,"<configuration override data>",0);
 }
 
@@ -912,52 +973,10 @@ int df_include(int dtoken) {
   r= paa_1path(&cp); if (r) return r;
   r= parse_file(cp,&found); if (r) return r;
   if (found || dtoken == tokv_word_includeifexist) return 0;
-  parseerrprint(dtoken == tokv_word_includesysconfig ?
-               "system configuration file `%s' does not exist" :
-               "included file `%s' does not exist",
-               cp);
-  return tokv_error;
-}
-
-static int paa_message(const char **message_r) {
-  static char *buildbuf;
-  static int buildbuflen;
-
-  int r, tl;
-
-  r= pa_mwsp(); if (r) return r;
-  tl= 1;
-  makeroom(&buildbuf,&buildbuflen,10);
-  buildbuf[0]= 0;
-  for (;;) {
-    r= yylex(); if (r == tokv_error) return r;
-    if (r == tokv_eof) {
-      parseerrprint("unexpected end of file in message text"); return tokv_error;
-    }
-    if (r == tokv_newline) break;
-    tl+= strlen(yytext);
-    makeroom(&buildbuf,&buildbuflen,tl);
-    strcat(buildbuf,yytext);
-  }
-  *message_r= buildbuf;
-  return 0;
-}
-
-int df_message(int dtoken) {
-  const char *mp;
-  int r;
-
-  r= paa_message(&mp); if (r) return r;
-  parseerrprint("`message' directive: %s",mp);
-  return 0;
-}
-
-int df_error(int dtoken) {
-  const char *mp;
-  int r;
-
-  r= paa_message(&mp); if (r) return r;
-  parseerrprint("`error' directive: %s",mp); return tokv_error;
+  return parseerrprint(dtoken == tokv_word_includesysconfig ?
+                      "system configuration file `%s' does not exist" :
+                      "included file `%s' does not exist",
+                      cp);
 }
 
 int df_includedirectory(int dtoken) {
@@ -967,116 +986,148 @@ int df_includedirectory(int dtoken) {
   int r, cpl, tel, c, found;
   DIR *d;
   struct dirent *de;
-  const char *p, *cp;
+  const char *p, *cpget;
+  char *cp;
   
-  r= paa_1path(&cp); if (r) return r;
-  d= opendir(cp);
-  if (!d) {
-    parseerrprint("unable to open directory `%s': %s",cp,strerror(errno));
-    return tokv_error;
-  }
+  r= paa_1path(&cpget); if (r) return r;
+  d= opendir(cpget);
+  if (!d)
+    return parseerrprint("unable to open directory `%s': %s",cpget,strerror(errno));
+  cp= xstrsave(cpget);
   cpl= strlen(cp);
   while ((de= readdir(d))) {
     tel= strlen(de->d_name);
     if (!tel) continue;
     p= de->d_name;
-    if (!isalnum(*p)) continue;
-    while ((c= *++p)) if (!(isalnum(c) || c=='-')) break;
+    if (!*p || !ISCHAR(isalnum,*p)) continue;
+    while ((c= *++p)) if (!(ISCHAR(isalnum,c) || c=='-')) break;
     if (c) continue;
-    makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1);
+    if (makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1)) {
+      stringoverflow("pathname in directory");
+      r= tokv_error; goto x_err;
+    }
     snyprintf(buildbuf,buildbuflen,"%s/%s",cp,de->d_name);
-    r= parse_file(buildbuf,&found); if (r) { closedir(d); return r; }
+    r= parse_file(buildbuf,&found); if (r) goto x_err;
     if (!found) {
-      parseerrprint("unable to open file `%s' in included directory `%s': %s",
-                   de->d_name,cp,strerror(errno));
-      closedir(d); return tokv_error;
+      r= parseerrprint("unable to open file `%s' in included directory `%s': %s",
+                      de->d_name,cp,strerror(errno));
+      goto x_err;
     }
   }
   if (closedir(d)) {
     parseerrprint("error closing directory `%s': %s",cp,strerror(errno));
+    free(cp);
     return tokv_error;
   }
+  free(cp);
   return 0;
+
+x_err:
+  closedir(d);
+  free(cp);
+  return r;
 }
 
 int df_includelookup(int dtoken) {
   static char *buildbuf=0;
   int buildbuflen=0;
   
-  char **parmvalues, **pp, *p, *q;
-  const char *cp;
+  char **parmvalues, **pp, *p, *q, *cp;
+  const char *cpget;
   struct stat stab;
   int r, done, thisdone, cpl, c;
 
   r= pa_mwsp(); if (r) return r;
-  r= pa_parameter(&parmvalues); if (r) return r;
-  r= paa_1path(&cp); if (r) { freecharparray(parmvalues); return r; }
-  if (stat(cp,&stab)) {
-    parseerrprint("unable to access directory `%s': %s",cp,strerror(errno));
+  r= pa_parameter(&parmvalues,0); if (r) return r;
+  r= paa_1path(&cpget); if (r) { freecharparray(parmvalues); return r; }
+  if (stat(cpget,&stab)) {
+    parseerrprint("unable to access directory `%s': %s",cpget,strerror(errno));
     freecharparray(parmvalues); return tokv_error;
   }
   if (!S_ISDIR(stab.st_mode)) {
-    parseerrprint("object `%s' is not a directory or link to one",cp);
+    parseerrprint("object `%s' is not a directory or link to one",cpget);
     freecharparray(parmvalues); return tokv_error;
   }
   done= 0;
+  cp= xstrsave(cpget);
   cpl= strlen(cp);
   if (!parmvalues[0]) {
-    makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP));
+    if (makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP))) {
+      stringoverflow("pathname in directory for lookup of undefined parameter");
+      r= tokv_error; goto x_err;
+    }
     snyprintf(buildbuf,buildbuflen,"%s/" NONEINCLUDELOOKUP,cp);
-    r= parse_file(buildbuf,&thisdone);
-    if (r) { freecharparray(parmvalues); return r; }
+    r= parse_file(buildbuf,&thisdone); if (r) goto x_err;
     if (thisdone) done= 1;
   } else {
     for (pp=parmvalues;
         *pp && (!done || dtoken == tokv_word_includelookupall);
         pp++) {
-      makeroom(&buildbuf,&buildbuflen,
-              cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1);
+      if (makeroom(&buildbuf,&buildbuflen,
+                  cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1)) {
+       stringoverflow("pathname in directory for lookup");
+       r= tokv_error; goto x_err;
+      }
       strcpy(buildbuf,cp);
       p= *pp; q= buildbuf+cpl;
       *q++= '/';
-      if (*p=='.') *q++= ':';
-      while ((c= *p++)) {
-       if (c=='/') { *q++= ':'; c='-'; }
-       else if (c==':') { *q++= ':'; }
-       *q++= c;
+      if (!*p) {
+       strcpy(q,EMPTYINCLUDELOOKUP);
+      } else {
+       if (*p=='.') *q++= ':';
+       while ((c= *p++)) {
+         if (c=='/') {
+           *q++= ':';
+           c= '-';
+         } else if (!((c >= '0' && c <= '9') ||
+                      (c >= 'a' && c <= 'z') ||
+                      c == '-' || c == '_')) {
+           *q++= ':';
+         }
+         *q++= c;
+       }
+       *q++= 0;
       }
-      *q++= 0;
       r= parse_file(buildbuf,&thisdone);
-      if (r) { freecharparray(parmvalues); return r; }
+      if (r) goto x_err;
       if (thisdone) done= 1;
     }
   }
-  freecharparray(parmvalues);
   if (!done) {
-    makeroom(&buildbuf,&buildbuflen,
-             cpl+1+sizeof(DEFAULTINCLUDELOOKUP));
+    if (makeroom(&buildbuf,&buildbuflen,
+                cpl+1+sizeof(DEFAULTINCLUDELOOKUP))) {
+      stringoverflow("pathname in directory for lookup of default");
+      r= tokv_error; goto x_err;
+    }
     snyprintf(buildbuf,buildbuflen,"%s/" DEFAULTINCLUDELOOKUP,cp);
-    r= parse_file(buildbuf,0); if (r) return r;
+    r= parse_file(buildbuf,0); if (r) goto x_err;
   }
-  return 0;
+  r= 0;
+  
+x_err:
+  freecharparray(parmvalues);
+  free(cp);
+  return r;
 }
 
+/* Control constructs */
+
 int df_catchquit(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
   r= parser(tokv_word_catchquit);
-  if (r & tokt_controlend) {
-    assert(r == tokv_word_hctac);
-    r= pa_mnl();
-  } else if (r == tokv_quit || r == tokv_error) {
+  if (r == tokv_quit || r == tokv_error) {
     if (r == tokv_error) {
       r= parse_string(RESET_CONFIGURATION,
                      "<builtin reset configuration (caught error)>",1);
       assert(!r);
     }
     r= skip(tokv_word_catchquit);
-    if (r & tokt_controlend) {
-      assert(r == tokv_word_hctac);
-      r= pa_mnl();
-    }
+  }
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_hctac);
+    r= pa_mnl();
   }
   return r;
 }
@@ -1089,65 +1140,75 @@ int df_if(int dtoken) {
     r= pa_condition(&true); if (r) return r;
     if (!done && true) { r= parser(tokv_word_if); done= 1; }
     else { r= skip(tokv_word_if); }
-    if (!(r & tokv_word_if)) return r;
+    if (!(r & tokt_controlend)) return r;
   } while (r == tokv_word_elif);
   if (r == tokv_word_else) {
     r= pa_mnl(); if (r) return r;
     cstate->reportlineno= cstate->lineno;
     if (done) r= skip(tokv_word_if);
     else r= parser(tokv_word_if);
-    if (!(r & tokv_word_if)) return r;
+    if (!(r & tokt_controlend)) return r;
   }
   if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error;
   return pa_mnl();
 }
 
-int dfg_fdwant(int dtoken) {
-  int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;
+int df_errorspush(int dt) {
+  struct error_handling save;
+  int r;
 
-  needreadwrite= lr_fdwant_readwrite;
-  r= pa_mwsp(); if (r) return r;
-  r= yylex(); if (r == tokv_error) return r;
-  if (!(r & tokt_fdrange)) return r;
-  fdmin= lr_min; fdmax= lr_max;
-  r= yylex(); if (r == tokv_error) return r;
-  if (r == tokv_newline) {
-    if (needreadwrite > 0) {
-      parseerrprint("read or write is required"); return tokv_error;
-    }
-    havereadwrite= 0;
-  } else if (r == tokv_lwsp) {
-    if (needreadwrite < 0) {
-      parseerrprint("read or write not allowed"); return tokv_error;
-    }
-    r= yylex(); if (r == tokv_error) return r;
-    if (!(r & tokt_readwrite))
-      return unexpected(r,-1,"read or write (or perhaps newline)");
-    havereadwrite= r;
-    r= pa_mnl(); if (r) return r;
-  } else {
-    return unexpected(r,-1,"whitespace before read or write or newline");
-  }
-  ensurefdarray(fdmin);
-  if (fdmax == -1) {
-    if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd))
-      parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd");
-    fdmax= fdarrayused-1;
-    restfdwantstate= dtoken;
-    restfdwantrw= havereadwrite;
-  }    
-  for (fd=fdmin; fd<=fdmax; fd++) {
-    fdarray[fd].wantstate= dtoken;
-    fdarray[fd].wantrw= havereadwrite;
+  r= pa_mnl(); if (r) return r;
+
+  save= eh;
+  eh.filekeep= 1;
+
+  r= parser(tokv_word_errorspush);
+
+  closeerrorfile();
+  eh= save;
+
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_srorre);
+    r= pa_mnl();
   }
+  return r;
+}
+
+/* Miscelleanous directives */
+
+int df_cd(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  if (!chdir(cp)) return 0;
+  return parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
+}
+
+int df_userrcfile(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  free(userrcfile); userrcfile= xstrsave(cp);
   return 0;
 }
 
-int df_quit(int dtoken) {
+int df_message(int dtoken) {
+  const char *mp;
   int r;
 
-  r= pa_mnl(); if (r) return r;
-  return tokv_quit;
+  r= paa_message(&mp); if (r) return r;
+  parseerrprint("`message' directive: %s",mp);
+  return 0;
+}
+
+int df_error(int dtoken) {
+  const char *mp;
+  int r;
+
+  r= paa_message(&mp); if (r) return r;
+  return parseerrprint("`error' directive: %s",mp);
 }
 
 int df_eof(int dtoken) {
@@ -1157,23 +1218,150 @@ int df_eof(int dtoken) {
   return tokv_eof;
 }
 
-int df_errorspush(int dt) {
-  struct error_handling save;
+int df_quit(int dtoken) {
   int r;
 
   r= pa_mnl(); if (r) return r;
+  return tokv_quit;
+}
 
-  save= eh;
-  eh.filekeep= 1;
+/*
+ * Main parser routines
+ */
+      
+static void parser_push(struct parser_state *usestate,
+                       const char *newfile,
+                       const struct stat *newfilestab,
+                       YY_BUFFER_STATE ybuf,
+                       int isinternal) {
+  usestate->lineno= 1;
+  usestate->reportlineno= 1;
+  usestate->filename= newfile;
+  usestate->filestab= *newfilestab;
+  usestate->notedreferer= 0;
+  usestate->isinternal= isinternal;
+  usestate->ybuf= ybuf;
+  usestate->upstate= cstate;
 
-  r= parser(tokv_word_errorspush);
+  cstate= usestate;
+  yy_switch_to_buffer(ybuf);
+}
 
-  closeerrorfile();
-  eh= save;
+static void parser_pop(void) {
+  struct parser_state *oldstate;
 
-  if (r & tokt_controlend) {
-    assert(r == tokv_word_srorre);
-    r= pa_mnl();
+  oldstate= cstate;
+  cstate= cstate->upstate;
+  if (cstate) yy_switch_to_buffer(cstate->ybuf);
+  yy_delete_buffer(oldstate->ybuf);
+}
+
+int parse_string(const char *string, const char *descrip, int isinternal) {
+  /* Returns the same things as parser, except that tokv_eof is turned
+   * into 0.  *string must be statically allocated or copied, so that
+   * it is not overwritten while the parsing takes place (unlike with
+   * parse_file).
+   */
+  static const struct stat blankstab;
+  
+  struct parser_state usestate;
+  YY_BUFFER_STATE ybuf;
+  int r;
+
+  ybuf= yy_scan_string(string);
+  if (!ybuf) syscallerror("unable to create flex buffer for internal string");
+  parser_push(&usestate,descrip,&blankstab,ybuf,isinternal);
+  
+  r= parser(0);
+
+  parser_pop();
+  if (r == tokv_eof) r= 0;
+  return r;
+}
+
+static int parse_file(const char *string, int *didexist) {
+  /* Returns the same things as parser, except that tokv_eof is turned
+   * into 0.  If *didexist is 0 then errno will have been set.
+   * *string will be copied by parse_file so it may be be overwritten
+   * during the parsing (so, for example, yytext need not be copied).
+   */
+  static int fileparselevel= 0;
+  
+  struct parser_state usestate, *checkrecurse;
+  YY_BUFFER_STATE ybuf;
+  int r;
+  FILE *file;
+  char *filename;
+  struct stat newstab;
+
+  if (fileparselevel >= MAX_INCLUDE_NEST)
+    return parseerrprint("too many nested levels of included files");
+  file= fopen(string,"r");
+  if (!file) {
+    if (errno == ENOENT) {
+      if (didexist) *didexist= 0;
+      return 0;
+    }
+    return parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
   }
+  r= fstat(fileno(file),&newstab); if (r) syscallerror("unable to fstat new file");
+  for (checkrecurse= cstate; checkrecurse; checkrecurse= checkrecurse->upstate) {
+    if (!checkrecurse->filestab.st_mode) continue;
+    if (newstab.st_dev==checkrecurse->filestab.st_dev &&
+       newstab.st_ino==checkrecurse->filestab.st_ino) {
+      fclose(file);
+      return parseerrprint("recursion detected - config file `%s' calls itself",string);
+    }
+  }
+  
+  if (didexist) *didexist= 1;
+
+  ybuf= yy_create_buffer(file,YY_BUF_SIZE);
+  if (!ybuf) syscallerror("unable to create flex buffer for file");
+  filename= xstrsave(string);
+  parser_push(&usestate,filename,&newstab,ybuf,0);
+  fileparselevel++;
+  
+  r= parser(0);
+  if (ferror(file))
+    r= parseerrprint("error reading configuration file `%s'",string);
+
+  fileparselevel--;
+  parser_pop();
+  free(filename);
+  fclose(file);
+  if (r == tokv_eof) r= 0;
   return r;
 }
+
+static int parser(int allowce) {
+  /* Returns:
+   *  an exception (error, eof or quit)
+   *   then rest of `file' is uninteresting
+   * or
+   *  token if allowce was !0 and equal to token's controlend
+   *   then rest of `file' (including rest of line with the
+   *   controlend - even the whitespace) not scanned yet
+   */
+  int token, r;
+
+  for (;;) { /* loop over lines */
+    cstate->reportlineno= cstate->lineno;
+    do { token= yylex(); } while (token == tokv_lwsp);
+    if (token & tokt_exception) {
+      return token;
+    } else if (token & tokt_controlend) {
+      if (lr_controlend == allowce) return token;
+      else return unexpected(token,-1,"directive (not this kind of"
+                            " control structure end)");
+    } else if (token & tokt_directive) {
+      if ((token & tokt_internal) && !cstate->isinternal)
+       return unexpected(token,-1,"published directive, not internal-use-only one");
+      r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
+    } else if (token == tokv_newline) {
+      /* ignore blank lines (and comment-only lines) */
+    } else {
+      return unexpected(token,-1,"directive");
+    }
+  }
+}