blob: cdda2e1a89fe8fc81e5986f1e22498736dbac638 [file] [log] [blame]
/* ----------------------------------------------------------------------- *
*
* parse_subs.c - misc parser subroutines
* automounter map
*
* Copyright 1997 Transmeta Corporation - All Rights Reserved
* Copyright 2000 Jeremy Fitzhardinge <jeremy@goop.org>
* Copyright 2004-2006 Ian Kent <raven@themaw.net>
*
* This program 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, Inc., 675 Mass Ave, Cambridge MA 02139,
* USA; either version 2 of the License, or (at your option) any later
* version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <libgen.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "automount.h"
#define MAX_OPTIONS_LEN 256
#define MAX_OPTION_LEN 40
#define MAX_NETWORK_LEN 255
#define MAX_IFC_BUF 2048
static int volatile ifc_buf_len = MAX_IFC_BUF;
static int volatile ifc_last_len = 0;
#define MASK_A 0x7F000000
#define MASK_B 0xBFFF0000
#define MASK_C 0xDFFFFF00
/* Get numeric value of the n bits starting at position p */
#define getbits(x, p, n) ((x >> (p + 1 - n)) & ~(~0 << n))
#define EXPAND_LEADING_SLASH 0x0001
#define EXPAND_TRAILING_SLASH 0x0002
#define EXPAND_LEADING_DOT 0x0004
#define EXPAND_TRAILING_DOT 0x0008
#define SELECTOR_HASH_SIZE 20
static struct sel sel_table[] = {
{ SEL_ARCH, "arch", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_KARCH, "karch", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_OS, "os", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_OSVER, "osver", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_FULL_OS, "full_os", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_VENDOR, "vendor", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_HOST, "host", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_HOSTD, "hostd", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_XHOST, "xhost", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_DOMAIN, "domain", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_BYTE, "byte", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_CLUSTER, "cluster", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_NETGRP, "netgrp", SEL_FLAG_FUNC2|SEL_FLAG_BOOL, NULL },
{ SEL_NETGRPD, "netgrpd", SEL_FLAG_FUNC2|SEL_FLAG_BOOL, NULL },
{ SEL_IN_NETWORK, "in_network", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_IN_NETWORK, "netnumber", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_IN_NETWORK, "network", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_IN_NETWORK, "wire", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_UID, "uid", SEL_FLAG_MACRO|SEL_FLAG_NUM, NULL },
{ SEL_GID, "gid", SEL_FLAG_MACRO|SEL_FLAG_NUM, NULL },
{ SEL_KEY, "key", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_MAP, "map", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_PATH, "path", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_EXISTS, "exists", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_AUTODIR, "autodir", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_DOLLAR, "dollar", SEL_FLAG_MACRO|SEL_FLAG_STR, NULL },
{ SEL_TRUE, "true", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
{ SEL_FALSE, "false", SEL_FLAG_FUNC1|SEL_FLAG_BOOL, NULL },
};
static unsigned int sel_count = sizeof(sel_table)/sizeof(struct sel);
static struct sel *sel_hash[SELECTOR_HASH_SIZE];
static unsigned int sel_hash_init_done = 0;
static pthread_mutex_t sel_hash_mutex = PTHREAD_MUTEX_INITIALIZER;
struct types {
char *type;
unsigned int len;
};
static struct types map_type[] = {
{ "file", 4 },
{ "program", 7 },
{ "yp", 2 },
{ "nis", 3 },
{ "nisplus", 7 },
{ "ldap", 4 },
{ "ldaps", 5 },
{ "hesiod", 6 },
{ "userdir", 7 },
{ "hosts", 5 },
};
static unsigned int map_type_count = sizeof(map_type)/sizeof(struct types);
static struct types format_type[] = {
{ "sun", 3 },
{ "hesiod", 6 },
{ "amd", 3},
};
static unsigned int format_type_count = sizeof(format_type)/sizeof(struct types);
static void sel_add(struct sel *sel)
{
u_int32_t hval = hash(sel->name, SELECTOR_HASH_SIZE);
struct sel *old;
old = sel_hash[hval];
sel_hash[hval] = sel;
sel_hash[hval]->next = old;
}
void sel_hash_init(void)
{
int i;
pthread_mutex_lock(&sel_hash_mutex);
if (sel_hash_init_done) {
pthread_mutex_unlock(&sel_hash_mutex);
return;
}
for (i = 0; i < SELECTOR_HASH_SIZE; i++)
sel_hash[i] = NULL;
for (i = 0; i < sel_count; i++)
sel_add(&sel_table[i]);
sel_hash_init_done = 1;
pthread_mutex_unlock(&sel_hash_mutex);
}
struct sel *sel_lookup(const char *name)
{
u_int32_t hval = hash(name, SELECTOR_HASH_SIZE);
struct sel *sel;
pthread_mutex_lock(&sel_hash_mutex);
for (sel = sel_hash[hval]; sel != NULL; sel = sel->next) {
if (strcmp(name, sel->name) == 0) {
pthread_mutex_unlock(&sel_hash_mutex);
return sel;
}
}
pthread_mutex_unlock(&sel_hash_mutex);
return NULL;
}
struct selector *get_selector(char *name)
{
struct sel *sel;
sel = sel_lookup(name);
if (sel) {
struct selector *new = malloc(sizeof(struct selector));
if (!new)
return NULL;
memset(new, 0, sizeof(*new));
new->sel = sel;
return new;
}
return NULL;
}
void free_selector(struct selector *selector)
{
struct selector *s = selector;
struct selector *next = s;
while (s) {
next = s->next;
if (s->sel->flags & SEL_FREE_VALUE_MASK)
free(s->comp.value);
if (s->sel->flags & SEL_FREE_ARG1_MASK)
free(s->func.arg1);
if (s->sel->flags & SEL_FREE_ARG2_MASK)
free(s->func.arg2);
s = next;
}
free(selector);
return;
}
static unsigned int ipv6_mask_cmp(uint32_t *host, uint32_t *iface, uint32_t *mask)
{
unsigned int ret = 1;
unsigned int i;
for (i = 0; i < 4; i++) {
if ((host[i] & mask[i]) != (iface[i] & mask[i])) {
ret = 0;
break;
}
}
return ret;
}
unsigned int get_proximity(struct sockaddr *host_addr)
{
struct ifaddrs *ifa = NULL;
struct ifaddrs *this;
struct sockaddr_in *addr, *msk_addr, *if_addr;
struct sockaddr_in6 *addr6, *msk6_addr, *if6_addr;
struct in_addr *hst_addr;
struct in6_addr *hst6_addr;
int addr_len;
char buf[MAX_ERR_BUF];
uint32_t mask, ha, ia, *mask6, *ha6, *ia6;
int ret;
addr = NULL;
addr6 = NULL;
hst_addr = NULL;
hst6_addr = NULL;
mask6 = NULL;
ha6 = NULL;
ia6 = NULL;
ha = 0;
switch (host_addr->sa_family) {
case AF_INET:
addr = (struct sockaddr_in *) host_addr;
hst_addr = (struct in_addr *) &addr->sin_addr;
ha = ntohl((uint32_t) hst_addr->s_addr);
addr_len = sizeof(*hst_addr);
break;
case AF_INET6:
#ifndef WITH_LIBTIRPC
return PROXIMITY_UNSUPPORTED;
#else
addr6 = (struct sockaddr_in6 *) host_addr;
hst6_addr = (struct in6_addr *) &addr6->sin6_addr;
ha6 = &hst6_addr->s6_addr32[0];
addr_len = sizeof(*hst6_addr);
break;
#endif
default:
return PROXIMITY_ERROR;
}
ret = getifaddrs(&ifa);
if (ret) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
logerr("getifaddrs: %s", estr);
return PROXIMITY_ERROR;
}
this = ifa;
while (this) {
if (!(this->ifa_flags & IFF_UP) ||
this->ifa_flags & IFF_POINTOPOINT ||
this->ifa_addr == NULL) {
this = this->ifa_next;
continue;
}
switch (this->ifa_addr->sa_family) {
case AF_INET:
if (host_addr->sa_family == AF_INET6)
break;
if_addr = (struct sockaddr_in *) this->ifa_addr;
ret = memcmp(&if_addr->sin_addr, hst_addr, addr_len);
if (!ret) {
freeifaddrs(ifa);
return PROXIMITY_LOCAL;
}
break;
case AF_INET6:
#ifdef WITH_LIBTIRPC
if (host_addr->sa_family == AF_INET)
break;
if6_addr = (struct sockaddr_in6 *) this->ifa_addr;
ret = memcmp(&if6_addr->sin6_addr, hst6_addr, addr_len);
if (!ret) {
freeifaddrs(ifa);
return PROXIMITY_LOCAL;
}
#endif
default:
break;
}
this = this->ifa_next;
}
this = ifa;
while (this) {
if (!(this->ifa_flags & IFF_UP) ||
this->ifa_flags & IFF_POINTOPOINT ||
this->ifa_addr == NULL) {
this = this->ifa_next;
continue;
}
switch (this->ifa_addr->sa_family) {
case AF_INET:
if (host_addr->sa_family == AF_INET6)
break;
if_addr = (struct sockaddr_in *) this->ifa_addr;
ia = ntohl((uint32_t) if_addr->sin_addr.s_addr);
/* Is the address within a localy attached subnet */
msk_addr = (struct sockaddr_in *) this->ifa_netmask;
mask = ntohl((uint32_t) msk_addr->sin_addr.s_addr);
if ((ia & mask) == (ha & mask)) {
freeifaddrs(ifa);
return PROXIMITY_SUBNET;
}
/*
* Is the address within a local ipv4 network.
*
* Bit position 31 == 0 => class A.
* Bit position 30 == 0 => class B.
* Bit position 29 == 0 => class C.
*/
if (!getbits(ia, 31, 1))
mask = MASK_A;
else if (!getbits(ia, 30, 1))
mask = MASK_B;
else if (!getbits(ia, 29, 1))
mask = MASK_C;
else
break;
if ((ia & mask) == (ha & mask)) {
freeifaddrs(ifa);
return PROXIMITY_NET;
}
break;
case AF_INET6:
#ifdef WITH_LIBTIRPC
if (host_addr->sa_family == AF_INET)
break;
if6_addr = (struct sockaddr_in6 *) this->ifa_addr;
ia6 = &if6_addr->sin6_addr.s6_addr32[0];
/* Is the address within the network of the interface */
msk6_addr = (struct sockaddr_in6 *) this->ifa_netmask;
mask6 = &msk6_addr->sin6_addr.s6_addr32[0];
if (ipv6_mask_cmp(ha6, ia6, mask6)) {
freeifaddrs(ifa);
return PROXIMITY_SUBNET;
}
/* How do we define "local network" in ipv6? */
#endif
default:
break;
}
this = this->ifa_next;
}
freeifaddrs(ifa);
return PROXIMITY_OTHER;
}
static char *inet_fill_net(const char *net_num, char *net)
{
char *np;
int dots = 3;
if (strlen(net_num) > INET_ADDRSTRLEN)
return NULL;
if (!isdigit(*net_num))
return NULL;
*net = '\0';
strcpy(net, net_num);
np = net;
while (*np++) {
if (*np == '.') {
np++;
dots--;
if (!*np && dots)
strcat(net, "0");
continue;
}
if ((*np && !isdigit(*np)) || dots < 0) {
*net = '\0';
return NULL;
}
}
while (dots--)
strcat(net, ".0");
return net;
}
static char *get_network_number(const char *network)
{
struct netent *netent;
char cnet[MAX_NETWORK_LEN];
uint32_t h_net;
size_t len;
len = strlen(network) + 1;
if (len > MAX_NETWORK_LEN)
return NULL;
netent = getnetbyname(network);
if (!netent)
return NULL;
h_net = ntohl(netent->n_net);
if (!inet_ntop(AF_INET, &h_net, cnet, INET_ADDRSTRLEN))
return NULL;
return strdup(cnet);
}
unsigned int get_network_proximity(const char *name)
{
struct addrinfo hints;
struct addrinfo *ni, *this;
char name_or_num[NI_MAXHOST + 1];
unsigned int proximity;
char *net;
int ret;
if (!name)
return PROXIMITY_ERROR;
net = get_network_number(name);
if (net) {
strcpy(name_or_num, net);
free(net);
} else {
char this[NI_MAXHOST + 1];
char *mask;
if (strlen(name) > NI_MAXHOST)
return PROXIMITY_ERROR;
strcpy(this, name);
if ((mask = strchr(this, '/')))
*mask++ = '\0';
if (!strchr(this, '.'))
strcpy(name_or_num, this);
else {
char buf[NI_MAXHOST + 1], *new;
new = inet_fill_net(this, buf);
if (!new)
return PROXIMITY_ERROR;
strcpy(name_or_num, new);
}
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_CANONNAME;
ret = getaddrinfo(name_or_num, NULL, &hints, &ni);
if (ret) {
logerr("hostname lookup for %s failed: %s",
name_or_num, gai_strerror(ret));
return PROXIMITY_ERROR;
}
proximity = PROXIMITY_OTHER;
this = ni;
while (this) {
unsigned int prx = get_proximity(this->ai_addr);
if (prx < proximity)
proximity = prx;
this = this->ai_next;
}
freeaddrinfo(ni);
return proximity;
}
unsigned int in_network(char *network)
{
unsigned int proximity = get_network_proximity(network);
if (proximity == PROXIMITY_ERROR ||
proximity > PROXIMITY_SUBNET)
return 0;
return 1;
}
struct mapent *match_cached_key(struct autofs_point *ap,
const char *err_prefix,
struct map_source *source,
const char *key)
{
char buf[MAX_ERR_BUF];
struct mapent_cache *mc;
struct mapent *me;
mc = source->mc;
if (!(source->flags & MAP_FLAG_FORMAT_AMD)) {
int ret;
me = cache_lookup(mc, key);
/*
* Stale mapent => check for entry in alternate source or
* wildcard. Note, plus included direct mount map entries
* are included as an instance (same map entry cache), not
* in a distinct source.
*/
if (me && (!me->mapent ||
(me->source != source && *me->key != '/'))) {
while ((me = cache_lookup_key_next(me)))
if (me->source == source)
break;
if (!me)
me = cache_lookup_distinct(mc, "*");
}
if (!me)
goto done;
/*
* If this is a lookup add wildcard match for later validation
* checks and negative cache lookups.
*/
if (!(ap->flags & MOUNT_FLAG_REMOUNT) &&
ap->type == LKP_INDIRECT && *me->key == '*') {
ret = cache_update(mc, source, key, me->mapent, me->age);
if (!(ret & (CHE_OK | CHE_UPDATED)))
me = NULL;
}
} else {
char *lkp_key = strdup(key);
if (!lkp_key) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt, "%s strdup: %s", err_prefix, estr);
return NULL;
}
/* If it's found we're done */
me = cache_lookup_distinct(mc, lkp_key);
if (me)
goto free;
/*
* Otherwise strip successive directory components and try
* a match against map entries ending with a wildcard and
* finally try the wilcard entry itself.
*/
while (!me) {
char *prefix;
while ((prefix = strrchr(lkp_key, '/'))) {
*prefix = '\0';
me = cache_partial_match_wild(mc, lkp_key);
if (me)
goto free;
}
me = cache_lookup_distinct(mc, "*");
if (me)
goto free;
break;
}
free:
free(lkp_key);
}
done:
return me;
}
/*
* Skip whitespace in a string; if we hit a #, consider the rest of the
* entry a comment.
*/
const char *skipspace(const char *whence)
{
while (1) {
switch (*whence) {
case ' ':
case '\b':
case '\t':
case '\n':
case '\v':
case '\f':
case '\r':
whence++;
break;
case '#': /* comment: skip to end of string */
while (*whence != '\0')
whence++;
/* FALLTHROUGH */
default:
return whence;
}
}
}
/*
* Check a string to see if a colon appears before the next '/'.
*/
int check_colon(const char *str)
{
char *ptr = (char *) str;
/* Colon escape */
if (!strncmp(ptr, ":/", 2))
return 1;
while (*ptr && strncmp(ptr, ":/", 2))
ptr++;
if (!*ptr)
return 0;
return 1;
}
/* Get the length of a chunk delimitered by whitespace */
int chunklen(const char *whence, int expect_colon)
{
char *str = (char *) whence;
int n = 0;
int quote = 0;
for (; *str; str++, n++) {
switch (*str) {
case '\\':
if( quote ) {
break;
} else {
quote = 1;
continue;
}
case '"':
if (quote)
break;
while (*str) {
str++;
n++;
if (*str == '"')
break;
if (!strncmp(str, ":/", 2))
expect_colon = 0;
}
break;
case ':':
if (expect_colon && !strncmp(str, ":/", 2))
expect_colon = 0;
continue;
case ' ':
case '\t':
/* Skip space or tab if we expect a colon */
if (expect_colon)
continue;
case '\b':
case '\n':
case '\v':
case '\f':
case '\r':
case '\0':
if (!quote)
return n;
/* FALLTHROUGH */
default:
break;
}
quote = 0;
}
return n;
}
/*
* Compare str with pat. Return 0 if compare equal or
* str is an abbreviation of pat of no less than mchr characters.
*/
int strmcmp(const char *str, const char *pat, int mchr)
{
int nchr = 0;
while (*str == *pat) {
if (!*str)
return 0;
str++;
pat++;
nchr++;
}
if (!*str && nchr > mchr)
return 0;
return *pat - *str;
}
char *dequote(const char *str, int origlen, unsigned int logopt)
{
char *ret = malloc(origlen + 1);
char *cp = ret;
const char *scp;
int len = origlen;
int quote = 0, dquote = 0;
int i, j;
if (ret == NULL)
return NULL;
/* first thing to do is strip white space from the end */
i = len - 1;
while (isspace(str[i])) {
/* of course, we have to keep escaped white-space */
j = i - 1;
if (j > 0 && (str[j] == '\\' || str[j] == '"'))
break;
i--;
len--;
}
for (scp = str; len > 0 && *scp; scp++, len--) {
if (!quote) {
if (*scp == '"') {
if (dquote)
dquote = 0;
else
dquote = 1;
continue;
}
if (!dquote) {
if (*scp == '\\') {
quote = 1;
continue;
}
}
}
quote = 0;
*cp++ = *scp;
}
*cp = '\0';
if (dquote) {
debug(logopt, "unmatched quote in %.*s", origlen, str);
free(ret);
return NULL;
}
return ret;
}
int span_space(const char *str, unsigned int maxlen)
{
const char *p = str;
unsigned int len = 0;
while (*p && !isblank(*p) && len < maxlen) {
if (*p == '"') {
while (*p++ && len++ < maxlen) {
if (*p == '"')
break;
}
} else if (*p == '\\') {
p += 2;
len += 2;
continue;
}
p++;
len++;
}
return len;
}
char *sanitize_path(const char *path, int origlen, unsigned int type, unsigned int logopt)
{
char *slash, *cp, *s_path;
const char *scp;
int len = origlen;
unsigned int seen_slash = 0, quote = 0, dquote = 0;
if (type & (LKP_INDIRECT | LKP_DIRECT)) {
const char *tmp = path;
if (*tmp == '"')
tmp++;
slash = strchr(tmp, '/');
if (slash) {
if (type == LKP_INDIRECT)
return NULL;
if (*tmp != '/')
return NULL;
} else {
if (type == LKP_DIRECT)
return NULL;
}
}
s_path = malloc(origlen + 1);
if (!s_path)
return NULL;
for (cp = s_path, scp = path; len > 0; scp++, len--) {
if (!quote) {
if (*scp == '"') {
if (dquote)
dquote = 0;
else
dquote = 1;
continue;
}
if (!dquote) {
/* Badness in string - go away */
if (*scp < 32) {
free(s_path);
return NULL;
}
if (*scp == '\\') {
quote = 1;
continue;
}
}
/*
* Not really proper but we get problems with
* paths with multiple slashes. The kernel
* compresses them so when we get a query there
* should be only single slashes.
*/
if (*scp == '/') {
if (seen_slash)
continue;
seen_slash = 1;
} else
seen_slash = 0;
}
quote = 0;
*cp++ = *scp;
}
*cp = '\0';
if (dquote) {
debug(logopt, "unmatched quote in %.*s", origlen, path);
free(s_path);
return NULL;
}
/* Remove trailing / but watch out for a quoted / alone */
if (strlen(cp) > 1 && origlen > 1 && *(cp - 1) == '/')
*(cp - 1) = '\0';
return s_path;
}
static char *hasopt(const char *str, const char *opt)
{
const size_t optlen = strlen(opt);
char *rest = (char *) str, *p;
while ((p = strstr(rest, opt)) != NULL) {
if ((p == rest || p[-1] == ',') &&
(p[optlen] == '\0' || p[optlen] == '=' ||
p[optlen] == ','))
return p;
rest = strchr (p, ',');
if (rest == NULL)
break;
++rest;
}
return NULL;
}
char *merge_options(const char *opt1, const char *opt2)
{
char str[MAX_OPTIONS_LEN + 1];
char result[MAX_OPTIONS_LEN + 1];
char neg[MAX_OPTION_LEN + 1];
char *tok, *ptr = NULL;
size_t resultlen, len;
if ((!opt1 || !*opt1) && (!opt2 || !*opt2))
return NULL;
if (!opt2 || !*opt2) {
if (!*opt1)
return NULL;
return strdup(opt1);
}
if (!opt1 || !*opt1) {
if (!*opt2)
return NULL;
return strdup(opt2);
}
if (!strcmp(opt1, opt2))
return strdup(opt1);
if (strlen(str) > MAX_OPTIONS_LEN)
return NULL;
memset(result, 0, sizeof(result));
strcpy(str, opt1);
resultlen = 0;
tok = strtok_r(str, ",", &ptr);
while (tok) {
const char *this = (const char *) tok;
char *eq = strchr(this, '=');
if (eq) {
*eq = '\0';
if (!hasopt(opt2, this)) {
if (resultlen + strlen(this) > MAX_OPTIONS_LEN)
return NULL;
*eq = '=';
if (!*result)
strcpy(result, this);
else
strcat(result, this);
strcat(result, ",");
resultlen += strlen(this) + 1;
goto next;
}
}
if (!strcmp(this, "rw") && hasopt(opt2, "ro"))
goto next;
if (!strcmp(this, "ro") && hasopt(opt2, "rw"))
goto next;
if (!strcmp(this, "bg") && hasopt(opt2, "fg"))
goto next;
if (!strcmp(this, "fg") && hasopt(opt2, "bg"))
goto next;
if (!strcmp(this, "bg") && hasopt(opt2, "fg"))
goto next;
if (!strcmp(this, "soft") && hasopt(opt2, "hard"))
goto next;
if (!strcmp(this, "hard") && hasopt(opt2, "soft"))
goto next;
if (!strncmp(this, "no", 2)) {
if (strlen(this + 2) > MAX_OPTION_LEN)
return NULL;
strcpy(neg, this + 2);
if (hasopt(opt2, neg))
goto next;
} else {
if ((strlen(this) + 2) > MAX_OPTION_LEN)
return NULL;
strcpy(neg, "no");
strcat(neg, this);
if (hasopt(opt2, neg))
goto next;
}
if (hasopt(opt2, tok))
goto next;
if (resultlen + strlen(this) + 1 > MAX_OPTIONS_LEN)
return NULL;
if (!*result)
strcpy(result, this);
else
strcat(result, this);
strcat(result, ",");
resultlen =+ strlen(this) + 1;
next:
tok = strtok_r(NULL, ",", &ptr);
}
if (resultlen + strlen(opt2) > MAX_OPTIONS_LEN)
return NULL;
if (!*result)
strcpy(result, opt2);
else
strcat(result, opt2);
len = strlen(result);
if (len && result[len - 1] == ',')
result[len - 1] = '\0';
return strdup(result);
}
static char *expand_slash_or_dot(char *str, unsigned int type)
{
char *val = NULL;
if (!str)
return NULL;
if (!type)
return str;
if (type & EXPAND_LEADING_SLASH)
val = basename(str);
else if (type & EXPAND_TRAILING_SLASH)
val = dirname(str);
else if (type & (EXPAND_LEADING_DOT | EXPAND_TRAILING_DOT)) {
char *dot = strchr(str, '.');
if (dot)
*dot++ = '\0';
if (type & EXPAND_LEADING_DOT)
val = dot;
else
val = str;
}
return val;
}
/*
* $-expand an amd-style map entry and return the length of the entry.
* If "dst" is NULL, just count the length.
*/
int expandamdent(const char *src, char *dst, const struct substvar *svc)
{
unsigned int flags = conf_amd_get_flags(NULL);
const struct substvar *sv;
const char *o_src = src;
unsigned int squote = 0;
int len, l;
const char *p;
char ch;
len = 0;
while ((ch = *src++)) {
switch (ch) {
case '$':
if (*src == '{') {
char *start, *end;
unsigned int type = 0;
p = strchr(++src, '}');
if (!p) {
/* Ignore rest of string */
if (dst)
*dst = '\0';
return len;
}
start = (char *) src;
if (*src == '/' || *src == '.') {
start++;
type = EXPAND_LEADING_SLASH;
if (*src == '.')
type = EXPAND_LEADING_DOT;
}
end = (char *) p;
if (*(p - 1) == '/' || *(p - 1) == '.') {
end--;
type = EXPAND_TRAILING_SLASH;
if (*(p - 1) == '.')
type = EXPAND_TRAILING_DOT;
}
sv = macro_findvar(svc, start, end - start);
if (sv) {
char *val;
char *str = strdup(sv->val);
val = expand_slash_or_dot(str, type);
if (!val)
val = sv->val;
l = strlen(val);
if (dst) {
if (*dst)
strcat(dst, val);
else
strcpy(dst, val);
dst += l;
}
len += l;
if (str)
free(str);
} else {
if (dst) {
*dst++ = ch;
*dst++ = '{';
strncat(dst, src, p - src);
dst += (p - src);
*dst++ = '}';
}
len += 1 + 1 + (p - src) + 1;
}
src = p + 1;
} else {
if (dst)
*(dst++) = ch;
len++;
}
break;
case '\\':
if (squote || !(flags & CONF_NORMALIZE_SLASHES)) {
len++;
if (dst)
*dst++ = ch;
break;
}
if (*src) {
len++;
if (dst)
*dst++ = *src;
src++;
}
break;
case '/':
len++;
if (dst)
*dst++ = ch;
if (squote || !(flags & CONF_NORMALIZE_SLASHES))
break;
/* Double slash at start is allowed */
if (src == (o_src + 1) && *src == '/') {
len++;
if (dst)
*dst++ = *src;
src++;
}
while (*src == '/')
src++;
break;
/* 39 is single quote */
case 39:
len++;
if (dst)
*dst++ = ch;
squote = !squote;
break;
default:
if (dst)
*(dst++) = ch;
len++;
break;
}
}
if (dst)
*dst = '\0';
return len;
}
int expand_selectors(struct autofs_point *ap,
const char *mapstr, char **pmapstr,
struct substvar *sv)
{
char buf[MAX_ERR_BUF];
char *expand;
size_t len;
if (!mapstr)
return 0;
len = expandamdent(mapstr, NULL, sv);
if (len == 0) {
error(ap->logopt, "failed to expand map entry");
return 0;
}
expand = malloc(len + 1);
if (!expand) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt, "malloc: %s", estr);
return 0;
}
memset(expand, 0, len + 1);
expandamdent(mapstr, expand, sv);
*pmapstr = expand;
return len;
}
/* Get next space seperated argument, arguments containing
* space characters may be single quoted.
*/
static char *next_arg(char *str, char **next)
{
char *start;
char *ptr;
if (!*str)
return NULL;
start = ptr = str;
/* The amd map format parser should ensure there
* are matching single quotes.
*/
if (*start == 39) {
start++;
ptr++;
while (*ptr && *ptr != 39)
ptr++;
} else {
while (*ptr && *ptr != ' ')
ptr++;
}
if (*ptr)
*ptr++ = 0;
*next = ptr;
return start;
}
/* Construct program path name plus argument array for use with
* execv(3).
*/
int construct_argv(char *str, char **prog, char ***argv)
{
char *program = NULL;
char *start, *next;
char **args, *arg;
int argc;
start = str;
args = malloc(sizeof(char *));
if (!args)
return -1;
args[0] = NULL;
argc = 0;
next = NULL;
program = next_arg(str, &next);
if (!program) {
free(args);
return -1;
}
start = next;
while (1) {
if (!*next)
break;
arg = next_arg(start, &next);
if (arg) {
argc++;
args = add_argv(argc, args, arg);
if (!args)
return -1;
}
start = next;
}
*prog = program;
*argv = args;
return argc;
}
void free_map_type_info(struct map_type_info *info)
{
if (info->type)
free(info->type);
if (info->format)
free(info->format);
if (info->map)
free(info->map);
free(info);
return;
}
struct map_type_info *parse_map_type_info(const char *str)
{
struct map_type_info *info;
char *buf, *type, *fmt, *map, *tmp;
char *pos;
buf = strdup(str);
if (!buf)
return NULL;
info = malloc(sizeof(struct map_type_info));
if (!info) {
free(buf);
return NULL;
}
memset(info, 0, sizeof(struct map_type_info));
type = fmt = map = NULL;
tmp = strchr(buf, ':');
if (!tmp) {
pos = buf;
while (*pos == ' ')
*pos++ = '\0';
map = pos;
} else {
int i, j;
for (i = 0; i < map_type_count; i++) {
char *m_type = map_type[i].type;
unsigned int m_len = map_type[i].len;
pos = buf;
if (strncmp(m_type, pos, m_len))
continue;
type = pos;
pos += m_len;
if (*pos == ' ' || *pos == ':') {
while (*pos == ' ')
*pos++ = '\0';
if (*pos != ':') {
free(buf);
free(info);
return NULL;
} else {
*pos++ = '\0';
while (*pos && *pos == ' ')
*pos++ = '\0';
map = pos;
break;
}
}
if (*pos == ',') {
*pos++ = '\0';
for (j = 0; j < format_type_count; j++) {
char *f_type = format_type[j].type;
unsigned int f_len = format_type[j].len;
if (strncmp(f_type, pos, f_len))
continue;
fmt = pos;
pos += f_len;
if (*pos == ' ' || *pos == ':') {
while (*pos == ' ')
*pos++ = '\0';
if (*pos != ':') {
free(buf);
free(info);
return NULL;
} else {
*pos++ = '\0';
while (*pos && *pos == ' ')
*pos++ = '\0';
map = pos;
break;
}
}
}
}
}
if (!type) {
pos = buf;
while (*pos == ' ')
*pos++ = '\0';
map = pos;
}
}
/* Look for space terminator - ignore local options */
for (tmp = buf; *tmp; tmp++) {
if (*tmp == ' ') {
*tmp = '\0';
break;
}
if (*tmp == '\\')
tmp++;
}
if (type) {
info->type = strdup(type);
if (!info->type) {
free(buf);
free_map_type_info(info);
return NULL;
}
}
if (fmt) {
info->format = strdup(fmt);
if (!info->format) {
free(buf);
free_map_type_info(info);
return NULL;
}
}
if (map) {
info->map = strdup(map);
if (!info->map) {
free(buf);
free_map_type_info(info);
return NULL;
}
}
free(buf);
return info;
}