blob: 7d704cc6654c67c8bdf7e14e70967dfdf7776d34 [file] [log] [blame]
/*
* Copyright 2009 Oracle. All rights reserved.
*
* This file is part of nfs-utils.
*
* nfs-utils 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; either version 2 of the License, or
* (at your option) any later version.
*
* nfs-utils is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nfs-utils. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* NSM for Linux.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "sockaddr.h"
#include "statd.h"
#include "xlog.h"
#ifndef HAVE_DECL_AI_ADDRCONFIG
#define AI_ADDRCONFIG 0
#endif
/**
* statd_present_address - convert sockaddr to presentation address
* @sap: pointer to socket address to convert
* @buf: pointer to buffer to fill in
* @buflen: length of buffer
*
* Convert the passed-in sockaddr-style address to presentation format.
* The presentation format address is placed in @buf and is
* '\0'-terminated.
*
* Returns true if successful; otherwise false.
*
* getnameinfo(3) is preferred, since it can parse IPv6 scope IDs.
* An alternate version of statd_present_address() is available to
* handle older glibcs that do not have getnameinfo(3).
*/
#ifdef HAVE_GETNAMEINFO
_Bool
statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
{
socklen_t salen;
int error;
salen = nfs_sockaddr_length(sap);
if (salen == 0) {
xlog(D_GENERAL, "%s: unsupported address family",
__func__);
return false;
}
error = getnameinfo(sap, salen, buf, (socklen_t)buflen,
NULL, 0, NI_NUMERICHOST);
if (error != 0) {
xlog(D_GENERAL, "%s: getnameinfo(3): %s",
__func__, gai_strerror(error));
return false;
}
return true;
}
#else /* !HAVE_GETNAMEINFO */
_Bool
statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen)
{
const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
if (sin->sin_family != AF_INET) {
xlog(D_GENERAL, "%s: unsupported address family", __func__);
return false;
}
/* ensure '\0' termination */
memset(buf, 0, buflen);
if (inet_ntop(AF_INET, (char *)&sin->sin_addr,
buf, (socklen_t)buflen) == NULL) {
xlog(D_GENERAL, "%s: inet_ntop(3): %m", __func__);
return false;
}
return true;
}
#endif /* !HAVE_GETNAMEINFO */
/*
* Look up the hostname; report exceptional errors. Caller must
* call freeaddrinfo(3) if a valid addrinfo is returned.
*/
__attribute_malloc__
static struct addrinfo *
get_addrinfo(const char *hostname, const struct addrinfo *hint)
{
struct addrinfo *ai = NULL;
int error;
error = getaddrinfo(hostname, NULL, hint, &ai);
switch (error) {
case 0:
return ai;
case EAI_NONAME:
break;
default:
xlog(D_GENERAL, "%s: failed to resolve host %s: %s",
__func__, hostname, gai_strerror(error));
}
return NULL;
}
#ifdef HAVE_GETNAMEINFO
static _Bool
get_nameinfo(const struct sockaddr *sap, const socklen_t salen,
/*@out@*/ char *buf, const socklen_t buflen)
{
int error;
error = getnameinfo(sap, salen, buf, buflen, NULL, 0, NI_NAMEREQD);
if (error != 0) {
xlog(D_GENERAL, "%s: failed to resolve address: %s",
__func__, gai_strerror(error));
return false;
}
return true;
}
#else /* !HAVE_GETNAMEINFO */
static _Bool
get_nameinfo(const struct sockaddr *sap,
__attribute__ ((unused)) const socklen_t salen,
/*@out@*/ char *buf, socklen_t buflen)
{
struct sockaddr_in *sin = (struct sockaddr_in *)(char *)sap;
struct hostent *hp;
if (sin->sin_family != AF_INET) {
xlog(D_GENERAL, "%s: unknown address family: %d",
sin->sin_family);
return false;
}
hp = gethostbyaddr((const char *)&(sin->sin_addr.s_addr),
sizeof(struct in_addr), AF_INET);
if (hp == NULL) {
xlog(D_GENERAL, "%s: failed to resolve address: %m", __func__);
return false;
}
strncpy(buf, hp->h_name, (size_t)buflen);
return true;
}
#endif /* !HAVE_GETNAMEINFO */
/**
* statd_canonical_name - choose file name for monitor record files
* @hostname: C string containing hostname or presentation address
*
* Returns a '\0'-terminated ASCII string containing a fully qualified
* canonical hostname, or NULL if @hostname does not have a reverse
* mapping. Caller must free the result with free(3).
*
* Incoming hostnames are looked up to determine the canonical hostname,
* and incoming presentation addresses are converted to canonical
* hostnames.
*
* We won't monitor peers that don't have a reverse map. The canonical
* name gives us a key for our monitor list.
*/
__attribute_malloc__
char *
statd_canonical_name(const char *hostname)
{
struct addrinfo hint = {
#ifdef IPV6_SUPPORTED
.ai_family = AF_UNSPEC,
#else /* !IPV6_SUPPORTED */
.ai_family = AF_INET,
#endif /* !IPV6_SUPPORTED */
.ai_flags = AI_NUMERICHOST,
.ai_protocol = (int)IPPROTO_UDP,
};
char buf[NI_MAXHOST];
struct addrinfo *ai;
ai = get_addrinfo(hostname, &hint);
if (ai != NULL) {
/* @hostname was a presentation address */
_Bool result;
result = get_nameinfo(ai->ai_addr, ai->ai_addrlen,
buf, (socklen_t)sizeof(buf));
freeaddrinfo(ai);
if (!result)
return NULL;
return strdup(buf);
}
/* @hostname was a hostname */
hint.ai_flags = AI_CANONNAME;
ai = get_addrinfo(hostname, &hint);
if (ai == NULL)
return NULL;
strcpy(buf, ai->ai_canonname);
freeaddrinfo(ai);
return strdup(buf);
}
/**
* statd_matchhostname - check if two hostnames are equivalent
* @hostname1: C string containing hostname
* @hostname2: C string containing hostname
*
* Returns true if the hostnames are the same, the hostnames resolve
* to the same canonical name, or the hostnames resolve to at least
* one address that is the same. False is returned if the hostnames
* do not match in any of these ways, if either hostname contains
* wildcard characters, if either hostname is a netgroup name, or
* if an error occurs.
*/
_Bool
statd_matchhostname(const char *hostname1, const char *hostname2)
{
struct addrinfo *ai1, *ai2, *results1 = NULL, *results2 = NULL;
struct addrinfo hint = {
.ai_family = AF_UNSPEC,
.ai_flags = AI_CANONNAME,
.ai_protocol = (int)IPPROTO_UDP,
};
_Bool result = false;
if (strcasecmp(hostname1, hostname2) == 0) {
result = true;
goto out;
}
results1 = get_addrinfo(hostname1, &hint);
if (results1 == NULL)
goto out;
results2 = get_addrinfo(hostname2, &hint);
if (results2 == NULL)
goto out;
if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) {
result = true;
goto out;
}
for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next)
for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next)
if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) {
result = true;
break;
}
out:
freeaddrinfo(results2);
freeaddrinfo(results1);
xlog(D_CALL, "%s: hostnames %s", __func__,
(result ? "matched" : "did not match"));
return result;
}