From 7dbcb26f7f67b9ff2bee47e6144763a3d729717e Mon Sep 17 00:00:00 2001 From: Michal Privoznik Date: Sun, 14 Feb 2016 11:38:37 +0100 Subject: [PATCH] nss: Implement _nss_libvirt_gethostbyname3_r The implementation is pretty straightforward. Moreover, because of the nature of things, gethostbyname_r and gethostbyname2_r can be implemented at the same time too. Signed-off-by: Michal Privoznik --- config-post.h | 24 +++ src/Makefile.am | 57 +++++++ src/util/virfile.c | 3 +- src/util/virlease.c | 1 + tests/Makefile.am | 2 +- tools/Makefile.am | 8 +- tools/nss/libvirt_nss.c | 336 ++++++++++++++++++++++++++++++++++++- tools/nss/libvirt_nss.h | 14 +- tools/nss/libvirt_nss.syms | 4 +- 9 files changed, 440 insertions(+), 9 deletions(-) diff --git a/config-post.h b/config-post.h index 8367200f9c..2398d3d9ce 100644 --- a/config-post.h +++ b/config-post.h @@ -43,3 +43,27 @@ # undef WITH_YAJL # undef WITH_YAJL2 #endif + +/* + * With the NSS module it's the same story as virt-login-shell. See the + * explanation above. + */ +#ifdef LIBVIRT_NSS +# undef HAVE_LIBDEVMAPPER_H +# undef HAVE_LIBNL +# undef HAVE_LIBNL3 +# undef HAVE_LIBSASL2 +# undef WITH_CAPNG +# undef WITH_CURL +# undef WITH_DTRACE_PROBES +# undef WITH_GNUTLS +# undef WITH_GNUTLS_GCRYPT +# undef WITH_MACVTAP +# undef WITH_NUMACTL +# undef WITH_SASL +# undef WITH_SSH2 +# undef WITH_VIRTUALPORT +# undef WITH_SECDRIVER_SELINUX +# undef WITH_SECDRIVER_APPARMOR +# undef WITH_CAPNG +#endif /* LIBVIRT_NSS */ diff --git a/src/Makefile.am b/src/Makefile.am index 2ba397f1cf..dad7bab88c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2946,6 +2946,63 @@ endif WITH_LIBVIRTD endif WITH_SECDRIVER_APPARMOR EXTRA_DIST += $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) +noinst_LTLIBRARIES += libvirt-nss.la + +libvirt_nss_la_SOURCES = \ + util/viralloc.c \ + util/viralloc.h \ + util/virbitmap.c \ + util/virbitmap.h \ + util/virbuffer.c \ + util/virbuffer.h \ + util/vircommand.c \ + util/vircommand.h \ + util/virerror.c \ + util/virerror.h \ + util/virfile.c \ + util/virfile.h \ + util/virjson.c \ + util/virjson.h \ + util/virkmod.c \ + util/virkmod.h \ + util/virlease.c \ + util/virlease.h \ + util/virlog.c \ + util/virlog.h \ + util/virobject.c \ + util/virobject.h \ + util/virpidfile.c \ + util/virpidfile.h \ + util/virprocess.c \ + util/virprocess.h \ + util/virsocketaddr.c \ + util/virsocketaddr.h \ + util/virstring.c \ + util/virstring.h \ + util/virthread.c \ + util/virthread.h \ + util/virthreadjob.c \ + util/virthreadjob.h \ + util/virtime.c \ + util/virtime.h \ + util/virutil.c \ + util/virutil.h \ + $(NULL) + +libvirt_nss_la_CFLAGS = \ + -DLIBVIRT_NSS \ + $(AM_CFLAGS) \ + $(YAJL_CFLAGS) \ + $(NULL) +libvirt_nss_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(NULL) + +libvirt_nss_la_LIBADD = \ + $(YAJL_LIBS) \ + $(NULL) + + install-data-local: install-init install-systemd if WITH_LIBVIRTD $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" diff --git a/src/util/virfile.c b/src/util/virfile.c index 0bba850203..f0412c6de5 100644 --- a/src/util/virfile.c +++ b/src/util/virfile.c @@ -67,7 +67,6 @@ #include "virlog.h" #include "virprocess.h" #include "virstring.h" -#include "virstoragefile.h" #include "virutil.h" #include "c-ctype.h" @@ -554,7 +553,7 @@ int virFileUpdatePerm(const char *path, #if defined(__linux__) && HAVE_DECL_LO_FLAGS_AUTOCLEAR && \ - !defined(LIBVIRT_SETUID_RPC_CLIENT) + !defined(LIBVIRT_SETUID_RPC_CLIENT) && !defined(LIBVIRT_NSS) # if HAVE_DECL_LOOP_CTL_GET_FREE diff --git a/src/util/virlease.c b/src/util/virlease.c index 910c00346b..920ebaf122 100644 --- a/src/util/virlease.c +++ b/src/util/virlease.c @@ -30,6 +30,7 @@ #include "virstring.h" #include "virerror.h" #include "viralloc.h" +#include "virutil.h" #define VIR_FROM_THIS VIR_FROM_NETWORK diff --git a/tests/Makefile.am b/tests/Makefile.am index 74f7f5a73e..a1d61f1cb8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -65,7 +65,7 @@ GNULIB_LIBS = \ ../gnulib/lib/libgnu.la LDADDS = \ - $(WARN_CFLAGS) \ + $(WARN_CFLAGS) \ $(NO_INDIRECT_LDFLAGS) \ $(PROBES_O) \ $(GNULIB_LIBS) \ diff --git a/tools/Makefile.am b/tools/Makefile.am index 97ae4e10bd..3218d2f998 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -429,10 +429,16 @@ nss_libnss_libvirt_impl_la_SOURCES = \ $(LIBVIRT_NSS_SOURCES) nss_libnss_libvirt_impl_la_CFLAGS = \ + -DLIBVIRT_NSS \ $(AM_CFLAGS) \ $(WARN_CFLAGS) \ $(PIE_CFLAGS) \ - $(COVERAGE_CFLAGS) + $(COVERAGE_CFLAGS) \ + $(LIBXML_CFLAGS) + +nss_libnss_libvirt_impl_la_LIBADD = \ + ../gnulib/lib/libgnu.la \ + ../src/libvirt-nss.la if WITH_NSS nss_libnss_libvirt_la_SOURCES = diff --git a/tools/nss/libvirt_nss.c b/tools/nss/libvirt_nss.c index 461d8cac1e..1b9ccba607 100644 --- a/tools/nss/libvirt_nss.c +++ b/tools/nss/libvirt_nss.c @@ -29,8 +29,338 @@ #include "libvirt_nss.h" -int -blah(int c) +#include +#include +#include +#include + +#include "virlease.h" +#include "viralloc.h" +#include "virfile.h" +#include "virerror.h" +#include "virstring.h" +#include "virsocketaddr.h" +#include "configmake.h" + +#if 0 +# define ERROR(...) \ +do { \ + char ebuf[1024]; \ + fprintf(stderr, "ERROR %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " : %s\n", virStrerror(errno, ebuf, sizeof(ebuf))); \ + fprintf(stderr, "\n"); \ +} while (0) + +# define DEBUG(...) \ +do { \ + fprintf(stderr, "DEBUG %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) +#else +# define ERROR(...) do { } while (0) +# define DEBUG(...) do { } while (0) +#endif + +#define LEASEDIR LOCALSTATEDIR "/lib/libvirt/dnsmasq/" + +#define ALIGN(x) (((x) + __SIZEOF_POINTER__ - 1) & ~(__SIZEOF_POINTER__ - 1)) +#define FAMILY_ADDRESS_SIZE(family) ((family) == AF_INET6 ? 16 : 4) + +typedef struct { + unsigned char addr[16]; + int af; +} leaseAddress; + +/** + * 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) { - return c; + DIR *dir = NULL; + int ret = -1; + const char *leaseDir = LEASEDIR; + struct dirent *entry; + virJSONValuePtr leases_array = NULL; + ssize_t i, nleases; + leaseAddress *tmpAddress = NULL; + size_t ntmpAddress = 0; + + *address = NULL; + *naddress = 0; + *found = false; + + if (af != AF_UNSPEC && af != AF_INET && af != AF_INET6) { + errno = EAFNOSUPPORT; + goto cleanup; + } + + + if (!(dir = opendir(leaseDir))) { + ERROR("Failed to open dir '%s'", leaseDir); + goto cleanup; + } + + if (!(leases_array = virJSONValueNewArray())) { + ERROR("Failed to create json array"); + goto cleanup; + } + + DEBUG("Dir: %s", leaseDir); + while ((ret = virDirRead(dir, &entry, leaseDir)) > 0) { + char *path; + + if (entry->d_name[0] == '.') + continue; + + if (!virFileHasSuffix(entry->d_name, ".status")) + continue; + + if (!(path = virFileBuildPath(leaseDir, entry->d_name, NULL))) + goto cleanup; + + DEBUG("Processing %s", path); + + if (virLeaseReadCustomLeaseFile(leases_array, path, NULL, NULL) < 0) { + ERROR("Unable to parse %s", path); + VIR_FREE(path); + goto cleanup; + } + + VIR_FREE(path); + } + + closedir(dir); + dir = NULL; + + nleases = virJSONValueArraySize(leases_array); + DEBUG("Read %zd leases", nleases); + + for (i = 0; i < nleases; i++) { + virJSONValuePtr lease; + const char *lease_name; + virSocketAddr sa; + const char *ipAddr; + int family; + + lease = virJSONValueArrayGet(leases_array, i); + + if (!lease) { + /* This should never happen (TM) */ + ERROR("Unable to get element %zd of %zd", i, nleases); + goto cleanup; + } + + lease_name = virJSONValueObjectGetString(lease, "hostname"); + + if (STRNEQ_NULLABLE(name, lease_name)) + continue; + + DEBUG("Found record for %s", lease_name); + *found = true; + + if (!(ipAddr = virJSONValueObjectGetString(lease, "ip-address"))) { + ERROR("ip-address field missing for %s", name); + goto cleanup; + } + + DEBUG("IP address: %s", ipAddr); + + if (virSocketAddrParse(&sa, ipAddr, AF_UNSPEC) < 0) { + ERROR("Unable to parse %s", ipAddr); + goto cleanup; + } + + family = VIR_SOCKET_ADDR_FAMILY(&sa); + if (af != AF_UNSPEC && af != family) { + DEBUG("Skipping address which family is %d, %d requested", family, af); + continue; + } + + if (VIR_REALLOC_N_QUIET(tmpAddress, ntmpAddress + 1) < 0) { + ERROR("Out of memory"); + goto cleanup; + } + + tmpAddress[ntmpAddress].af = family; + memcpy(tmpAddress[ntmpAddress].addr, + (family == AF_INET ? + (void *) &sa.data.inet4.sin_addr.s_addr : + (void *) &sa.data.inet6.sin6_addr.s6_addr), + FAMILY_ADDRESS_SIZE(family)); + ntmpAddress++; + } + + *address = tmpAddress; + *naddress = ntmpAddress; + tmpAddress = NULL; + ntmpAddress = 0; + + ret = 0; + + cleanup: + *errnop = errno; + VIR_FREE(tmpAddress); + virJSONValueFree(leases_array); + if (dir) + closedir(dir); + return ret; +} + + +enum nss_status +_nss_libvirt_gethostbyname_r(const char *name, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop) +{ + int af = ((_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET); + + return _nss_libvirt_gethostbyname3_r(name, af, result, buffer, buflen, + errnop, herrnop, NULL, NULL); +} + +enum nss_status +_nss_libvirt_gethostbyname2_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop) +{ + return _nss_libvirt_gethostbyname3_r(name, af, result, buffer, buflen, + errnop, herrnop, NULL, NULL); +} + +enum nss_status +_nss_libvirt_gethostbyname3_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop, int32_t *ttlp, char **canonp) +{ + enum nss_status ret = NSS_STATUS_UNAVAIL; + char *r_name, **r_aliases, *r_addr, **r_addr_list; + leaseAddress *addr = NULL; + size_t naddr, i; + bool found = false; + size_t nameLen, need, idx; + 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 */ + need = ALIGN(nameLen + 1) + naddr * ALIGN(alen) + (naddr + 2) * sizeof(char*); + + 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); + idx = ALIGN(nameLen + 1); + + /* Second, create aliases array */ + r_aliases = (char **) buffer + idx; + r_aliases[0] = NULL; + idx += sizeof(char*); + + /* Third, append address */ + r_addr = buffer + idx; + for (i = 0; i < naddr; i++) + memcpy(r_addr + i * ALIGN(alen), addr[i].addr, alen); + idx += naddr * ALIGN(alen); + + /* Third, append address pointer array */ + r_addr_list = (char **) buffer + idx; + for (i = 0; i < naddr; i++) + r_addr_list[i] = r_addr + i * ALIGN(alen); + 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: + VIR_FREE(addr); + return ret; } diff --git a/tools/nss/libvirt_nss.h b/tools/nss/libvirt_nss.h index b54e5e361a..dd037f555c 100644 --- a/tools/nss/libvirt_nss.h +++ b/tools/nss/libvirt_nss.h @@ -32,5 +32,17 @@ # include # include -int blah(int c); +enum nss_status +_nss_libvirt_gethostbyname_r(const char *name, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop); + +enum nss_status +_nss_libvirt_gethostbyname2_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop); +enum nss_status +_nss_libvirt_gethostbyname3_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop, int32_t *ttlp, char **canonp); #endif /* __LIBVIRT_NSS_H__ */ diff --git a/tools/nss/libvirt_nss.syms b/tools/nss/libvirt_nss.syms index 3246213db0..b88b8be3a0 100644 --- a/tools/nss/libvirt_nss.syms +++ b/tools/nss/libvirt_nss.syms @@ -4,6 +4,8 @@ { global: - blah; + _nss_libvirt_gethostbyname_r; + _nss_libvirt_gethostbyname2_r; + _nss_libvirt_gethostbyname3_r; local: *; };