/*      $NetBSD: opts.c,v 1.1.1.3 2015/01/17 16:34:15 christos Exp $    */

/*
* Copyright (c) 1997-2014 Erez Zadok
* Copyright (c) 1989 Jan-Simon Pendry
* Copyright (c) 1989 Imperial College of Science, Technology & Medicine
* Copyright (c) 1989 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Jan-Simon Pendry at Imperial College, London.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* File: am-utils/amd/opts.c
*
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <am_defs.h>
#include <amd.h>

/*
* MACROS:
*/
#define NLEN    16      /* Length of longest option name (conservative) */
#define S(x) (x) , (sizeof(x)-1)
/*
* The BUFSPACE macros checks that there is enough space
* left in the expansion buffer.  If there isn't then we
* give up completely.  This is done to avoid crashing the
* automounter itself (which would be a bad thing to do).
*/
#define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN)

/*
* TYPEDEFS:
*/
typedef int (*IntFuncPtr) (char *);
typedef struct opt_apply opt_apply;
enum vs_opt { SelEQ, SelNE, VarAss };

/*
* STRUCTURES
*/
struct opt {
 char *name;                   /* Name of the option */
 int nlen;                     /* Length of option name */
 char **optp;                  /* Pointer to option value string */
 char **sel_p;                 /* Pointer to selector value string */
 int (*fxn_p)(char *);         /* Pointer to boolean function */
 int case_insensitive;         /* How to do selector comparisons */
};

struct opt_apply {
 char **opt;
 char *val;
};

struct functable {
 char *name;
 IntFuncPtr func;
};

/*
* FORWARD DEFINITION:
*/
static int f_in_network(char *);
static int f_xhost(char *);
static int f_netgrp(char *);
static int f_netgrpd(char *);
static int f_exists(char *);
static int f_false(char *);
static int f_true(char *);
static inline char *expand_options(char *key);

/*
* STATICS:
*/
static char NullStr[] = "<NULL>";
static char nullstr[] = "";
static char *opt_dkey = NullStr;
static char *opt_host = nullstr; /* XXX: was the global hostname */
static char *opt_hostd = hostd;
static char *opt_key = nullstr;
static char *opt_keyd = nullstr;
static char *opt_map = nullstr;
static char *opt_path = nullstr;
char uid_str[SIZEOF_UID_STR], gid_str[SIZEOF_GID_STR];
char *opt_uid = uid_str;
char *opt_gid = gid_str;
static char *vars[8];
static char *literal_dollar = "$"; /* ${dollar}: a literal '$' in maps */

/*
* GLOBALS
*/
static struct am_opts fs_static;      /* copy of the options to play with */


/*
* Options in some order corresponding to frequency of use so that
* first-match algorithm is sped up.
*/
static struct opt opt_fields[] = {
 /* Name and length.
       Option str.             Selector str.   boolean fxn.    case sensitive */
 { S("opts"),
      &fs_static.opt_opts,     0,              0,              FALSE   },
 { S("host"),
       0,                      &opt_host,      0,              TRUE    },
 { S("hostd"),
       0,                      &opt_hostd,     0,              TRUE    },
 { S("type"),
       &fs_static.opt_type,    0,              0,              FALSE   },
 { S("rhost"),
       &fs_static.opt_rhost,   0,              0,              TRUE    },
 { S("rfs"),
       &fs_static.opt_rfs,     0,              0,              FALSE   },
 { S("fs"),
       &fs_static.opt_fs,      0,              0,              FALSE   },
 { S("key"),
       0,                      &opt_key,       0,              FALSE   },
 { S("map"),
       0,                      &opt_map,       0,              FALSE   },
 { S("sublink"),
       &fs_static.opt_sublink, 0,              0,              FALSE   },
 { S("arch"),
       0,                      &gopt.arch,     0,              TRUE    },
 { S("dev"),
       &fs_static.opt_dev,     0,              0,              FALSE   },
 { S("pref"),
       &fs_static.opt_pref,    0,              0,              FALSE   },
 { S("path"),
       0,                      &opt_path,      0,              FALSE   },
 { S("autodir"),
       0,                      &gopt.auto_dir, 0,              FALSE   },
 { S("delay"),
       &fs_static.opt_delay,   0,              0,              FALSE   },
 { S("domain"),
       0,                      &hostdomain,    0,              TRUE    },
 { S("karch"),
       0,                      &gopt.karch,    0,              TRUE    },
 { S("cluster"),
       0,                      &gopt.cluster,  0,              TRUE    },
 { S("wire"),
       0,                      0,              f_in_network,   TRUE    },
 { S("network"),
       0,                      0,              f_in_network,   TRUE    },
 { S("netnumber"),
       0,                      0,              f_in_network,   TRUE    },
 { S("byte"),
       0,                      &endian,        0,              TRUE    },
 { S("os"),
       0,                      &gopt.op_sys,   0,              TRUE    },
 { S("osver"),
       0,                      &gopt.op_sys_ver,       0,      TRUE    },
 { S("full_os"),
       0,                      &gopt.op_sys_full,      0,      TRUE    },
 { S("vendor"),
       0,                      &gopt.op_sys_vendor,    0,      TRUE    },
 { S("remopts"),
       &fs_static.opt_remopts, 0,              0,              FALSE   },
 { S("mount"),
       &fs_static.opt_mount,   0,              0,              FALSE   },
 { S("unmount"),
       &fs_static.opt_unmount, 0,              0,              FALSE   },
 { S("umount"),
       &fs_static.opt_umount,  0,              0,              FALSE   },
 { S("cache"),
       &fs_static.opt_cache,   0,              0,              FALSE   },
 { S("user"),
       &fs_static.opt_user,    0,              0,              FALSE   },
 { S("group"),
       &fs_static.opt_group,   0,              0,              FALSE   },
 { S(".key"),
       0,                      &opt_dkey,      0,              FALSE   },
 { S("key."),
       0,                      &opt_keyd,      0,              FALSE   },
 { S("maptype"),
       &fs_static.opt_maptype, 0,              0,              FALSE   },
 { S("cachedir"),
       &fs_static.opt_cachedir, 0,             0,              FALSE   },
 { S("addopts"),
       &fs_static.opt_addopts, 0,              0,              FALSE   },
 { S("uid"),
       0,                      &opt_uid,       0,              FALSE   },
 { S("gid"),
       0,                      &opt_gid,       0,              FALSE   },
 { S("mount_type"),
       &fs_static.opt_mount_type, 0,           0,              FALSE   },
 { S("dollar"),
       &literal_dollar,        0,              0,              FALSE   },
 { S("var0"),
       &vars[0],               0,              0,              FALSE   },
 { S("var1"),
       &vars[1],               0,              0,              FALSE   },
 { S("var2"),
       &vars[2],               0,              0,              FALSE   },
 { S("var3"),
       &vars[3],               0,              0,              FALSE   },
 { S("var4"),
       &vars[4],               0,              0,              FALSE   },
 { S("var5"),
       &vars[5],               0,              0,              FALSE   },
 { S("var6"),
       &vars[6],               0,              0,              FALSE   },
 { S("var7"),
       &vars[7],               0,              0,              FALSE   },
 { 0, 0, 0, 0, 0, FALSE },
};

static struct functable functable[] = {
 { "in_network",       f_in_network },
 { "xhost",            f_xhost },
 { "netgrp",           f_netgrp },
 { "netgrpd",          f_netgrpd },
 { "exists",           f_exists },
 { "false",            f_false },
 { "true",             f_true },
 { 0, 0 },
};

/*
* Specially expand the remote host name first
*/
static opt_apply rhost_expansion[] =
{
 {&fs_static.opt_rhost, "${host}"},
 {0, 0},
};

/*
* List of options which need to be expanded
* Note that the order here _may_ be important.
*/
static opt_apply expansions[] =
{
 {&fs_static.opt_sublink, 0},
 {&fs_static.opt_rfs, "${path}"},
 {&fs_static.opt_fs, "${autodir}/${rhost}${rfs}"},
 {&fs_static.opt_opts, "rw"},
 {&fs_static.opt_remopts, "${opts}"},
 {&fs_static.opt_mount, 0},
 {&fs_static.opt_unmount, 0},
 {&fs_static.opt_umount, 0},
 {&fs_static.opt_cachedir, 0},
 {&fs_static.opt_addopts, 0},
 {0, 0},
};

/*
* List of options which need to be free'ed before re-use
*/
static opt_apply to_free[] =
{
 {&fs_static.fs_glob, 0},
 {&fs_static.fs_local, 0},
 {&fs_static.fs_mtab, 0},
 {&fs_static.opt_sublink, 0},
 {&fs_static.opt_rfs, 0},
 {&fs_static.opt_fs, 0},
 {&fs_static.opt_rhost, 0},
 {&fs_static.opt_opts, 0},
 {&fs_static.opt_remopts, 0},
 {&fs_static.opt_mount, 0},
 {&fs_static.opt_unmount, 0},
 {&fs_static.opt_umount, 0},
 {&fs_static.opt_cachedir, 0},
 {&fs_static.opt_addopts, 0},
 {&vars[0], 0},
 {&vars[1], 0},
 {&vars[2], 0},
 {&vars[3], 0},
 {&vars[4], 0},
 {&vars[5], 0},
 {&vars[6], 0},
 {&vars[7], 0},
 {0, 0},
};


/*
* expand backslash escape sequences
* (escaped slash is handled separately in normalize_slash)
*/
static char
backslash(char **p)
{
 char c;

 if ((*p)[1] == '\0') {
   plog(XLOG_USER, "Empty backslash escape");
   return **p;
 }

 if (**p == '\\') {
   (*p)++;
   switch (**p) {
   case 'g':
     c = '\007';               /* Bell */
     break;
   case 'b':
     c = '\010';               /* Backspace */
     break;
   case 't':
     c = '\011';               /* Horizontal Tab */
     break;
   case 'n':
     c = '\012';               /* New Line */
     break;
   case 'v':
     c = '\013';               /* Vertical Tab */
     break;
   case 'f':
     c = '\014';               /* Form Feed */
     break;
   case 'r':
     c = '\015';               /* Carriage Return */
     break;
   case 'e':
     c = '\033';               /* Escape */
     break;
   case '0':
   case '1':
   case '2':
   case '3':
   case '4':
   case '5':
   case '6':
   case '7':
     {
       int cnt, val, ch;

       for (cnt = 0, val = 0; cnt < 3; cnt++) {
         ch = *(*p)++;
         if (ch < '0' || ch > '7') {
           (*p)--;
           break;
         }
         val = (val << 3) | (ch - '0');
       }

       if ((val & 0xffffff00) != 0)
         plog(XLOG_USER,
              "Too large character constant %u\n",
              val);
       c = (char) val;
       --(*p);
     }
     break;

   default:
     c = **p;
     break;
   }
 } else
   c = **p;

 return c;
}


/*
* Skip to next option in the string
*/
static char *
opt(char **p)
{
 char *cp = *p;
 char *dp = cp;
 char *s = cp;

top:
 while (*cp && *cp != ';') {
   if (*cp == '"') {
     /*
      * Skip past string
      */
     for (cp++; *cp && *cp != '"'; cp++)
       if (*cp == '\\')
         *dp++ = backslash(&cp);
       else
         *dp++ = *cp;
     if (*cp)
       cp++;
   } else {
     *dp++ = *cp++;
   }
 }

 /*
  * Skip past any remaining ';'s
  */
 while (*cp == ';')
   cp++;

 /*
  * If we have a zero length string
  * and there are more fields, then
  * parse the next one.  This allows
  * sequences of empty fields.
  */
 if (*cp && dp == s)
   goto top;

 *dp = '\0';

 *p = cp;
 return s;
}


/*
* These routines add a new style of selector; function-style boolean
* operators.  To add new ones, just define functions as in true, false,
* exists (below) and add them to the functable, above.
*
* Usage example: Some people have X11R5 local, some go to a server. I do
* this:
*
*    *       exists(/usr/pkg/${key});type:=link;fs:=/usr/pkg/${key} || \
*            -type:=nfs;rfs=/usr/pkg/${key} \
*            rhost:=server1 \
*            rhost:=server2
*
* -Rens Troost <[email protected]>
*/
static IntFuncPtr
functable_lookup(char *key)
{
 struct functable *fp;

 for (fp = functable; fp->name; fp++)
   if (FSTREQ(fp->name, key))
       return (fp->func);
 return (IntFuncPtr) NULL;
}


/*
* Fill in the global structure fs_static by
* cracking the string opts.  opts may be
* scribbled on at will.  Does NOT evaluate options.
* Returns 0 on error, 1 if no syntax errors were discovered.
*/
static int
split_opts(char *opts, char *mapkey)
{
 char *o = opts;
 char *f;

 /*
  * For each user-specified option
  */
 for (f = opt(&o); *f; f = opt(&o)) {
   struct opt *op;
   char *eq = strchr(f, '=');
   char *opt = NULL;

   if (!eq)
     continue;

   if (*(eq-1) == '!' ||
       eq[1] == '=' ||
       eq[1] == '!') { /* != or == or =! */
     continue;                 /* we don't care about selectors */
   }

   if (*(eq-1) == ':') {       /* := */
     *(eq-1) = '\0';
   } else {
     /* old style assignment */
     eq[0] = '\0';
   }
   opt = eq + 1;

   /*
    * For each recognized option
    */
   for (op = opt_fields; op->name; op++) {
     /*
      * Check whether they match
      */
     if (FSTREQ(op->name, f)) {
       if (op->sel_p) {
         plog(XLOG_USER, "key %s: Can't assign to a selector (%s)",
              mapkey, op->name);
         return 0;
       }
       *op->optp = opt;        /* actual assignment into fs_static */
       break;                  /* break out of for loop */
     } /* end of "if (FSTREQ(op->name, f))" statement  */
   } /* end of "for (op = opt_fields..." statement  */

   if (!op->name)
     plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f);
 }

 return 1;
}


/*
* Just evaluate selectors, which were split by split_opts.
* Returns 0 on error or no match, 1 if matched.
*/
static int
eval_selectors(char *opts, char *mapkey)
{
 char *o, *old_o;
 char *f;
 int ret = 0;

 o = old_o = xstrdup(opts);

 /*
  * For each user-specified option
  */
 for (f = opt(&o); *f; f = opt(&o)) {
   struct opt *op;
   enum vs_opt vs_opt;
   char *eq = strchr(f, '=');
   char *fx;
   IntFuncPtr func;
   char *opt = NULL;
   char *arg;

   if (!eq) {
     /*
      * No value, is it a function call?
      */
     arg = strchr(f, '(');

     if (!arg || arg[1] == '\0' || arg == f) {
       /*
        * No, just continue
        */
       plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
       continue;
     }

     /* null-terminate the argument  */
     *arg++ = '\0';
     fx = strchr(arg, ')');
     if (fx == NULL || fx == arg) {
       plog(XLOG_USER, "key %s: Malformed function in \"%s\"", mapkey, f);
       continue;
     }
     *fx = '\0';

     if (f[0] == '!') {
       vs_opt = SelNE;
       f++;
     } else {
       vs_opt = SelEQ;
     }
     /*
      * look up f in functable and pass it arg.
      * func must return 0 on failure, and 1 on success.
      */
     if ((func = functable_lookup(f))) {
       int funok;

       /* this allocates memory, don't forget to free */
       arg = expand_options(arg);
       funok = func(arg);
       XFREE(arg);

       if (vs_opt == SelNE)
         funok = !funok;
       if (!funok)
         goto out;

       continue;
     } else {
       plog(XLOG_USER, "key %s: unknown function \"%s\"", mapkey, f);
       goto out;
     }
   } else {
     if (eq[1] == '\0' || eq == f) {
#ifdef notdef
       /* We allow empty assignments */
       plog(XLOG_USER, "key %s: Bad selector \"%s\"", mapkey, f);
#endif
       continue;
     }
   }

   /*
    * Check what type of operation is happening
    * !=, =!  is SelNE
    * == is SelEQ
    * =, := is VarAss
    */
   if (*(eq-1) == '!') {       /* != */
     vs_opt = SelNE;
     *(eq-1) = '\0';
     opt = eq + 1;
   } else if (*(eq-1) == ':') {        /* := */
     continue;
   } else if (eq[1] == '=') {  /* == */
     vs_opt = SelEQ;
     eq[0] = '\0';
     opt = eq + 2;
   } else if (eq[1] == '!') {  /* =! */
     vs_opt = SelNE;
     eq[0] = '\0';
     opt = eq + 2;
   } else {
     /* old style assignment */
     continue;
   }

   /*
    * For each recognized option
    */
   for (op = opt_fields; op->name; op++) {
     /*
      * Check whether they match
      */
     if (FSTREQ(op->name, f)) {
       opt = expand_options(opt);

       if (op->sel_p != NULL) {
         int selok;
         if (op->case_insensitive) {
           selok = STRCEQ(*op->sel_p, opt);
         } else {
           selok = STREQ(*op->sel_p, opt);
         }
         if (vs_opt == SelNE)
           selok = !selok;
         if (!selok) {
           plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
                mapkey,
                op->name,
                *op->sel_p,
                vs_opt == SelNE ? "mis" : "",
                opt);
           XFREE(opt);
           goto out;
         }
         XFREE(opt);
       }
       /* check if to apply a function */
       if (op->fxn_p) {
         int funok;

         funok = op->fxn_p(opt);
         if (vs_opt == SelNE)
           funok = !funok;
         if (!funok) {
           plog(XLOG_MAP, "key %s: map function %s did not %smatch %s",
                mapkey,
                op->name,
                vs_opt == SelNE ? "mis" : "",
                opt);
           XFREE(opt);
           goto out;
         }
         XFREE(opt);
       }
       break;                  /* break out of for loop */
     }
   }

   if (!op->name)
     plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f);
 }

 /* all is ok */
 ret = 1;

out:
 free(old_o);
 return ret;
}


/*
* Skip to next option in the string, but don't scribble over the string.
* However, *p gets repointed to the start of the next string past ';'.
*/
static char *
opt_no_scribble(char **p)
{
 char *cp = *p;
 char *dp = cp;
 char *s = cp;

top:
 while (*cp && *cp != ';') {
   if (*cp == '\"') {
     /*
      * Skip past string
      */
     cp++;
     while (*cp && *cp != '\"')
       *dp++ = *cp++;
     if (*cp)
       cp++;
   } else {
     *dp++ = *cp++;
   }
 }

 /*
  * Skip past any remaining ';'s
  */
 while (*cp == ';')
   cp++;

 /*
  * If we have a zero length string
  * and there are more fields, then
  * parse the next one.  This allows
  * sequences of empty fields.
  */
 if (*cp && dp == s)
   goto top;

 *p = cp;
 return s;
}


/*
* Strip any selectors from a string.  Selectors are all assumed to be
* first in the string.  This is used for the new /defaults method which will
* use selectors as well.
*/
char *
strip_selectors(char *opts, char *mapkey)
{
 /*
  * Fill in the global structure fs_static by
  * cracking the string opts.  opts may be
  * scribbled on at will.
  */
 char *o = opts;
 char *oo = opts;
 char *f;

 /*
  * Scan options.  Note that the opt() function scribbles on the opt string.
  */
 while (*(f = opt_no_scribble(&o))) {
   enum vs_opt vs_opt = VarAss;
   char *eq = strchr(f, '=');

   if (!eq || eq[1] == '\0' || eq == f) {
     /*
      * No option or assignment?  Return as is.
      */
     plog(XLOG_USER, "key %s: No option or assignment in \"%s\"", mapkey, f);
     return o;
   }
   /*
    * Check what type of operation is happening
    * !=, =!  is SelNE
    * == is SelEQ
    * := is VarAss
    */
   if (*(eq-1) == '!') {       /* != */
     vs_opt = SelNE;
   } else if (*(eq-1) == ':') {        /* := */
     vs_opt = VarAss;
   } else if (eq[1] == '=') {  /* == */
     vs_opt = SelEQ;
   } else if (eq[1] == '!') {  /* =! */
     vs_opt = SelNE;
   }
   switch (vs_opt) {
   case SelEQ:
   case SelNE:
     /* Skip this selector, maybe there's another one following it */
     plog(XLOG_USER, "skipping selector to \"%s\"", o);
     /* store previous match. it may have been the first assignment */
     oo = o;
     break;

   case VarAss:
     /* found the first assignment, return the string starting with it */
     dlog("found first assignment past selectors \"%s\"", o);
     return oo;
   }
 }

 /* return the same string by default. should not happen. */
 return oo;
}


/*****************************************************************************
*** BOOLEAN FUNCTIONS (return 0 if false, 1 if true):                     ***
*****************************************************************************/

/* test if arg is any of this host's network names or numbers */
static int
f_in_network(char *arg)
{
 int status;

 if (!arg)
   return 0;

 status = is_network_member(arg);
 dlog("%s is %son a local network", arg, (status ? "" : "not "));
 return status;
}


/*
* Test if arg is any of this host's names or aliases (CNAMES).
* Note: this function compares against the fully expanded host name (hostd).
* XXX: maybe we also need to compare against the stripped host name?
*/
static int
f_xhost(char *arg)
{
 struct hostent *hp;
 char **cp;

 if (!arg)
   return 0;

 /* simple test: does it match main host name? */
 if (STREQ(arg, opt_hostd))
   return 1;

 /* now find all of the names of "arg" and compare against opt_hostd */
 hp = gethostbyname(arg);
 if (hp == NULL) {
#ifdef HAVE_HSTRERROR
   plog(XLOG_ERROR, "gethostbyname xhost(%s): %s", arg, hstrerror(h_errno));
#else /* not HAVE_HSTRERROR */
   plog(XLOG_ERROR, "gethostbyname xhost(%s): h_errno %d", arg, h_errno);
#endif /* not HAVE_HSTRERROR */
   return 0;
 }
 /* check primary name */
 if (hp->h_name) {
   dlog("xhost: compare %s==%s", hp->h_name, opt_hostd);
   if (STREQ(hp->h_name, opt_hostd)) {
     plog(XLOG_INFO, "xhost(%s): matched h_name %s", arg, hp->h_name);
     return 1;
   }
 }
 /* check all aliases, if any */
 if (hp->h_aliases == NULL) {
   dlog("gethostbyname(%s) has no aliases", arg);
   return 0;
 }
 cp = hp->h_aliases;
 while (*cp) {
   dlog("xhost: compare alias %s==%s", *cp, opt_hostd);
   if (STREQ(*cp, opt_hostd)) {
     plog(XLOG_INFO, "xhost(%s): matched alias %s", arg, *cp);
     return 1;
   }
   cp++;
 }
 /* nothing matched */
 return 0;
}


/* test if this host (short hostname form) is in netgroup (arg) */
static int
f_netgrp(char *arg)
{
 int status;
 char *ptr, *nhost;

 if ((ptr = strchr(arg, ',')) != NULL) {
   *ptr = '\0';
   nhost = ptr + 1;
 } else {
   nhost = opt_host;
 }
 status = innetgr(arg, nhost, NULL, NULL);
 dlog("netgrp = %s status = %d host = %s", arg, status, nhost);
 if (ptr)
   *ptr = ',';
 return status;
}


/* test if this host (fully-qualified name) is in netgroup (arg) */
static int
f_netgrpd(char *arg)
{
 int status;
 char *ptr, *nhost;

 if ((ptr = strchr(arg, ',')) != NULL) {
   *ptr = '\0';
   nhost = ptr + 1;
 } else {
   nhost = opt_hostd;
 }
 status = innetgr(arg, nhost, NULL, NULL);
 dlog("netgrp = %s status = %d hostd = %s", arg, status, nhost);
 if (ptr)
   *ptr = ',';
 return status;
}


/* test if file (arg) exists via lstat */
static int
f_exists(char *arg)
{
 struct stat buf;

 if (lstat(arg, &buf) < 0)
   return (0);
 else
   return (1);
}


/* always false */
static int
f_false(char *arg)
{
 return (0);
}


/* always true */
static int
f_true(char *arg)
{
 return (1);
}


/*
* Free an option
*/
static void
free_op(opt_apply *p, int b)
{
 XFREE(*p->opt);
}


/*
* Normalize slashes in the string.
*/
void
normalize_slash(char *p)
{
 char *f, *f0;

 if (!(gopt.flags & CFM_NORMALIZE_SLASHES))
   return;

 f0 = f = strchr(p, '/');
 if (f) {
   char *t = f;
   do {
     /* assert(*f == '/'); */
     if (f == f0 && f[0] == '/' && f[1] == '/') {
       /* copy double slash iff first */
       *t++ = *f++;
       *t++ = *f++;
     } else {
       /* copy a single / across */
       *t++ = *f++;
     }

     /* assert(f[-1] == '/'); */
     /* skip past more /'s */
     while (*f == '/')
       f++;

     /* assert(*f != '/'); */
     /* keep copying up to next / */
     while (*f && *f != '/') {
       /* support escaped slashes '\/' */
       if (f[0] == '\\' && f[1] == '/')
         f++;                  /* skip backslash */
       *t++ = *f++;
     }

     /* assert(*f == 0 || *f == '/'); */

   } while (*f);
   *t = '\0';                  /* derived from fix by Steven Glassman */
 }
}


/*
* Macro-expand an option.  Note that this does not
* handle recursive expansions.  They will go badly wrong.
* If sel_p is true then old expand selectors, otherwise
* don't expand selectors.
*/
static char *
expand_op(char *opt, int sel_p)
{
#define EXPAND_ERROR "No space to expand \"%s\""
 char expbuf[MAXPATHLEN + 1];
 char nbuf[NLEN + 1];
 char *ep = expbuf;
 char *cp = opt;
 char *dp;
 struct opt *op;
 char *cp_orig = opt;

 while ((dp = strchr(cp, '$'))) {
   char ch;
   /*
    * First copy up to the $
    */
   {
     int len = dp - cp;

     if (len > 0) {
       if (BUFSPACE(ep, len)) {
         /*
          * We use strncpy (not xstrlcpy) because 'ep' relies on its
          * semantics.  BUFSPACE guarantees that ep can hold len.
          */
         strncpy(ep, cp, len);
         ep += len;
       } else {
         plog(XLOG_ERROR, EXPAND_ERROR, opt);
         goto out;
       }
     }
   }

   cp = dp + 1;
   ch = *cp++;
   if (ch == '$') {
     if (BUFSPACE(ep, 1)) {
       *ep++ = '$';
     } else {
       plog(XLOG_ERROR, EXPAND_ERROR, opt);
       goto out;
     }
   } else if (ch == '{') {
     /* Expansion... */
     enum {
       E_All, E_Dir, E_File, E_Domain, E_Host
     } todo;
     /*
      * Find closing brace
      */
     char *br_p = strchr(cp, '}');
     int len;

     /*
      * Check we found it
      */
     if (!br_p) {
       /*
        * Just give up
        */
       plog(XLOG_USER, "No closing '}' in \"%s\"", opt);
       goto out;
     }
     len = br_p - cp;

     /*
      * Figure out which part of the variable to grab.
      */
     if (*cp == '/') {
       /*
        * Just take the last component
        */
       todo = E_File;
       cp++;
       --len;
     } else if (*(br_p-1) == '/') {
       /*
        * Take all but the last component
        */
       todo = E_Dir;
       --len;
     } else if (*cp == '.') {
       /*
        * Take domain name
        */
       todo = E_Domain;
       cp++;
       --len;
     } else if (*(br_p-1) == '.') {
       /*
        * Take host name
        */
       todo = E_Host;
       --len;
     } else {
       /*
        * Take the whole lot
        */
       todo = E_All;
     }

     /*
      * Truncate if too long.  Since it won't
      * match anyway it doesn't matter that
      * it has been cut short.
      */
     if (len > NLEN)
       len = NLEN;

     /*
      * Put the string into another buffer so
      * we can do comparisons.
      *
      * We use strncpy here (not xstrlcpy) because the dest is meant
      * to be truncated and we don't want to log it as an error.  The
      * use of the BUFSPACE macro above guarantees the safe use of
      * strncpy with nbuf.
      */
     strncpy(nbuf, cp, len);
     nbuf[len] = '\0';

     /*
      * Advance cp
      */
     cp = br_p + 1;

     /*
      * Search the option array
      */
     for (op = opt_fields; op->name; op++) {
       /*
        * Check for match
        */
       if (len == op->nlen && STREQ(op->name, nbuf)) {
         char xbuf[NLEN + 3];
         char *val;
         /*
          * Found expansion.  Copy
          * the correct value field.
          */
         if (!(!op->sel_p == !sel_p)) {
           /*
            * Copy the string across unexpanded
            */
           xsnprintf(xbuf, sizeof(xbuf), "${%s%s%s}",
                     todo == E_File ? "/" :
                     todo == E_Domain ? "." : "",
                     nbuf,
                     todo == E_Dir ? "/" :
                     todo == E_Host ? "." : "");
           val = xbuf;
           /*
            * Make sure expansion doesn't
            * munge the value!
            */
           todo = E_All;
         } else if (op->sel_p) {
           val = *op->sel_p;
         } else {
           val = *op->optp;
         }

         if (val) {
           /*
            * Do expansion:
            * ${/var} means take just the last part
            * ${var/} means take all but the last part
            * ${.var} means take all but first part
            * ${var.} means take just the first part
            * ${var} means take the whole lot
            */
           int vlen = strlen(val);
           char *vptr = val;
           switch (todo) {
           case E_Dir:
             vptr = strrchr(val, '/');
             if (vptr)
               vlen = vptr - val;
             vptr = val;
             break;
           case E_File:
             vptr = strrchr(val, '/');
             if (vptr) {
               vptr++;
               vlen = strlen(vptr);
             } else
               vptr = val;
             break;
           case E_Domain:
             vptr = strchr(val, '.');
             if (vptr) {
               vptr++;
               vlen = strlen(vptr);
             } else {
               vptr = "";
               vlen = 0;
             }
             break;
           case E_Host:
             vptr = strchr(val, '.');
             if (vptr)
               vlen = vptr - val;
             vptr = val;
             break;
           case E_All:
             break;
           }

           if (BUFSPACE(ep, vlen+1)) {
             /*
              * Don't call xstrlcpy() to truncate a string here.  It causes
              * spurious xstrlcpy() syslog() errors.  Use memcpy() and
              * explicitly terminate the string.
              */
             memcpy(ep, vptr, vlen+1);
             ep += vlen;
             *ep = '\0';
           } else {
             plog(XLOG_ERROR, EXPAND_ERROR, opt);
             goto out;
           }
         }
         /*
          * Done with this variable
          */
         break;
       }
     }

     /*
      * Check that the search was successful
      */
     if (!op->name) {
       /*
        * If it wasn't then scan the
        * environment for that name
        * and use any value found
        */
       char *env = getenv(nbuf);

       if (env) {
         int vlen = strlen(env);

         if (BUFSPACE(ep, vlen+1)) {
           xstrlcpy(ep, env, vlen+1);
           ep += vlen;
         } else {
           plog(XLOG_ERROR, EXPAND_ERROR, opt);
           goto out;
         }
         if (amuDebug(D_STR))
           plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
       } else {
         plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
       }
     }
   } else {
     /*
      * Error, error
      */
     plog(XLOG_USER, "Unknown $ sequence in \"%s\"", opt);
   }
 }

out:
 /*
  * Handle common case - no expansion
  */
 if (cp == opt) {
   opt = xstrdup(cp);
 } else {
   /*
    * Finish off the expansion
    */
   int vlen = strlen(cp);
   if (BUFSPACE(ep, vlen+1)) {
     xstrlcpy(ep, cp, vlen+1);
     /* ep += vlen; */
   } else {
     plog(XLOG_ERROR, EXPAND_ERROR, opt);
   }

   /*
    * Save the expansion
    */
   opt = xstrdup(expbuf);
 }

 normalize_slash(opt);

 if (amuDebug(D_STR)) {
   plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
   plog(XLOG_DEBUG, "......... is \"%s\"", opt);
 }
 return opt;
}


/*
* Wrapper for expand_op
*/
static void
expand_opts(opt_apply *p, int sel_p)
{
 if (*p->opt) {
   *p->opt = expand_op(*p->opt, sel_p);
 } else if (p->val) {
   /*
    * Do double expansion, remembering
    * to free the string from the first
    * expansion...
    */
   char *s = expand_op(p->val, TRUE);
   *p->opt = expand_op(s, sel_p);
   XFREE(s);
 }
}


/*
* Apply a function to a list of options
*/
static void
apply_opts(void (*op) (opt_apply *, int), opt_apply ppp[], int b)
{
 opt_apply *pp;

 for (pp = ppp; pp->opt; pp++)
   (*op) (pp, b);
}


/*
* Free the option table
*/
void
free_opts(am_opts *fo)
{
 /*
  * Copy in the structure we are playing with
  */
 fs_static = *fo;

 /*
  * Free previously allocated memory
  */
 apply_opts(free_op, to_free, FALSE);
}

am_opts *
copy_opts(am_opts *old)
{
 am_opts *newopts;
 newopts = CALLOC(struct am_opts);

#define _AM_OPT_COPY(field) do { \
   if (old->field) \
     newopts->field = xstrdup(old->field); \
 } while (0)

 _AM_OPT_COPY(fs_glob);
 _AM_OPT_COPY(fs_local);
 _AM_OPT_COPY(fs_mtab);
 _AM_OPT_COPY(opt_dev);
 _AM_OPT_COPY(opt_delay);
 _AM_OPT_COPY(opt_dir);
 _AM_OPT_COPY(opt_fs);
 _AM_OPT_COPY(opt_group);
 _AM_OPT_COPY(opt_mount);
 _AM_OPT_COPY(opt_opts);
 _AM_OPT_COPY(opt_remopts);
 _AM_OPT_COPY(opt_pref);
 _AM_OPT_COPY(opt_cache);
 _AM_OPT_COPY(opt_rfs);
 _AM_OPT_COPY(opt_rhost);
 _AM_OPT_COPY(opt_sublink);
 _AM_OPT_COPY(opt_type);
 _AM_OPT_COPY(opt_mount_type);
 _AM_OPT_COPY(opt_unmount);
 _AM_OPT_COPY(opt_umount);
 _AM_OPT_COPY(opt_user);
 _AM_OPT_COPY(opt_maptype);
 _AM_OPT_COPY(opt_cachedir);
 _AM_OPT_COPY(opt_addopts);

 return newopts;
}


/*
* Expand selectors (variables that cannot be assigned to or overridden)
*/
char *
expand_selectors(char *key)
{
 return expand_op(key, TRUE);
}


/*
* Expand options (i.e. non-selectors, see above for definition)
*/
static inline char *
expand_options(char *key)
{
 return expand_op(key, FALSE);
}


/*
* Remove trailing /'s from a string
* unless the string is a single / (Steven Glassman)
* or unless it is two slashes // (Kevin D. Bond)
* or unless amd.conf says not to touch slashes.
*/
void
deslashify(char *s)
{
 if (!(gopt.flags & CFM_NORMALIZE_SLASHES))
   return;

 if (s && *s) {
   char *sl = s + strlen(s);

   while (*--sl == '/' && sl > s)
     *sl = '\0';
 }
}


int
eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, char *key, char *map)
{
 int ok = TRUE;

 free_opts(fo);

 /*
  * Clear out the option table
  */
 memset((voidp) &fs_static, 0, sizeof(fs_static));
 memset((voidp) vars, 0, sizeof(vars));
 memset((voidp) fo, 0, sizeof(*fo));

 /* set hostname */
 opt_host = (char *) am_get_hostname();

 /*
  * Set key, map & path before expansion
  */
 opt_key = key;
 opt_map = map;
 opt_path = path;

 opt_dkey = strchr(key, '.');
 if (!opt_dkey) {
   opt_dkey = NullStr;
   opt_keyd = key;
 } else {
   opt_keyd = strnsave(key, opt_dkey - key);
   opt_dkey++;
   if (*opt_dkey == '\0')      /* check for 'host.' */
     opt_dkey = NullStr;
 }

 /*
  * Expand global options
  */
 fs_static.fs_glob = expand_selectors(g_opts);

 /*
  * Expand local options
  */
 fs_static.fs_local = expand_selectors(opts);

 /* break global options into fs_static fields */
 if ((ok = split_opts(fs_static.fs_glob, key))) {
   dlog("global split_opts ok");
   /*
    * evaluate local selectors
    */
   if ((ok = eval_selectors(fs_static.fs_local, key))) {
     dlog("local eval_selectors ok");
     /* if the local selectors matched, then do the local overrides */
     ok = split_opts(fs_static.fs_local, key);
     if (ok)
       dlog("local split_opts ok");
   }
 }

 /*
  * Normalize remote host name.
  * 1.  Expand variables
  * 2.  Normalize relative to host tables
  * 3.  Strip local domains from the remote host
  *     name before using it in other expansions.
  *     This makes mount point names and other things
  *     much shorter, while allowing cross domain
  *     sharing of mount maps.
  */
 apply_opts(expand_opts, rhost_expansion, FALSE);
 if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
   host_normalize(&fs_static.opt_rhost);

 /*
  * Macro expand the options.
  * Do this regardless of whether we are accepting
  * this mount - otherwise nasty things happen
  * with memory allocation.
  */
 apply_opts(expand_opts, expansions, FALSE);

 /*
  * Strip trailing slashes from local pathname...
  */
 deslashify(fs_static.opt_fs);

 /*
  * ok... copy the data back out.
  */
 *fo = fs_static;

 /*
  * Clear defined options
  */
 if (opt_keyd != key && opt_keyd != nullstr)
   XFREE(opt_keyd);
 opt_keyd = nullstr;
 opt_dkey = NullStr;
 opt_key = opt_map = opt_path = nullstr;

 return ok;
}