| /* |
| * support/export/client.c |
| * |
| * Maintain list of nfsd clients. |
| * |
| * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <sys/types.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <netdb.h> |
| #include <errno.h> |
| |
| #include "sockaddr.h" |
| #include "misc.h" |
| #include "nfslib.h" |
| #include "exportfs.h" |
| |
| /* netgroup stuff never seems to be defined in any header file. Linux is |
| * not alone in this. |
| */ |
| #if !defined(__GLIBC__) || __GLIBC__ < 2 |
| extern int innetgr(char *netgr, char *host, char *, char *); |
| #endif |
| |
| static char *add_name(char *old, const char *add); |
| |
| nfs_client *clientlist[MCL_MAXTYPES] = { NULL, }; |
| |
| |
| static void |
| init_addrlist(nfs_client *clp, const struct addrinfo *ai) |
| { |
| int i; |
| |
| if (ai == NULL) |
| return; |
| |
| for (i = 0; (ai != NULL) && (i < NFSCLNT_ADDRMAX); i++) { |
| set_addrlist(clp, i, ai->ai_addr); |
| ai = ai->ai_next; |
| } |
| |
| clp->m_naddr = i; |
| } |
| |
| static void |
| client_free(nfs_client *clp) |
| { |
| free(clp->m_hostname); |
| free(clp); |
| } |
| |
| static int |
| init_netmask4(nfs_client *clp, const char *slash) |
| { |
| struct sockaddr_in sin = { |
| .sin_family = AF_INET, |
| }; |
| uint32_t shift; |
| |
| /* |
| * Decide what kind of netmask was specified. If there's |
| * no '/' present, assume the netmask is all ones. If |
| * there is a '/' and at least one '.', look for a spelled- |
| * out netmask. Otherwise, assume it was a prefixlen. |
| */ |
| if (slash == NULL) |
| shift = 0; |
| else { |
| unsigned long prefixlen; |
| |
| if (strchr(slash + 1, '.') != NULL) { |
| if (inet_pton(AF_INET, slash + 1, |
| &sin.sin_addr.s_addr) == 0) |
| goto out_badmask; |
| set_addrlist_in(clp, 1, &sin); |
| return 1; |
| } else { |
| char *endptr; |
| |
| prefixlen = strtoul(slash + 1, &endptr, 10); |
| if (*endptr != '\0' && prefixlen != ULONG_MAX && |
| errno != ERANGE) |
| goto out_badprefix; |
| } |
| if (prefixlen > 32) |
| goto out_badprefix; |
| shift = 32 - (uint32_t)prefixlen; |
| } |
| |
| /* |
| * Now construct the full netmask bitmask in a sockaddr_in, |
| * and plant it in the nfs_client record. |
| */ |
| sin.sin_addr.s_addr = htonl((uint32_t)~0 << shift); |
| set_addrlist_in(clp, 1, &sin); |
| |
| return 1; |
| |
| out_badmask: |
| xlog(L_ERROR, "Invalid netmask `%s' for %s", slash + 1, clp->m_hostname); |
| return 0; |
| |
| out_badprefix: |
| xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname); |
| return 0; |
| } |
| |
| #ifdef IPV6_SUPPORTED |
| static int |
| init_netmask6(nfs_client *clp, const char *slash) |
| { |
| struct sockaddr_in6 sin6 = { |
| .sin6_family = AF_INET6, |
| }; |
| unsigned long prefixlen; |
| uint32_t shift; |
| int i; |
| |
| /* |
| * Decide what kind of netmask was specified. If there's |
| * no '/' present, assume the netmask is all ones. If |
| * there is a '/' and at least one ':', look for a spelled- |
| * out netmask. Otherwise, assume it was a prefixlen. |
| */ |
| if (slash == NULL) |
| prefixlen = 128; |
| else { |
| if (strchr(slash + 1, ':') != NULL) { |
| if (!inet_pton(AF_INET6, slash + 1, &sin6.sin6_addr)) |
| goto out_badmask; |
| set_addrlist_in6(clp, 1, &sin6); |
| return 1; |
| } else { |
| char *endptr; |
| |
| prefixlen = strtoul(slash + 1, &endptr, 10); |
| if (*endptr != '\0' && prefixlen != ULONG_MAX && |
| errno != ERANGE) |
| goto out_badprefix; |
| } |
| if (prefixlen > 128) |
| goto out_badprefix; |
| } |
| |
| /* |
| * Now construct the full netmask bitmask in a sockaddr_in6, |
| * and plant it in the nfs_client record. |
| */ |
| for (i = 0; prefixlen > 32; i++) { |
| sin6.sin6_addr.s6_addr32[i] = 0xffffffff; |
| prefixlen -= 32; |
| } |
| shift = 32 - (uint32_t)prefixlen; |
| sin6.sin6_addr.s6_addr32[i] = htonl((uint32_t)~0 << shift); |
| set_addrlist_in6(clp, 1, &sin6); |
| |
| return 1; |
| |
| out_badmask: |
| xlog(L_ERROR, "Invalid netmask `%s' for %s", slash + 1, clp->m_hostname); |
| return 0; |
| |
| out_badprefix: |
| xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname); |
| return 0; |
| } |
| #else /* IPV6_SUPPORTED */ |
| static int |
| init_netmask6(nfs_client *UNUSED(clp), const char *UNUSED(slash)) |
| { |
| return 0; |
| } |
| #endif /* IPV6_SUPPORTED */ |
| |
| /* |
| * Parse the network mask for M_SUBNETWORK type clients. |
| * |
| * Return TRUE if successful, or FALSE if some error occurred. |
| */ |
| static int |
| init_subnetwork(nfs_client *clp) |
| { |
| struct addrinfo *ai; |
| sa_family_t family; |
| int result = 0; |
| char *slash; |
| |
| slash = strchr(clp->m_hostname, '/'); |
| if (slash != NULL) { |
| *slash = '\0'; |
| ai = host_pton(clp->m_hostname); |
| *slash = '/'; |
| } else |
| ai = host_pton(clp->m_hostname); |
| if (ai == NULL) { |
| xlog(L_ERROR, "Invalid IP address %s", clp->m_hostname); |
| return result; |
| } |
| |
| set_addrlist(clp, 0, ai->ai_addr); |
| family = ai->ai_addr->sa_family; |
| |
| freeaddrinfo(ai); |
| |
| switch (family) { |
| case AF_INET: |
| result = init_netmask4(clp, slash); |
| break; |
| case AF_INET6: |
| result = init_netmask6(clp, slash); |
| break; |
| default: |
| xlog(L_ERROR, "Unsupported address family for %s", |
| clp->m_hostname); |
| } |
| |
| return result; |
| } |
| |
| static int |
| client_init(nfs_client *clp, const char *hname, const struct addrinfo *ai) |
| { |
| clp->m_hostname = strdup(hname); |
| if (clp->m_hostname == NULL) |
| return 0; |
| |
| clp->m_exported = 0; |
| clp->m_count = 0; |
| clp->m_naddr = 0; |
| |
| if (clp->m_type == MCL_SUBNETWORK) |
| return init_subnetwork(clp); |
| |
| init_addrlist(clp, ai); |
| return 1; |
| } |
| |
| static void |
| client_add(nfs_client *clp) |
| { |
| nfs_client **cpp; |
| |
| cpp = &clientlist[clp->m_type]; |
| while (*cpp != NULL) |
| cpp = &((*cpp)->m_next); |
| clp->m_next = NULL; |
| *cpp = clp; |
| } |
| |
| /** |
| * client_lookup - look for @hname in our list of cached nfs_clients |
| * @hname: '\0'-terminated ASCII string containing hostname to look for |
| * @canonical: if set, @hname is known to be canonical DNS name |
| * |
| * Returns pointer to a matching or freshly created nfs_client. NULL |
| * is returned if some problem occurs. |
| */ |
| nfs_client * |
| client_lookup(char *hname, int canonical) |
| { |
| nfs_client *clp = NULL; |
| int htype; |
| struct addrinfo *ai = NULL; |
| |
| htype = client_gettype(hname); |
| |
| if (htype == MCL_FQDN && !canonical) { |
| ai = host_addrinfo(hname); |
| if (!ai) { |
| xlog(L_ERROR, "Failed to resolve %s", hname); |
| goto out; |
| } |
| hname = ai->ai_canonname; |
| |
| for (clp = clientlist[htype]; clp; clp = clp->m_next) |
| if (client_check(clp, ai)) |
| break; |
| } else { |
| for (clp = clientlist[htype]; clp; clp = clp->m_next) { |
| if (strcasecmp(hname, clp->m_hostname)==0) |
| break; |
| } |
| } |
| |
| if (clp == NULL) { |
| clp = calloc(1, sizeof(*clp)); |
| if (clp == NULL) |
| goto out; |
| clp->m_type = htype; |
| if (!client_init(clp, hname, NULL)) { |
| client_free(clp); |
| clp = NULL; |
| goto out; |
| } |
| client_add(clp); |
| } |
| |
| if (htype == MCL_FQDN && clp->m_naddr == 0) |
| init_addrlist(clp, ai); |
| |
| out: |
| freeaddrinfo(ai); |
| return clp; |
| } |
| |
| /** |
| * client_dup - create a copy of an nfs_client |
| * @clp: pointer to nfs_client to copy |
| * @ai: pointer to addrinfo used to initialize the new client's addrlist |
| * |
| * Returns a dynamically allocated nfs_client if successful, or |
| * NULL if some problem occurs. Caller must free the returned |
| * nfs_client with free(3). |
| */ |
| nfs_client * |
| client_dup(const nfs_client *clp, const struct addrinfo *ai) |
| { |
| nfs_client *new; |
| |
| new = (nfs_client *)malloc(sizeof(*new)); |
| if (new == NULL) |
| return NULL; |
| memcpy(new, clp, sizeof(*new)); |
| new->m_type = MCL_FQDN; |
| new->m_hostname = NULL; |
| |
| if (!client_init(new, ai->ai_canonname, ai)) { |
| client_free(new); |
| return NULL; |
| } |
| client_add(new); |
| return new; |
| } |
| |
| /** |
| * client_release - drop a reference to an nfs_client record |
| * |
| */ |
| void |
| client_release(nfs_client *clp) |
| { |
| if (clp->m_count <= 0) |
| xlog(L_FATAL, "client_free: m_count <= 0!"); |
| clp->m_count--; |
| } |
| |
| /** |
| * client_freeall - deallocate all nfs_client records |
| * |
| */ |
| void |
| client_freeall(void) |
| { |
| nfs_client *clp, **head; |
| int i; |
| |
| for (i = 0; i < MCL_MAXTYPES; i++) { |
| head = clientlist + i; |
| while (*head) { |
| *head = (clp = *head)->m_next; |
| client_free(clp); |
| } |
| } |
| } |
| |
| /** |
| * client_resolve - look up an IP address |
| * @sap: pointer to socket address to resolve |
| * |
| * Returns an addrinfo structure, or NULL if some problem occurred. |
| * Caller must free the result with freeaddrinfo(3). |
| */ |
| struct addrinfo * |
| client_resolve(const struct sockaddr *sap) |
| { |
| struct addrinfo *ai = NULL; |
| |
| if (clientlist[MCL_WILDCARD] || clientlist[MCL_NETGROUP]) |
| ai = host_reliable_addrinfo(sap); |
| if (ai == NULL) |
| ai = host_numeric_addrinfo(sap); |
| |
| return ai; |
| } |
| |
| /** |
| * client_compose - Make a list of cached hostnames that match an IP address |
| * @ai: pointer to addrinfo containing IP address information to match |
| * |
| * Gather all known client hostnames that match the IP address, and sort |
| * the result into a comma-separated list. |
| * |
| * Returns a '\0'-terminated ASCII string containing a comma-separated |
| * sorted list of client hostnames, or NULL if no client records matched |
| * the IP address or memory could not be allocated. Caller must free the |
| * returned string with free(3). |
| */ |
| char * |
| client_compose(const struct addrinfo *ai) |
| { |
| char *name = NULL; |
| int i; |
| |
| for (i = 0 ; i < MCL_MAXTYPES; i++) { |
| nfs_client *clp; |
| for (clp = clientlist[i]; clp ; clp = clp->m_next) { |
| if (!client_check(clp, ai)) |
| continue; |
| name = add_name(name, clp->m_hostname); |
| } |
| } |
| return name; |
| } |
| |
| /** |
| * client_member - check if @name is contained in the list @client |
| * @client: '\0'-terminated ASCII string containing |
| * comma-separated list of hostnames |
| * @name: '\0'-terminated ASCII string containing hostname to look for |
| * |
| * Returns 1 if @name was found in @client, otherwise zero is returned. |
| */ |
| int |
| client_member(const char *client, const char *name) |
| { |
| size_t l = strlen(name); |
| |
| while (*client) { |
| if (strncmp(client, name, l) == 0 && |
| (client[l] == ',' || client[l] == '\0')) |
| return 1; |
| client = strchr(client, ','); |
| if (client == NULL) |
| return 0; |
| client++; |
| } |
| return 0; |
| } |
| |
| static int |
| name_cmp(const char *a, const char *b) |
| { |
| /* compare strings a and b, but only upto ',' in a */ |
| while (*a && *b && *a != ',' && *a == *b) |
| a++, b++; |
| if (!*b && (!*a || *a == ',')) |
| return 0; |
| if (!*b) return 1; |
| if (!*a || *a == ',') return -1; |
| return *a - *b; |
| } |
| |
| static char * |
| add_name(char *old, const char *add) |
| { |
| size_t len = strlen(add) + 2; |
| char *new; |
| char *cp; |
| if (old) len += strlen(old); |
| |
| new = malloc(len); |
| if (!new) { |
| free(old); |
| return NULL; |
| } |
| cp = old; |
| while (cp && *cp && name_cmp(cp, add) < 0) { |
| /* step cp forward over a name */ |
| char *e = strchr(cp, ','); |
| if (e) |
| cp = e+1; |
| else |
| cp = cp + strlen(cp); |
| } |
| strncpy(new, old, cp-old); |
| new[cp-old] = 0; |
| if (cp != old && !*cp) |
| strcat(new, ","); |
| strcat(new, add); |
| if (cp && *cp) { |
| strcat(new, ","); |
| strcat(new, cp); |
| } |
| free(old); |
| return new; |
| } |
| |
| /* |
| * Check each address listed in @ai against each address |
| * stored in @clp. Return 1 if a match is found, otherwise |
| * zero. |
| */ |
| static int |
| check_fqdn(const nfs_client *clp, const struct addrinfo *ai) |
| { |
| int i; |
| |
| for (; ai; ai = ai->ai_next) |
| for (i = 0; i < clp->m_naddr; i++) |
| if (nfs_compare_sockaddr(ai->ai_addr, |
| get_addrlist(clp, i))) |
| return 1; |
| |
| return 0; |
| } |
| |
| static _Bool |
| mask_match(const uint32_t a, const uint32_t b, const uint32_t m) |
| { |
| return ((a ^ b) & m) == 0; |
| } |
| |
| static int |
| check_subnet_v4(const struct sockaddr_in *address, |
| const struct sockaddr_in *mask, const struct addrinfo *ai) |
| { |
| for (; ai; ai = ai->ai_next) { |
| struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; |
| |
| if (sin->sin_family != AF_INET) |
| continue; |
| |
| if (mask_match(address->sin_addr.s_addr, |
| sin->sin_addr.s_addr, |
| mask->sin_addr.s_addr)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| #ifdef IPV6_SUPPORTED |
| static int |
| check_subnet_v6(const struct sockaddr_in6 *address, |
| const struct sockaddr_in6 *mask, const struct addrinfo *ai) |
| { |
| for (; ai; ai = ai->ai_next) { |
| struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr; |
| |
| if (sin6->sin6_family != AF_INET6) |
| continue; |
| |
| if (mask_match(address->sin6_addr.s6_addr32[0], |
| sin6->sin6_addr.s6_addr32[0], |
| mask->sin6_addr.s6_addr32[0]) && |
| mask_match(address->sin6_addr.s6_addr32[1], |
| sin6->sin6_addr.s6_addr32[1], |
| mask->sin6_addr.s6_addr32[1]) && |
| mask_match(address->sin6_addr.s6_addr32[2], |
| sin6->sin6_addr.s6_addr32[2], |
| mask->sin6_addr.s6_addr32[2]) && |
| mask_match(address->sin6_addr.s6_addr32[3], |
| sin6->sin6_addr.s6_addr32[3], |
| mask->sin6_addr.s6_addr32[3])) |
| return 1; |
| } |
| return 0; |
| } |
| #else /* !IPV6_SUPPORTED */ |
| static int |
| check_subnet_v6(const struct sockaddr_in6 *UNUSED(address), |
| const struct sockaddr_in6 *UNUSED(mask), |
| const struct addrinfo *UNUSED(ai)) |
| { |
| return 0; |
| } |
| #endif /* !IPV6_SUPPORTED */ |
| |
| /* |
| * Check each address listed in @ai against the subnetwork or |
| * host address stored in @clp. Return 1 if an address in @hp |
| * matches the host address stored in @clp, otherwise zero. |
| */ |
| static int |
| check_subnetwork(const nfs_client *clp, const struct addrinfo *ai) |
| { |
| switch (get_addrlist(clp, 0)->sa_family) { |
| case AF_INET: |
| return check_subnet_v4(get_addrlist_in(clp, 0), |
| get_addrlist_in(clp, 1), ai); |
| case AF_INET6: |
| return check_subnet_v6(get_addrlist_in6(clp, 0), |
| get_addrlist_in6(clp, 1), ai); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Check if a wildcard nfs_client record matches the canonical name |
| * or the aliases of a host. Return 1 if a match is found, otherwise |
| * zero. |
| */ |
| static int |
| check_wildcard(const nfs_client *clp, const struct addrinfo *ai) |
| { |
| char *cname = clp->m_hostname; |
| char *hname = ai->ai_canonname; |
| struct hostent *hp; |
| char **ap; |
| |
| if (wildmat(hname, cname)) |
| return 1; |
| |
| /* See if hname aliases listed in /etc/hosts or nis[+] |
| * match the requested wildcard */ |
| hp = gethostbyname(hname); |
| if (hp != NULL) { |
| for (ap = hp->h_aliases; *ap; ap++) |
| if (wildmat(*ap, cname)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Check if @ai's hostname or aliases fall in a given netgroup. |
| * Return 1 if @ai represents a host in the netgroup, otherwise |
| * zero. |
| */ |
| #ifdef HAVE_INNETGR |
| static int |
| check_netgroup(const nfs_client *clp, const struct addrinfo *ai) |
| { |
| const char *netgroup = clp->m_hostname + 1; |
| struct addrinfo *tmp = NULL; |
| struct hostent *hp; |
| char *dot, *hname; |
| int i, match; |
| |
| match = 0; |
| |
| hname = strdup(ai->ai_canonname); |
| if (hname == NULL) { |
| xlog(D_GENERAL, "%s: no memory for strdup", __func__); |
| goto out; |
| } |
| |
| /* First, try to match the hostname without |
| * splitting off the domain */ |
| if (innetgr(netgroup, hname, NULL, NULL)) { |
| match = 1; |
| goto out; |
| } |
| |
| /* See if hname aliases listed in /etc/hosts or nis[+] |
| * match the requested netgroup */ |
| hp = gethostbyname(hname); |
| if (hp != NULL) { |
| for (i = 0; hp->h_aliases[i]; i++) |
| if (innetgr(netgroup, hp->h_aliases[i], NULL, NULL)) { |
| match = 1; |
| goto out; |
| } |
| } |
| |
| /* If hname happens to be an IP address, convert it |
| * to a the canonical DNS name bound to this address. */ |
| tmp = host_pton(hname); |
| if (tmp != NULL) { |
| char *cname = host_canonname(tmp->ai_addr); |
| freeaddrinfo(tmp); |
| |
| /* The resulting FQDN may be in our netgroup. */ |
| if (cname != NULL) { |
| free(hname); |
| hname = cname; |
| if (innetgr(netgroup, hname, NULL, NULL)) { |
| match = 1; |
| goto out; |
| } |
| } |
| } |
| |
| /* Okay, strip off the domain (if we have one) */ |
| dot = strchr(hname, '.'); |
| if (dot == NULL) |
| goto out; |
| |
| *dot = '\0'; |
| match = innetgr(netgroup, hname, NULL, NULL); |
| |
| out: |
| free(hname); |
| return match; |
| } |
| #else /* !HAVE_INNETGR */ |
| static int |
| check_netgroup(__attribute__((unused)) const nfs_client *clp, |
| __attribute__((unused)) const struct addrinfo *ai) |
| { |
| return 0; |
| } |
| #endif /* !HAVE_INNETGR */ |
| |
| /** |
| * client_check - check if IP address information matches a cached nfs_client |
| * @clp: pointer to a cached nfs_client record |
| * @ai: pointer to addrinfo to compare it with |
| * |
| * Returns 1 if the address information matches the cached nfs_client, |
| * otherwise zero. |
| */ |
| int |
| client_check(const nfs_client *clp, const struct addrinfo *ai) |
| { |
| switch (clp->m_type) { |
| case MCL_FQDN: |
| return check_fqdn(clp, ai); |
| case MCL_SUBNETWORK: |
| return check_subnetwork(clp, ai); |
| case MCL_WILDCARD: |
| return check_wildcard(clp, ai); |
| case MCL_NETGROUP: |
| return check_netgroup(clp, ai); |
| case MCL_ANONYMOUS: |
| return 1; |
| case MCL_GSS: |
| return 0; |
| default: |
| xlog(D_GENERAL, "%s: unrecognized client type: %d", |
| __func__, clp->m_type); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * client_gettype - determine type of nfs_client given an identifier |
| * @ident: '\0'-terminated ASCII string containing a client identifier |
| * |
| * Returns the type of nfs_client record that would be used for |
| * this client. |
| */ |
| int |
| client_gettype(char *ident) |
| { |
| char *sp; |
| |
| if (ident[0] == '\0' || strcmp(ident, "*")==0) |
| return MCL_ANONYMOUS; |
| if (strncmp(ident, "gss/", 4) == 0) |
| return MCL_GSS; |
| if (ident[0] == '@') { |
| #ifndef HAVE_INNETGR |
| xlog(L_WARNING, "netgroup support not compiled in"); |
| #endif |
| return MCL_NETGROUP; |
| } |
| for (sp = ident; *sp; sp++) { |
| if (*sp == '*' || *sp == '?' || *sp == '[') |
| return MCL_WILDCARD; |
| if (*sp == '/') |
| return MCL_SUBNETWORK; |
| if (*sp == '\\' && sp[1]) |
| sp++; |
| } |
| |
| return MCL_FQDN; |
| } |