summaryrefslogtreecommitdiffstats
path: root/src/sss_client/nss_hosts.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sss_client/nss_hosts.c')
-rw-r--r--src/sss_client/nss_hosts.c591
1 files changed, 591 insertions, 0 deletions
diff --git a/src/sss_client/nss_hosts.c b/src/sss_client/nss_hosts.c
new file mode 100644
index 0000000..81017bc
--- /dev/null
+++ b/src/sss_client/nss_hosts.c
@@ -0,0 +1,591 @@
+/*
+ SSSD
+
+ Authors:
+ Samuel Cabrero <scabrero@suse.com>
+
+ Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <nss.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include "sss_cli.h"
+
+static
+#ifdef HAVE_PTHREAD_EXT
+__thread
+#endif
+struct sss_nss_gethostent_data {
+ size_t len;
+ size_t ptr;
+ uint8_t *data;
+} sss_nss_gethostent_data;
+
+static void
+sss_nss_gethostent_data_clean(void)
+{
+ if (sss_nss_gethostent_data.data != NULL) {
+ free(sss_nss_gethostent_data.data);
+ sss_nss_gethostent_data.data = NULL;
+ }
+ sss_nss_gethostent_data.len = 0;
+ sss_nss_gethostent_data.ptr = 0;
+}
+
+/* GETHOSTBYNAME2 Request
+ *
+ * 0-X: One zero-terminated string (name)
+ *
+ * GETHOSTBYADDR Request:
+ * 0-3: 32-bit unsigned address family
+ * 4-7: 32-bit unsigned address length
+ * 8-X: binary address
+ *
+ * Replies:
+ * 0-3: 32-bit unsigned number of results
+ * 4-7: 32-bit unsigned (reserved/padding)
+ * 7-X: Result data (blocks equal to number of results)
+ *
+ * Result data:
+ * 0-3: 32-bit unsigned number of aliases
+ * 4-7: 32-bit unsigned number of addresses
+ * 8-X: sequence of zero-terminated strings
+ * (name, zero or more aliases, zero or more addresses)
+ */
+
+struct sss_nss_host_rep {
+ struct hostent *result;
+ char *buffer;
+ size_t buflen;
+};
+
+#define HOST_METADATA_COUNT 8
+
+static errno_t
+sss_nss_gethost_readrep(struct sss_nss_host_rep *sr,
+ uint8_t *buf, size_t *len, int af)
+{
+ errno_t ret;
+ uint32_t num_aliases;
+ uint32_t num_addresses;
+ const char *sbuf;
+ size_t i, a, l, slen, dlen, pad, ptmem, alen;
+
+ if (af != AF_INET && af != AF_INET6) {
+ return EBADMSG;
+ }
+
+ /* Buffer must contain two 32-bit integers,
+ * at least one character and null-terminator
+ * for the name, at least a null-terminator for
+ * the aliases and a null-terminator for the
+ * addresses.
+ */
+ if (*len < 12) {
+ /* not enough data, bad packet */
+ return EBADMSG;
+ }
+
+ /* Get the number of aliases */
+ SAFEALIGN_COPY_UINT32(&num_aliases, buf, NULL);
+
+ /* Get the number of addresses */
+ SAFEALIGN_COPY_UINT32(&num_addresses, buf + sizeof(uint32_t), NULL);
+
+ sbuf = (char *)&buf[2 * sizeof(uint32_t)];
+ slen = *len - (2 * sizeof(uint32_t));
+ dlen = sr->buflen;
+
+ i = 0;
+ sr->result->h_name = &(sr->buffer[i]);
+ ret = sss_readrep_copy_string(sbuf, &i,
+ &slen, &dlen,
+ &sr->result->h_name,
+ NULL);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ /* Copy the aliases */
+ pad = PADDING_SIZE(i, char *);
+ sr->result->h_aliases = DISCARD_ALIGN(&(sr->buffer[i+pad]), char **);
+
+ ptmem = (sizeof(char *) * (num_aliases + 1)) + pad;
+ if (ptmem > dlen) {
+ /* Not ENOMEM, ERANGE is what glibc looks for */
+ return ERANGE;
+ }
+
+ dlen -= ptmem;
+ ptmem += i;
+
+ /* Terminate array */
+ sr->result->h_aliases[num_aliases] = NULL;
+
+ for (l = 0; l < num_aliases; l++) {
+ sr->result->h_aliases[l] = &(sr->buffer[ptmem]);
+ ret = sss_readrep_copy_string(sbuf, &i,
+ &slen, &dlen,
+ &sr->result->h_aliases[l],
+ &alen);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ptmem += alen + 1;
+ }
+
+ /* Copy the addresses */
+ pad = PADDING_SIZE(ptmem, char *);
+ sr->result->h_addr_list =
+ DISCARD_ALIGN(&(sr->buffer[ptmem + pad]), char **);
+
+ ptmem += (sizeof(char *) * (num_addresses + 1)) + pad;
+ if (ptmem > dlen) {
+ /* Not ENOMEM, ERANGE is what glibc looks for */
+ return ERANGE;
+ }
+
+ dlen -= (sizeof(char *) * (num_addresses + 1)) + pad;
+
+ /* Initialize array, can return less address than num_addresses depending
+ * on requested address family */
+ for (a = 0; a < num_addresses + 1; a++) {
+ sr->result->h_addr_list[a] = NULL;
+ }
+
+ for (a = 0, l = 0; l < num_addresses; l++) {
+ /* Can be optimized, but ensure we can fit an IPv6 for now */
+ if (dlen < IN6ADDRSZ) {
+ return ERANGE;
+ }
+
+ sr->result->h_addr_list[a] = &(sr->buffer[ptmem]);
+
+ if (af == AF_INET &&
+ inet_pton(AF_INET, &sbuf[i], &(sr->buffer[ptmem]))) {
+ sr->result->h_addrtype = AF_INET;
+ sr->result->h_length = INADDRSZ;
+ dlen -= INADDRSZ;
+ ptmem += INADDRSZ;
+ a++;
+ } else if (af == AF_INET6 &&
+ inet_pton(AF_INET6, &sbuf[i], &(sr->buffer[ptmem]))) {
+ sr->result->h_addrtype = AF_INET6;
+ sr->result->h_length = IN6ADDRSZ;
+ dlen -= IN6ADDRSZ;
+ ptmem += IN6ADDRSZ;
+ a++;
+ } else {
+ /* Skip illegal address */
+ sr->result->h_addr_list[a] = NULL;
+ }
+
+ i += strlen(&sbuf[i]) + 1;
+ }
+
+ *len = slen - i;
+
+ return EOK;
+}
+
+static enum nss_status
+internal_gethostbyname2_r(const char *name, int af,
+ struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_host_rep hostrep;
+ size_t name_len;
+ uint8_t *repbuf;
+ size_t replen, len;
+ uint32_t num_results;
+ enum nss_status nret;
+ int ret;
+
+ if (af != AF_INET && af != AF_INET6) {
+ *errnop = EAFNOSUPPORT;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (buffer == NULL || buflen == 0) {
+ *errnop = ERANGE;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ ret = sss_strnlen(name, SSS_NAME_MAX, &name_len);
+ if (ret != 0) {
+ *errnop = EINVAL;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ rd.len = name_len + 1;
+ rd.data = name;
+
+ sss_nss_lock();
+
+ nret = sss_nss_make_request(SSS_NSS_GETHOSTBYNAME2, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ *h_errnop = NO_RECOVERY;
+ goto out;
+ }
+
+ hostrep.result = result;
+ hostrep.buffer = buffer;
+ hostrep.buflen = buflen;
+
+ /* Get number of results from repbuf. */
+ SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
+
+ /* No results if not found */
+ if (num_results == 0) {
+ free(repbuf);
+ nret = NSS_STATUS_NOTFOUND;
+ *h_errnop = HOST_NOT_FOUND;
+ goto out;
+ }
+
+ /* Only 1 result is accepted for this function */
+ if (num_results != 1) {
+ free(repbuf);
+ *errnop = EBADMSG;
+ *h_errnop = NETDB_INTERNAL;
+ nret = NSS_STATUS_TRYAGAIN;
+ goto out;
+ }
+
+ len = replen - HOST_METADATA_COUNT;
+ ret = sss_nss_gethost_readrep(&hostrep, repbuf + HOST_METADATA_COUNT,
+ &len, af);
+ free(repbuf);
+ if (ret) {
+ *errnop = ret;
+ nret = NSS_STATUS_TRYAGAIN;
+ *h_errnop = NETDB_INTERNAL;
+ goto out;
+ }
+
+ /* If host name is valid but does not have an IP address of the requested
+ * address family return the correct error. */
+ if (result->h_addr_list[0] == NULL) {
+ *h_errnop = NO_DATA;
+ nret = NSS_STATUS_TRYAGAIN;
+ goto out;
+ }
+
+ nret = NSS_STATUS_SUCCESS;
+
+out:
+ sss_nss_unlock();
+
+ return nret;
+}
+
+enum nss_status
+_nss_sss_gethostbyname2_r(const char *name, int af,
+ struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+{
+ return internal_gethostbyname2_r(name, af, result, buffer, buflen,
+ errnop, h_errnop);
+}
+
+enum nss_status
+_nss_sss_gethostbyname_r(const char *name,
+ struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+{
+ return internal_gethostbyname2_r(name, AF_INET, result, buffer, buflen,
+ errnop, h_errnop);
+}
+
+enum nss_status
+_nss_sss_gethostbyaddr_r(const void *addr, socklen_t addrlen,
+ int af, struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_host_rep hostrep;
+ uint8_t *repbuf;
+ uint8_t *data;
+ size_t replen, len;
+ uint32_t num_results;
+ enum nss_status nret;
+ int ret;
+ size_t data_len = 0;
+ size_t ctr = 0;
+
+ if (af != AF_INET && af != AF_INET6) {
+ *errnop = EAFNOSUPPORT;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (buffer == NULL || buflen == 0) {
+ *errnop = ERANGE;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ data_len = sizeof(uint32_t) + sizeof(socklen_t) + addrlen;
+ data = malloc(data_len);
+ if (data == NULL) {
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ /* Push AF */
+ SAFEALIGN_SETMEM_VALUE(data, af, uint32_t, &ctr);
+
+ /* Push LEN */
+ SAFEALIGN_SETMEM_VALUE(data + ctr, addrlen, socklen_t, &ctr);
+
+ /* Push ADDR */
+ SAFEALIGN_SETMEM_STRING(data + ctr, addr, addrlen, &ctr);
+
+ rd.data = data;
+ rd.len = data_len;
+
+ sss_nss_lock();
+
+ nret = sss_nss_make_request(SSS_NSS_GETHOSTBYADDR, &rd,
+ &repbuf, &replen, errnop);
+ free(data);
+ if (nret != NSS_STATUS_SUCCESS) {
+ *h_errnop = NO_RECOVERY;
+ goto out;
+ }
+
+ hostrep.result = result;
+ hostrep.buffer = buffer;
+ hostrep.buflen = buflen;
+
+ /* Get number of results from repbuf. */
+ SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
+
+ /* No results if not found */
+ if (num_results == 0) {
+ free(repbuf);
+ nret = NSS_STATUS_NOTFOUND;
+ *h_errnop = HOST_NOT_FOUND;
+ goto out;
+ }
+
+ /* Only 1 result is accepted for this function */
+ if (num_results != 1) {
+ free(repbuf);
+ *errnop = EBADMSG;
+ *h_errnop = NETDB_INTERNAL;
+ nret = NSS_STATUS_TRYAGAIN;
+ goto out;
+ }
+
+ len = replen - HOST_METADATA_COUNT;
+ ret = sss_nss_gethost_readrep(&hostrep, repbuf + HOST_METADATA_COUNT,
+ &len, af);
+ free(repbuf);
+ if (ret) {
+ *errnop = ret;
+ nret = NSS_STATUS_TRYAGAIN;
+ *h_errnop = NETDB_INTERNAL;
+ goto out;
+ }
+
+ /* If host name is valid but does not have an IP address of the requested
+ * address family return the correct error. */
+ if (result->h_addr_list[0] == NULL) {
+ *h_errnop = NO_DATA;
+ nret = NSS_STATUS_TRYAGAIN;
+ goto out;
+ }
+
+ nret = NSS_STATUS_SUCCESS;
+
+out:
+ sss_nss_unlock();
+
+ return nret;
+}
+
+static enum nss_status
+internal_gethostent_r(struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_host_rep pwrep;
+ uint8_t *repbuf;
+ size_t replen;
+ uint32_t num_results;
+ enum nss_status nret;
+ uint32_t num_entries;
+ int retval;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (buffer == NULL || buflen == 0) {
+ *errnop = ERANGE;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ /* if there are leftovers return the next one */
+ if (sss_nss_gethostent_data.data != NULL &&
+ sss_nss_gethostent_data.ptr < sss_nss_gethostent_data.len) {
+
+ repbuf = sss_nss_gethostent_data.data + sss_nss_gethostent_data.ptr;
+ replen = sss_nss_gethostent_data.len - sss_nss_gethostent_data.ptr;
+
+ pwrep.result = result;
+ pwrep.buffer = buffer;
+ pwrep.buflen = buflen;
+
+ retval = sss_nss_gethost_readrep(&pwrep, repbuf, &replen, AF_INET);
+ if (retval) {
+ *errnop = retval;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ /* advance buffer pointer */
+ sss_nss_gethostent_data.ptr = sss_nss_gethostent_data.len - replen;
+
+ /* If host name is valid but does not have an IP address of the
+ * requested address family return the correct error. */
+ if (result->h_addr_list[0] == NULL) {
+ *h_errnop = NO_DATA;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ *h_errnop = 0;
+
+ return NSS_STATUS_SUCCESS;
+ }
+
+ /* release memory if any */
+ sss_nss_gethostent_data_clean();
+
+ /* retrieve no more than SSS_NSS_MAX_ENTRIES at a time */
+ num_entries = SSS_NSS_MAX_ENTRIES;
+ rd.len = sizeof(uint32_t);
+ rd.data = &num_entries;
+
+ nret = sss_nss_make_request(SSS_NSS_GETHOSTENT, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ *h_errnop = NO_RECOVERY;
+ return nret;
+ }
+
+ /* Get number of results from repbuf */
+ SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
+
+ /* no results if not found */
+ if ((num_results == 0) || (replen - HOST_METADATA_COUNT == 0)) {
+ free(repbuf);
+ *h_errnop = HOST_NOT_FOUND;
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ sss_nss_gethostent_data.data = repbuf;
+ sss_nss_gethostent_data.len = replen;
+
+ /* skip metadata fields */
+ sss_nss_gethostent_data.ptr = HOST_METADATA_COUNT;
+
+ /* call again ourselves, this will return the first result */
+ return internal_gethostent_r(result, buffer, buflen, errnop, h_errnop);
+}
+
+enum nss_status
+_nss_sss_sethostent(void)
+{
+ enum nss_status nret;
+ int errnop;
+
+ sss_nss_lock();
+
+ /* make sure we do not have leftovers, and release memory */
+ sss_nss_gethostent_data_clean();
+
+ nret = sss_nss_make_request(SSS_NSS_SETHOSTENT,
+ NULL, NULL, NULL, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ errno = errnop;
+ }
+
+ sss_nss_unlock();
+
+ return nret;
+}
+
+enum nss_status
+_nss_sss_gethostent_r(struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+{
+ enum nss_status nret;
+
+ sss_nss_lock();
+ nret = internal_gethostent_r(result, buffer, buflen, errnop, h_errnop);
+ sss_nss_unlock();
+
+ return nret;
+}
+
+enum nss_status
+_nss_sss_endhostent(void)
+{
+ enum nss_status nret;
+ int errnop;
+ int saved_errno = errno;
+
+ sss_nss_lock();
+
+ /* make sure we do not have leftovers, and release memory */
+ sss_nss_gethostent_data_clean();
+
+ nret = sss_nss_make_request(SSS_NSS_ENDHOSTENT,
+ NULL, NULL, NULL, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ errno = errnop;
+ } else {
+ errno = saved_errno;
+ }
+
+ sss_nss_unlock();
+ return nret;
+}