diff options
Diffstat (limited to 'src/sss_client/nss_hosts.c')
-rw-r--r-- | src/sss_client/nss_hosts.c | 591 |
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; +} |