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