2016-02-13 11:27:50 +03:00
/*
* libvirt_nss : Name Service Switch plugin
*
* The aim is to enable users and applications to translate
* domain names into IP addresses . However , this is currently
* available only for those domains which gets their IP addresses
* from a libvirt managed network .
*
* Copyright ( C ) 2016 Red Hat , Inc .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*/
# include <config.h>
# include "libvirt_nss.h"
2016-03-27 21:07:10 +03:00
# include <netinet/in.h>
2016-02-14 13:38:37 +03:00
# include <resolv.h>
# include <sys/types.h>
# include <dirent.h>
# include <arpa/inet.h>
2016-03-27 21:07:10 +03:00
# if defined(HAVE_BSD_NSS)
# include <nsswitch.h>
# endif
2016-02-14 13:38:37 +03:00
# include "viralloc.h"
2016-09-30 18:11:38 +03:00
# include "virtime.h"
2016-02-14 13:38:37 +03:00
# include "virsocketaddr.h"
# include "configmake.h"
2019-07-31 12:40:39 +03:00
2019-07-31 12:40:39 +03:00
# include "libvirt_nss_leases.h"
2019-07-31 12:40:39 +03:00
# if defined(LIBVIRT_NSS_GUEST)
# include "libvirt_nss_macs.h"
# endif /* !LIBVIRT_NSS_GUEST */
2016-02-14 13:38:37 +03:00
# define LEASEDIR LOCALSTATEDIR " / lib / libvirt / dnsmasq / "
2016-03-27 21:07:10 +03:00
# define LIBVIRT_ALIGN(x) (((x) + __SIZEOF_POINTER__ - 1) & ~(__SIZEOF_POINTER__ - 1))
2016-02-14 13:38:37 +03:00
# define FAMILY_ADDRESS_SIZE(family) ((family) == AF_INET6 ? 16 : 4)
2019-07-10 15:30:11 +03:00
static int
leaseAddressSorter ( const void * a ,
const void * b )
{
const leaseAddress * la = a ;
const leaseAddress * lb = b ;
return lb - > expirytime - la - > expirytime ;
}
static void
sortAddr ( leaseAddress * tmpAddress ,
size_t ntmpAddress )
{
qsort ( tmpAddress , ntmpAddress , sizeof ( * tmpAddress ) , leaseAddressSorter ) ;
}
2016-02-14 13:38:37 +03:00
/**
* findLease :
* @ name : domain name to lookup
* @ af : address family
* @ address : all the addresses found for selected @ af
* @ naddress : number of elements in @ address array
* @ found : whether @ name has been found
* @ errnop : errno pointer
*
* Lookup @ name in libvirt ' s IP database , parse it and store all
* addresses found in @ address array . Callers can choose which
* address family ( @ af ) should be returned . Currently only
* AF_INET ( IPv4 ) and AF_INET6 ( IPv6 ) are supported . As a corner
* case , AF_UNSPEC may be passed to @ af in which case no address
* filtering is done and addresses from both families are
* returned .
*
* Returns - 1 on error
* 0 on success
*/
static int
findLease ( const char * name ,
int af ,
leaseAddress * * address ,
size_t * naddress ,
bool * found ,
int * errnop )
2016-02-13 11:27:50 +03:00
{
2016-02-14 13:38:37 +03:00
DIR * dir = NULL ;
int ret = - 1 ;
const char * leaseDir = LEASEDIR ;
struct dirent * entry ;
2019-07-31 12:40:39 +03:00
char * * leaseFiles = NULL ;
size_t nleaseFiles = 0 ;
2019-07-30 19:10:33 +03:00
char * * macs = NULL ;
size_t nmacs = 0 ;
size_t i ;
2019-07-31 12:40:39 +03:00
time_t now ;
2016-02-14 13:38:37 +03:00
* address = NULL ;
* naddress = 0 ;
* found = false ;
if ( af ! = AF_UNSPEC & & af ! = AF_INET & & af ! = AF_INET6 ) {
errno = EAFNOSUPPORT ;
goto cleanup ;
}
2019-07-30 17:33:20 +03:00
dir = opendir ( leaseDir ) ;
if ( ! dir ) {
2016-02-14 13:38:37 +03:00
ERROR ( " Failed to open dir '%s' " , leaseDir ) ;
goto cleanup ;
}
DEBUG ( " Dir: %s " , leaseDir ) ;
2019-07-30 17:33:20 +03:00
while ( ( entry = readdir ( dir ) ) ! = NULL ) {
2016-02-14 13:38:37 +03:00
char * path ;
2019-07-30 17:33:20 +03:00
size_t dlen = strlen ( entry - > d_name ) ;
2016-02-14 13:38:37 +03:00
2019-07-30 17:33:20 +03:00
if ( dlen > = 7 & & STREQ ( entry - > d_name + dlen - 7 , " .status " ) ) {
2019-07-31 12:40:39 +03:00
char * * tmpLease ;
2019-07-30 17:33:20 +03:00
if ( asprintf ( & path , " %s/%s " , leaseDir , entry - > d_name ) < 0 )
2016-11-29 15:48:53 +03:00
goto cleanup ;
2016-02-14 13:38:37 +03:00
2019-07-31 12:40:39 +03:00
tmpLease = realloc ( leaseFiles , sizeof ( char * ) * ( nleaseFiles + 1 ) ) ;
if ( ! tmpLease )
2016-11-29 15:48:53 +03:00
goto cleanup ;
2019-07-31 12:40:39 +03:00
leaseFiles = tmpLease ;
leaseFiles [ nleaseFiles + + ] = path ;
2019-07-30 19:10:33 +03:00
# if defined(LIBVIRT_NSS_GUEST)
2019-07-30 17:33:20 +03:00
} else if ( dlen > = 5 & & STREQ ( entry - > d_name + dlen - 5 , " .macs " ) ) {
2019-07-30 17:33:20 +03:00
if ( asprintf ( & path , " %s/%s " , leaseDir , entry - > d_name ) < 0 )
2016-11-29 15:48:53 +03:00
goto cleanup ;
DEBUG ( " Processing %s " , path ) ;
2019-07-31 12:40:39 +03:00
if ( findMACs ( path , name , & macs , & nmacs ) < 0 ) {
2016-11-29 15:48:53 +03:00
VIR_FREE ( path ) ;
goto cleanup ;
}
2016-02-14 13:38:37 +03:00
VIR_FREE ( path ) ;
2019-07-30 19:10:33 +03:00
# endif /* LIBVIRT_NSS_GUEST */
2016-02-14 13:38:37 +03:00
}
2019-07-30 17:33:20 +03:00
errno = 0 ;
2016-02-14 13:38:37 +03:00
}
2019-07-30 17:33:20 +03:00
closedir ( dir ) ;
dir = NULL ;
2016-02-14 13:38:37 +03:00
2019-07-30 19:10:33 +03:00
# if defined(LIBVIRT_NSS_GUEST)
DEBUG ( " Finding with %zu macs " , nmacs ) ;
if ( ! nmacs )
goto cleanup ;
2019-07-31 12:40:39 +03:00
for ( i = 0 ; i < nmacs ; i + + )
DEBUG ( " %s " , macs [ i ] ) ;
2019-07-30 19:10:33 +03:00
# endif
2019-07-31 12:40:39 +03:00
if ( ( now = time ( NULL ) ) = = ( time_t ) - 1 ) {
DEBUG ( " Failed to get time " ) ;
2016-09-30 18:11:38 +03:00
goto cleanup ;
2019-07-31 12:40:39 +03:00
}
2016-02-14 13:38:37 +03:00
2019-07-31 12:40:39 +03:00
for ( i = 0 ; i < nleaseFiles ; i + + ) {
if ( findLeases ( leaseFiles [ i ] ,
name , macs , nmacs ,
af , now ,
address , naddress ,
found ) < 0 )
goto cleanup ;
}
2019-07-10 15:30:11 +03:00
2019-07-31 12:40:39 +03:00
DEBUG ( " Found %zu addresses " , * naddress ) ;
sortAddr ( * address , * naddress ) ;
2016-02-14 13:38:37 +03:00
ret = 0 ;
cleanup :
* errnop = errno ;
2019-07-31 12:40:39 +03:00
for ( i = 0 ; i < nleaseFiles ; i + + )
free ( leaseFiles [ i ] ) ;
free ( leaseFiles ) ;
2019-07-30 19:10:33 +03:00
for ( i = 0 ; i < nmacs ; i + + )
free ( macs [ i ] ) ;
free ( macs ) ;
2019-07-31 12:40:39 +03:00
if ( ret < 0 ) {
free ( * address ) ;
* address = NULL ;
* naddress = 0 ;
}
2019-07-30 17:33:20 +03:00
if ( dir )
closedir ( dir ) ;
2016-02-14 13:38:37 +03:00
return ret ;
}
enum nss_status
2016-12-04 13:56:44 +03:00
NSS_NAME ( gethostbyname ) ( const char * name , struct hostent * result ,
char * buffer , size_t buflen , int * errnop ,
int * herrnop )
2016-02-14 13:38:37 +03:00
{
2017-01-18 20:30:18 +03:00
return NSS_NAME ( gethostbyname3 ) ( name , AF_INET , result , buffer , buflen ,
2016-12-04 13:56:44 +03:00
errnop , herrnop , NULL , NULL ) ;
2016-02-14 13:38:37 +03:00
}
enum nss_status
2016-12-04 13:56:44 +03:00
NSS_NAME ( gethostbyname2 ) ( const char * name , int af , struct hostent * result ,
char * buffer , size_t buflen , int * errnop ,
int * herrnop )
2016-02-14 13:38:37 +03:00
{
2016-12-04 13:56:44 +03:00
return NSS_NAME ( gethostbyname3 ) ( name , af , result , buffer , buflen ,
errnop , herrnop , NULL , NULL ) ;
2016-02-14 13:38:37 +03:00
}
2016-03-18 18:46:41 +03:00
static inline void *
move_and_align ( void * buf , size_t len , size_t * idx )
{
char * buffer = buf ;
2016-03-27 21:07:10 +03:00
size_t move = LIBVIRT_ALIGN ( len ) ;
2016-03-18 18:46:41 +03:00
if ( ! idx )
return buffer + move ;
* idx + = move ;
return buffer + * idx ;
}
2016-02-14 13:38:37 +03:00
enum nss_status
2016-12-04 13:56:44 +03:00
NSS_NAME ( gethostbyname3 ) ( const char * name , int af , struct hostent * result ,
char * buffer , size_t buflen , int * errnop ,
int * herrnop , int32_t * ttlp , char * * canonp )
2016-02-14 13:38:37 +03:00
{
enum nss_status ret = NSS_STATUS_UNAVAIL ;
2016-03-18 18:46:41 +03:00
char * r_name , * * r_aliases , * r_addr , * r_addr_next , * * r_addr_list ;
2019-07-10 11:03:49 +03:00
VIR_AUTOFREE ( leaseAddress * ) addr = NULL ;
2016-02-14 13:38:37 +03:00
size_t naddr , i ;
bool found = false ;
2016-03-18 18:46:41 +03:00
size_t nameLen , need , idx = 0 ;
2016-02-14 13:38:37 +03:00
int alen ;
int r ;
/* findLease is capable of returning both IPv4 and IPv6.
* However , this function has no way of telling user back the
* family per each address returned . Therefore , if @ af = =
* AF_UNSPEC return just one family instead of a mixture of
* both . Dice picked the former one . */
if ( af = = AF_UNSPEC )
af = AF_INET ;
if ( ( r = findLease ( name , af , & addr , & naddr , & found , errnop ) ) < 0 ) {
/* Error occurred. Return immediately. */
if ( * errnop = = EAGAIN ) {
* herrnop = TRY_AGAIN ;
return NSS_STATUS_TRYAGAIN ;
} else {
* herrnop = NO_RECOVERY ;
return NSS_STATUS_UNAVAIL ;
}
}
if ( ! found ) {
/* NOT found */
* errnop = ESRCH ;
* herrnop = HOST_NOT_FOUND ;
return NSS_STATUS_NOTFOUND ;
} else if ( ! naddr ) {
/* Found, but no data */
* errnop = ENXIO ;
* herrnop = NO_DATA ;
return NSS_STATUS_UNAVAIL ;
}
/* Found and have data */
alen = FAMILY_ADDRESS_SIZE ( addr [ 0 ] . af ) ;
nameLen = strlen ( name ) ;
/* We need space for:
* a ) name
* b ) alias
* c ) addresses
* d ) NULL stem */
2016-03-27 21:07:10 +03:00
need = LIBVIRT_ALIGN ( nameLen + 1 ) + naddr * LIBVIRT_ALIGN ( alen ) + ( naddr + 2 ) * sizeof ( char * ) ;
2016-02-14 13:38:37 +03:00
if ( buflen < need ) {
* errnop = ENOMEM ;
* herrnop = TRY_AGAIN ;
ret = NSS_STATUS_TRYAGAIN ;
goto cleanup ;
}
/* First, append name */
r_name = buffer ;
memcpy ( r_name , name , nameLen + 1 ) ;
2016-03-18 18:46:41 +03:00
r_aliases = move_and_align ( buffer , nameLen + 1 , & idx ) ;
2016-02-14 13:38:37 +03:00
/* Second, create aliases array */
r_aliases [ 0 ] = NULL ;
/* Third, append address */
2016-03-18 18:46:41 +03:00
r_addr = move_and_align ( buffer , sizeof ( char * ) , & idx ) ;
r_addr_next = r_addr ;
for ( i = 0 ; i < naddr ; i + + ) {
memcpy ( r_addr_next , addr [ i ] . addr , alen ) ;
r_addr_next = move_and_align ( buffer , alen , & idx ) ;
}
2016-02-14 13:38:37 +03:00
2016-03-18 18:46:41 +03:00
r_addr_list = move_and_align ( buffer , 0 , & idx ) ;
r_addr_next = r_addr ;
2016-02-14 13:38:37 +03:00
/* Third, append address pointer array */
2016-03-18 18:46:41 +03:00
for ( i = 0 ; i < naddr ; i + + ) {
r_addr_list [ i ] = r_addr_next ;
r_addr_next = move_and_align ( r_addr_next , alen , NULL ) ;
}
2016-02-14 13:38:37 +03:00
r_addr_list [ i ] = NULL ;
idx + = ( naddr + 1 ) * sizeof ( char * ) ;
/* At this point, idx == need */
DEBUG ( " Done idx:%zd need:%zd " , idx , need ) ;
result - > h_name = r_name ;
result - > h_aliases = r_aliases ;
result - > h_addrtype = af ;
result - > h_length = alen ;
result - > h_addr_list = r_addr_list ;
if ( ttlp )
* ttlp = 0 ;
if ( canonp )
* canonp = r_name ;
/* Explicitly reset all error variables */
* errnop = 0 ;
* herrnop = NETDB_SUCCESS ;
h_errno = 0 ;
ret = NSS_STATUS_SUCCESS ;
cleanup :
return ret ;
2016-02-13 11:27:50 +03:00
}
2016-02-14 10:13:54 +03:00
2016-03-27 21:07:10 +03:00
# ifdef HAVE_STRUCT_GAIH_ADDRTUPLE
2016-02-14 10:13:54 +03:00
enum nss_status
2016-12-04 13:56:44 +03:00
NSS_NAME ( gethostbyname4 ) ( const char * name , struct gaih_addrtuple * * pat ,
char * buffer , size_t buflen , int * errnop ,
int * herrnop , int32_t * ttlp )
2016-02-14 10:13:54 +03:00
{
enum nss_status ret = NSS_STATUS_UNAVAIL ;
leaseAddress * addr = NULL ;
size_t naddr , i ;
bool found = false ;
int r ;
2016-03-18 18:46:41 +03:00
size_t nameLen , need , idx = 0 ;
2016-02-14 10:13:54 +03:00
struct gaih_addrtuple * r_tuple , * r_tuple_first = NULL ;
char * r_name ;
if ( ( r = findLease ( name , AF_UNSPEC , & addr , & naddr , & found , errnop ) ) < 0 ) {
/* Error occurred. Return immediately. */
if ( * errnop = = EAGAIN ) {
* herrnop = TRY_AGAIN ;
return NSS_STATUS_TRYAGAIN ;
} else {
* herrnop = NO_RECOVERY ;
return NSS_STATUS_UNAVAIL ;
}
}
if ( ! found ) {
/* NOT found */
* errnop = ESRCH ;
* herrnop = HOST_NOT_FOUND ;
return NSS_STATUS_NOTFOUND ;
} else if ( ! naddr ) {
/* Found, but no data */
* errnop = ENXIO ;
* herrnop = NO_DATA ;
return NSS_STATUS_UNAVAIL ;
}
/* Found and have data */
nameLen = strlen ( name ) ;
/* We need space for:
* a ) name
* b ) addresses */
2016-03-27 21:07:10 +03:00
need = LIBVIRT_ALIGN ( nameLen + 1 ) + naddr * LIBVIRT_ALIGN ( sizeof ( struct gaih_addrtuple ) ) ;
2016-02-14 10:13:54 +03:00
if ( buflen < need ) {
* errnop = ENOMEM ;
* herrnop = TRY_AGAIN ;
ret = NSS_STATUS_TRYAGAIN ;
goto cleanup ;
}
/* First, append name */
r_name = buffer ;
memcpy ( r_name , name , nameLen + 1 ) ;
/* Second, append addresses */
2016-03-18 18:46:41 +03:00
r_tuple_first = move_and_align ( buffer , nameLen + 1 , & idx ) ;
2016-02-14 10:13:54 +03:00
for ( i = 0 ; i < naddr ; i + + ) {
int family = addr [ i ] . af ;
2016-03-18 18:46:41 +03:00
r_tuple = move_and_align ( buffer , 0 , & idx ) ;
2016-02-14 10:13:54 +03:00
if ( i = = naddr - 1 )
r_tuple - > next = NULL ;
else
2016-03-18 18:46:41 +03:00
r_tuple - > next = move_and_align ( buffer , sizeof ( struct gaih_addrtuple ) , & idx ) ;
2016-02-14 10:13:54 +03:00
r_tuple - > name = r_name ;
r_tuple - > family = family ;
r_tuple - > scopeid = 0 ;
memcpy ( r_tuple - > addr , addr [ i ] . addr , FAMILY_ADDRESS_SIZE ( family ) ) ;
}
/* At this point, idx == need */
DEBUG ( " Done idx:%zd need:%zd " , idx , need ) ;
if ( * pat )
* * pat = * r_tuple_first ;
else
* pat = r_tuple_first ;
if ( ttlp )
* ttlp = 0 ;
/* Explicitly reset all error variables */
* errnop = 0 ;
* herrnop = NETDB_SUCCESS ;
ret = NSS_STATUS_SUCCESS ;
cleanup :
return ret ;
}
2016-03-27 21:07:10 +03:00
# endif /* HAVE_STRUCT_GAIH_ADDRTUPLE */
# if defined(HAVE_BSD_NSS)
NSS_METHOD_PROTOTYPE ( _nss_compat_getaddrinfo ) ;
NSS_METHOD_PROTOTYPE ( _nss_compat_gethostbyname2_r ) ;
ns_mtab methods [ ] = {
{ NSDB_HOSTS , " getaddrinfo " , _nss_compat_getaddrinfo , NULL } ,
{ NSDB_HOSTS , " gethostbyname " , _nss_compat_gethostbyname2_r , NULL } ,
{ NSDB_HOSTS , " gethostbyname2_r " , _nss_compat_gethostbyname2_r , NULL } ,
} ;
static void
aiforaf ( const char * name , int af , struct addrinfo * pai , struct addrinfo * * aip )
{
int ret ;
struct hostent resolved ;
char buf [ 1024 ] = { 0 } ;
int err , herr ;
struct addrinfo hints , * res0 , * res ;
char * * addrList ;
2016-12-04 13:56:44 +03:00
if ( ( ret = NSS_NAME ( gethostbyname2 ) ( name , af , & resolved ,
buf , sizeof ( buf ) ,
& err , & herr ) ) ! = NS_SUCCESS )
2016-03-27 21:07:10 +03:00
return ;
addrList = resolved . h_addr_list ;
while ( * addrList ) {
virSocketAddr sa ;
char * ipAddr = NULL ;
void * address = * addrList ;
memset ( & sa , 0 , sizeof ( sa ) ) ;
if ( resolved . h_addrtype = = AF_INET ) {
virSocketAddrSetIPv4AddrNetOrder ( & sa , * ( ( uint32_t * ) address ) ) ;
} else {
virSocketAddrSetIPv6AddrNetOrder ( & sa , address ) ;
}
ipAddr = virSocketAddrFormat ( & sa ) ;
hints = * pai ;
hints . ai_flags = AI_NUMERICHOST ;
hints . ai_family = af ;
if ( getaddrinfo ( ipAddr , NULL , & hints , & res0 ) ) {
2017-09-22 13:41:51 +03:00
VIR_FREE ( ipAddr ) ;
2016-03-27 21:07:10 +03:00
addrList + + ;
continue ;
}
for ( res = res0 ; res ; res = res - > ai_next )
res - > ai_flags = pai - > ai_flags ;
( * aip ) - > ai_next = res0 ;
while ( ( * aip ) - > ai_next )
* aip = ( * aip ) - > ai_next ;
2017-09-22 13:41:51 +03:00
VIR_FREE ( ipAddr ) ;
2016-03-27 21:07:10 +03:00
addrList + + ;
}
}
int
_nss_compat_getaddrinfo ( void * retval , void * mdata ATTRIBUTE_UNUSED , va_list ap )
{
struct addrinfo sentinel , * cur , * ai ;
const char * name ;
name = va_arg ( ap , char * ) ;
ai = va_arg ( ap , struct addrinfo * ) ;
memset ( & sentinel , 0 , sizeof ( sentinel ) ) ;
cur = & sentinel ;
if ( ( ai - > ai_family = = AF_UNSPEC ) | | ( ai - > ai_family = = AF_INET6 ) )
aiforaf ( name , AF_INET6 , ai , & cur ) ;
if ( ( ai - > ai_family = = AF_UNSPEC ) | | ( ai - > ai_family = = AF_INET ) )
aiforaf ( name , AF_INET , ai , & cur ) ;
if ( sentinel . ai_next = = NULL ) {
h_errno = HOST_NOT_FOUND ;
return NS_NOTFOUND ;
}
* ( ( struct addrinfo * * ) retval ) = sentinel . ai_next ;
return NS_SUCCESS ;
}
int
_nss_compat_gethostbyname2_r ( void * retval , void * mdata ATTRIBUTE_UNUSED , va_list ap )
{
int ret ;
const char * name ;
int af ;
struct hostent * result ;
char * buffer ;
size_t buflen ;
int * errnop ;
int * herrnop ;
name = va_arg ( ap , const char * ) ;
af = va_arg ( ap , int ) ;
result = va_arg ( ap , struct hostent * ) ;
buffer = va_arg ( ap , char * ) ;
buflen = va_arg ( ap , size_t ) ;
errnop = va_arg ( ap , int * ) ;
herrnop = va_arg ( ap , int * ) ;
2016-12-04 13:56:44 +03:00
ret = NSS_NAME ( gethostbyname2 ) ( name , af , result , buffer , buflen , errnop , herrnop ) ;
2016-03-27 21:07:10 +03:00
* ( struct hostent * * ) retval = ( ret = = NS_SUCCESS ) ? result : NULL ;
return ret ;
}
ns_mtab *
nss_module_register ( const char * name ATTRIBUTE_UNUSED , unsigned int * size ,
nss_module_unregister_fn * unregister )
{
* size = sizeof ( methods ) / sizeof ( methods [ 0 ] ) ;
* unregister = NULL ;
return methods ;
}
# endif /* HAVE_BSD_NSS */