blob: dbf47b966522ad4bffbec98509793b076500db0b [file] [log] [blame]
/*
* 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;
}