/*
 * userv - parser.c
 * 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., but we have to #include it so that the C
 * objects from the lexer are available.
 *
 * userv is copyright Ian Jackson and other contributors.
 * See README for full authorship information.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with userv; if not, see <http://www.gnu.org/licenses/>.
 */

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);

/*
 * Error-handling routines
 */

static void closeerrorfile(void) {
  if (eh.file && !eh.filekeep) {
    if (fclose(eh.file)) {
      eh.handling= tokv_word_errorstostderr;
      parseerrprint("error writing to error log file `%s': %s",
		    eh.filename,strerror(errno));
    }
    free(eh.filename);
  }
  eh.handling= 0;
  eh.file= 0; eh.filename= 0; eh.filekeep= 0;
}

static void senderrmsg(const char *errmsg, int useehandling) {
  char suberrmsg[MAX_ERRMSG_LEN];
  int e;
  time_t now;
  struct tm *lt;

  switch (useehandling) {
  case tokv_word_errorstostderr:
    senderrmsgstderr(errmsg);
    break;
  case tokv_word_errorstosyslog:
    ensurelogopen(eh.logfacility);
    syslog(eh.loglevel,"%s",errmsg);
    break;
  case tokv_word_errorstofile:
    if (time(&now)==-1) syscallerror("get current time");
    lt= localtime(&now); if (!lt) syscallerror("convert current time");
    if (fprintf(eh.file,"%d-%02d-%02d %02d:%02d:%02d: uservd: %s\n",
		lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday,
		lt->tm_hour, lt->tm_min, lt->tm_sec,
		errmsg) == EOF) {
      e= errno;
      closeerrorfile(); eh.handling= tokv_word_errorstofile;
      snyprintf(suberrmsg,sizeof(suberrmsg),
		"error writing to error log file `%.*s': %s;"
		" reverting to errors-to-stderr",
		(int)(sizeof(suberrmsg)>>1),eh.filename,strerror(e));
      senderrmsg(suberrmsg,eh.handling);
      senderrmsg(errmsg,eh.handling);
    }
    break;
  default:
    abort();
  }
}

static void errwhere(struct parser_state *tstate, char *bufput, int bufputlen) {
  static const char suffix[]= "references ...";
  char errmsg[MAX_ERRMSG_LEN];
  
  if (!tstate) {
    strnycpy(bufput,"<initialisation>: ",bufputlen);
    return;
  }
  if (!tstate->notedreferer && tstate->upstate && !tstate->upstate->isinternal) {
    errwhere(tstate->upstate,errmsg,sizeof(errmsg)-sizeof(suffix));
    strcat(errmsg,suffix);
    senderrmsg(errmsg,eh.handling);
    tstate->notedreferer= 1;
  }
  snyprintf(bufput,bufputlen,"%.*s:%d: ",bufputlen-10,
	    tstate->filename,tstate->reportlineno);
}

int parseerrprint(const char *fmt, ...) {
  va_list al;
  char errmsg[MAX_ERRMSG_LEN];

  va_start(al,fmt);
  errwhere(cstate,errmsg,sizeof(errmsg)>>1);
  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;

  if (!array) return;
  for (pp=array; *pp; pp++) free(*pp);
  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=='"');  p++;
  while (*p && *p != '"') {
    if (*p != '\\') { *q++= *p++; continue; }
    switch (*++p) {
    case 'n': *q++= '\n'; p++; continue;
    case 'r': *q++= '\r'; p++; continue;
    case 't': *q++= '\t'; p++; continue;
    case 'x':
      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 (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))
          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++;
	v= *p++; assert(v=='\n');
      }
    }
  }
  assert(*p); 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, l;
  
  if ((token & tokm_repres) == tokr_word) {
    assert(strlen(yytext)+sizeof(keywordfmt)<sizeof(buf));
    snyprintf(buf,sizeof(buf),keywordfmt,yytext);
    return buf;
  } else if (token & tokt_number) {
    snyprintf(buf,sizeof(buf),"number %d",lr_min); return buf;
  } else if (token & tokt_fdrange && token & tokr_word) {
    snyprintf(buf,sizeof(buf),"fd %s",buf); 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) {
    snyprintf(buf,sizeof(buf),"fdrange %d-%d",lr_min,lr_max); return buf;
  } else if ((token & tokm_repres) == tokr_punct) {
    assert(strlen(yytext)+sizeof(operatorfmt)<sizeof(buf));
    snyprintf(buf,sizeof(buf),operatorfmt,yytext);
    return buf;
  } else if (token & tokt_string) {
    switch (token) {
    case tokv_barestring: strcpy(buf,"unquoted string (bare word)"); break;
    case tokv_quotedstring: strcpy(buf,"quoted string"); break;
    default: abort();
    }
    strcat(buf," `");
    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;
  } else {
    switch (token) {
    case tokv_lwsp:    return "linear whitespace";
    case tokv_newline: return "newline (or comment followed by newline)";
    case tokv_eof:     return "end of input file";
    case tokv_error:   return "syntax error token";
    default:
      sprintf(buf,"token#0%o",token); return buf;
    }
  }
}

static const char *string2path(const char *in) {
  /* 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;
  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;
}

/*
 * 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 pa_mnl(void) {
  return unexpected(yylex(),tokv_newline,"newline");
}
static int pa_mwsp(void) {
  return unexpected(yylex(),tokv_lwsp,"linear whitespace");
}

static int pa_string(const char **rv) {
  static char *p= 0;
  static int pl= 0;

  int r, l;

  r= pa_mwsp(); if (r) return r;
  r= yylex(); if (r == tokv_error) return r;
  if ((r & tokm_repres) == tokr_nonstring) return unexpected(r,-1,"string");
  l= strlen(yytext)+1;
  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) {
  int r;

  r= pa_string(rv); if (r) return r;
  return pa_mnl();
}  

static int paa_1path(const char **rv) {
  const char *cp;
  int r;
  
  r= paa_1string(&cp); if (r) return r;
  *rv= string2path(cp); if (!*rv) return tokv_error;
  return 0;
}

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.
   */
  char **newargs;
  const char *string;
  int used, size, r;

  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));
    }
    newargs[used++]= xstrsave(yytext);
  }
  newargs[used]= 0;
  *newargs_r= newargs;
  return 0;
  
error:
  newargs[used]=0;
  freecharparray(newargs);
  return 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;
  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;
}

/*
 * 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(); }
  while (token != tokv_newline && !(token & tokt_exception));
  if (token == tokv_newline) return 0;
  if (token == tokv_error) return token;
  assert(token == tokv_eof);
  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 (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;

  for (;;) { /* loop over lines */
    cstate->reportlineno= cstate->lineno;
    do { token= yylex(); } while (token == tokv_lwsp || token == tokv_newline);
    if (token & tokt_exception) {
      return token;
    } else if (token & tokt_controlend) {
      if (allowce == lr_controlend) return token;
      else return unexpected(token,-1,"control structure end of a different kind");
    } else if (token & tokt_controlstart) {
      r= token;
      while (r & tokt_controlstart) {
        r= skiptoeol(); if (r) return r;
	cstate->reportlineno= cstate->lineno;
        r= skip(token); if (r & tokt_exception) return r;
      }
    } else if (!(token & tokt_directive) && !(token & tokt_condop)) {
      return parseerrprint("not a directive (or conditional operator) "
			   "while looking for control structure end");
    }
    r= skiptoeol(); if (r) return r;
  }
}

/*
 * Routines for parsing and getting the values of parameters
 */

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 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;
}

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]);
  }
  a[size*2]= 0;
  *rvalues= a;
  return 0;
}  

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");
  }
  debug_dumpparameter(name,*rvalues);
  if (rname) *rname= name;
  else free(name);
  return 0;
}

/*
 * Routines for parsing conditions, including
 * parameter-based conditional functions (parmcondition's).
 */

int pcf_glob(int ctoken, char *const *pv, int *rtrue) {
  int token, actrue, r;
  char *const *pp;

  actrue= 0;
  r= pa_mwsp(); if (r) return r;
  for (;;) {
    token= yylex();
    if ((token & tokm_repres) == tokr_nonstring)
      return unexpected(token,-1,"glob pattern");
    for (pp= pv; !actrue && *pp; pp++) if (!fnmatch(yytext,*pp,0)) actrue= 1;
    token= yylex(); 
    if (token == tokv_newline) break;
    if (unexpected(token,tokv_lwsp,"newline after or whitespace between glob patterns"))
      return tokv_error;
  }
  *rtrue= actrue;
  return 0;
}

int pcf_range(int ctoken, char *const *pv, int *rtrue) {
  int mintoken, min, maxtoken, max, r;
  char *const *pp;
  char *ep;
  unsigned long v;

  mintoken= pa_numberdollar(&min); if (mintoken == tokv_error) return mintoken;
  maxtoken= pa_numberdollar(&max); if (maxtoken == tokv_error) return maxtoken;
  r= pa_mnl(); if (r) return r;
  for (pp= pv; *pp; pp++) {
    v= strtoul(*pp,&ep,10); if (*ep) continue;
    if (mintoken != tokv_dollar && v < min) continue;
    if (maxtoken != tokv_dollar && v > max) continue;
    *rtrue= 1; return 0;
  }
  *rtrue= 0; return 0;
}

int pcf_grep(int ctoken, char *const *pv, int *rtrue) {
  FILE *file;
  const char *cp;
  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)
    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 (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 || ISCHAR(isspace,c)) {
      while (p>buf && ISCHAR(isspace,p[-1])) --p;
      *p= 0; posstrue= 0;
      for (pp= pv; !posstrue && *pp; pp++)
	if (!strcmp(*pp,buf)) posstrue= 1;
    } else {
      posstrue= 0;
    }
    if (c!='\n' && c!=EOF) {
      for (;;) {
        c= getc(file);
        if (c==EOF || c=='\n') break;
        if (!ISCHAR(isspace,c)) posstrue= 0;
      }
    }
    if (posstrue) actrue= 1;
  }
  if (ferror(file)) {
    parseerrprint("error while reading `%s' for grep: %s",cp,strerror(errno));
    fclose(file); free(buf); return tokv_error;
  }
  assert(actrue || feof(file));
  fclose(file); free(buf); *rtrue= actrue;
  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, 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;

  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");
  }
}

/*
 * Directive functions and associated `common code' functions
 */

/* Directives specifying the service program (execute-... and reset) */

static void execreset(void) {
  execute= 0;
  execbuiltin= 0;
  free(execpath); execpath= 0;
  freecharparray(execargs); execargs= 0;
}

int df_reject(int dtoken) {
  int r;
  
  r= pa_mnl(); if (r) return r;
  execreset();
  execute= tokv_word_reject;
  return 0;
}

int df_executefrompath(int dtoken) {
  int r;
  
  r= pa_mnl(); if (r) return r;
  execreset();
  execute= tokv_word_executefrompath;
  return 0;
}

int df_execute(int dtoken) {
  const char *rv;
  char **newargs;
  int r;

  r= paa_pathargs(&rv,&newargs); if (r) return r;
  execreset();
  execute= tokv_word_execute;
  execargs= newargs;
  execpath= xstrsave(rv);
  return 0;
}

int df_executefromdirectory(int dtoken) {
  const char *p, *q, *rv;
  struct stat stab;
  char *fn, **newargs;
  int l, r;

  r= paa_pathargs(&rv,&newargs); if (r) return r;
  p= strrchr(service,'/'); if (p) p++; else p= service;
  if (!*p || !ISCHAR(isalnum,*p)) {
    parseerrprint("execute-from-directory requires initial char of service "
		  "portion to be alphanumeric (service portion was `%s')",
		  p);
    freecharparray(newargs);
    return tokv_error;
  }
  for (q=p+1; *q; q++) {
    if (!ISCHAR(isalnum,*q) && *q != '-') {
      parseerrprint("execute-from-directory requires service portion to "
		    "contain only alphanumerics and hyphens (was `%s')",
		    p);
      freecharparray(newargs);
      return tokv_error;
    }
  }
  l= strlen(rv)+1+strlen(p)+1;
  fn= xmalloc(l);
  snyprintf(fn,l,"%s/%s",rv,p);
  if (stat(fn,&stab)) {
    if (errno == ENOENT) { free(fn); freecharparray(newargs); return 0; }
    parseerrprint("failed to stat `%s' for execute-from-directory: %s",
		  fn,strerror(errno));
    free(fn); freecharparray(newargs); return tokv_error;
  }
  if (!S_ISREG(stab.st_mode)) {
    parseerrprint("file `%s' in execute-from-directory is not an ordinary file"
		  " 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;
  execargs= newargs;
  execpath= fn;
  return 0;
}

/* Parsing builtin service requests (execute-builtin) */

static int bispa_none(char ***rnewargs) {
  return pa_mnl();
}

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_executebuiltin(int dtoken) {
  int r;
  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;
}

int df_reset(int dtoken) {
  int r;

  r= pa_mnl(); if (r) return r;
  r= parse_string(RESET_CONFIGURATION,"<builtin reset configuration>",1);
  return r;
}

int dfg_fdwant(int dtoken) {
  int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;

  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;
  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;
  assert(userrcfile);
  return parse_file(userrcfile,0);
}

int dfi_includeclientconfig(int dtoken) {
  int r;

  r= pa_mnl(); if (r) return r;
  assert(overridedata);
  return parse_string(overridedata,"<configuration override data>",0);
}

int df_include(int dtoken) {
  const char *cp;
  int r, found;

  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;
  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) {
  static char *buildbuf=0;
  static int buildbuflen=0;
  
  int r, cpl, tel, c, found;
  DIR *d;
  struct dirent *de;
  const char *p, *cpget;
  char *cp;
  
  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 ((errno=0, de= readdir(d))) {
    tel= strlen(de->d_name);
    if (!tel) continue;
    p= de->d_name;
    if (!*p || !ISCHAR(isalnum,*p)) continue;
    while ((c= *++p)) if (!(ISCHAR(isalnum,c) || c=='-')) break;
    if (c) continue;
    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) goto x_err;
    if (!found) {
      r= parseerrprint("unable to open file `%s' in included directory `%s': %s",
		       de->d_name,cp,strerror(errno));
      goto x_err;
    }
  }
  if (errno) {
    parseerrprint("error reading directory `%s': %s",cp,strerror(errno));
    closedir(d);
    free(cp);
    return tokv_error;
  }
  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;
}

static int oldquote = 0;

int dfg_lookupquotemode(int dtoken) {
  int r;
  r= pa_mnl(); if (r) return r;
  oldquote = dtoken == tokv_word_includelookupquoteold;
  return r;
}

int df_includelookup(int dtoken) {
  static char *buildbuf=0;
  int buildbuflen=0;
  
  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,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",cpget);
    freecharparray(parmvalues); return tokv_error;
  }
  done= 0;
  cp= xstrsave(cpget);
  cpl= strlen(cp);
  if (!parmvalues[0]) {
    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) goto x_err;
    if (thisdone) done= 1;
  } else {
    for (pp=parmvalues;
	 *pp && (!done || dtoken == tokv_word_includelookupall);
	 pp++) {
      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) {
	strcpy(q,EMPTYINCLUDELOOKUP);
      } else {
	if (*p=='.') *q++= ':';
	while ((c= *p++)) {
	  if (c=='/') {
	    *q++= ':';
	    c= '-';
	  } else if (oldquote ?
		     !((c >= '0' && c <= '9') ||
		       (c >= 'a' && c <= 'z') ||
		       c == '-' || c == '_') :
		     (c==':')) {
	    *q++= ':';
	  }
	  *q++= c;
	}
	*q++= 0;
      }
      r= parse_file(buildbuf,&thisdone);
      if (r) goto x_err;
      if (thisdone) done= 1;
    }
  }
  if (!done) {
    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) goto x_err;
  }
  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 == 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();
  }
  return r;
}

int df_if(int dtoken) {
  int r, true, done;
  
  done= 0;
  do {
    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 & 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 & tokt_controlend)) return r;
  }
  if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error;
  return pa_mnl();
}

int df_errorspush(int dt) {
  struct error_handling save;
  int r;

  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_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;
  return parseerrprint("`error' directive: %s",mp);
}

int df_eof(int dtoken) {
  int r;

  r= pa_mnl(); if (r) return r;
  return tokv_eof;
}

int df_quit(int dtoken) {
  int r;

  r= pa_mnl(); if (r) return r;
  return tokv_quit;
}

/*
 * 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;

  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);
}

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");
    }
  }
}
