/* SSSD Authors: Samuel Cabrero Copyright (C) 2020 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 . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "sss_cli.h" static #ifdef HAVE_PTHREAD_EXT __thread #endif struct sss_nss_getnetent_data { size_t len; size_t ptr; uint8_t *data; } sss_nss_getnetent_data; static void sss_nss_getnetent_data_clean(void) { if (sss_nss_getnetent_data.data != NULL) { free(sss_nss_getnetent_data.data); sss_nss_getnetent_data.data = NULL; } sss_nss_getnetent_data.len = 0; sss_nss_getnetent_data.ptr = 0; } /* GETNETBYNAME Request * * 0-X: One zero-terminated string (name) * * GETNETBYADDR 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-X: sequence of zero-terminated strings * (name, address, zero or more aliases) */ struct sss_nss_net_rep { struct netent *result; char *buffer; size_t buflen; }; #define IP_NETWORK_METADATA_COUNT 8 static errno_t sss_nss_net_readrep(struct sss_nss_net_rep *sr, uint8_t *buf, size_t *len, int type) { errno_t ret; char *net_addrstr; uint32_t net_addr; uint32_t num_aliases; const char *sbuf; size_t i, l, slen, dlen, pad, ptmem, alen; /* Only AF_INET is supported */ if (type != AF_INET) { return EBADMSG; } /* Buffer must contain one 32-bit integer, * at least one character and null terminator * for the name, at least one character and a * null terminator for the address and a null * terminator for the aliases. */ if (*len < 9) { /* not enough data, bad packet */ return EBADMSG; } /* Get the number of aliases */ SAFEALIGN_COPY_UINT32(&num_aliases, buf, NULL); sbuf = (char *)&buf[sizeof(uint32_t)]; slen = *len - (sizeof(uint32_t)); dlen = sr->buflen; i = 0; /* Copy the name */ sr->result->n_name = &(sr->buffer[i]); ret = sss_readrep_copy_string(sbuf, &i, &slen, &dlen, &sr->result->n_name, NULL); if (ret != EOK) { return ret; } /* Copy the address */ net_addrstr = &(sr->buffer[i]); ret = sss_readrep_copy_string(sbuf, &i, &slen, &dlen, &net_addrstr, NULL); if (ret != EOK) { return ret; } if (inet_pton(AF_INET, net_addrstr, &net_addr)) { sr->result->n_addrtype = AF_INET; } else { /* Skip illegal address */ return EAFNOSUPPORT; } /* result->n_net must be represented in host byte order */ sr->result->n_net = ntohl(net_addr); /* Copy the aliases */ pad = PADDING_SIZE(i, char *); sr->result->n_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->n_aliases[num_aliases] = NULL; for (l = 0; l < num_aliases; l++) { sr->result->n_aliases[l] = &(sr->buffer[ptmem]); ret = sss_readrep_copy_string(sbuf, &i, &slen, &dlen, &sr->result->n_aliases[l], &alen); if (ret != EOK) { return ret; } ptmem += alen + 1; } *len = slen - i; return EOK; } enum nss_status _nss_sss_getnetbyname_r(const char *name, struct netent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { struct sss_cli_req_data rd; struct sss_nss_net_rep netrep; size_t name_len; uint8_t *repbuf; size_t replen, len; uint32_t num_results; enum nss_status nret; int ret; /* 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_GETNETBYNAME, &rd, &repbuf, &replen, errnop); if (nret != NSS_STATUS_SUCCESS) { *h_errnop = NETDB_INTERNAL; goto out; } netrep.result = result; netrep.buffer = buffer; netrep.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 - IP_NETWORK_METADATA_COUNT; ret = sss_nss_net_readrep(&netrep, repbuf + IP_NETWORK_METADATA_COUNT, &len, AF_INET); free(repbuf); /* If network name is valid but does not have an IP address of the * requested address family return the correct error */ if (ret == EAFNOSUPPORT) { *h_errnop = NO_DATA; nret = NSS_STATUS_TRYAGAIN; goto out; } else if (ret) { *errnop = ret; nret = NSS_STATUS_TRYAGAIN; *h_errnop = NETDB_INTERNAL; goto out; } nret = NSS_STATUS_SUCCESS; out: sss_nss_unlock(); return nret; } enum nss_status _nss_sss_getnetbyaddr_r(uint32_t addr, int type, struct netent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { struct sss_cli_req_data rd; struct sss_nss_net_rep netrep; 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; socklen_t addrlen; /* addr is in host byte order, but nss_protocol_parse_addr and inet_ntop * expects the buffer in network byte order */ addr = htonl(addr); if (type == AF_UNSPEC) { char addrbuf[INET_ADDRSTRLEN]; /* Try to parse to IPv4 */ if (inet_ntop(AF_INET, &addr, addrbuf, INET_ADDRSTRLEN)) { type = AF_INET; } } if (type != AF_INET) { *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; } addrlen = INADDRSZ; 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 type */ SAFEALIGN_SETMEM_VALUE(data, type, int, &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_GETNETBYADDR, &rd, &repbuf, &replen, errnop); free(data); if (nret != NSS_STATUS_SUCCESS) { *h_errnop = NETDB_INTERNAL; goto out; } netrep.result = result; netrep.buffer = buffer; netrep.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 - IP_NETWORK_METADATA_COUNT; ret = sss_nss_net_readrep(&netrep, repbuf + IP_NETWORK_METADATA_COUNT, &len, type); free(repbuf); /* If network name is valid but does not have an IP address of the * requested address family return the correct error */ if (ret == EAFNOSUPPORT) { *h_errnop = NO_DATA; nret = NSS_STATUS_TRYAGAIN; goto out; } else if (ret) { *errnop = ret; nret = NSS_STATUS_TRYAGAIN; *h_errnop = NETDB_INTERNAL; goto out; } nret = NSS_STATUS_SUCCESS; out: sss_nss_unlock(); return nret; } static enum nss_status internal_getnetent_r(struct netent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { struct sss_cli_req_data rd; struct sss_nss_net_rep netrep; 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_getnetent_data.data != NULL && sss_nss_getnetent_data.ptr < sss_nss_getnetent_data.len) { repbuf = sss_nss_getnetent_data.data + sss_nss_getnetent_data.ptr; replen = sss_nss_getnetent_data.len - sss_nss_getnetent_data.ptr; netrep.result = result; netrep.buffer = buffer; netrep.buflen = buflen; retval = sss_nss_net_readrep(&netrep, repbuf, &replen, AF_INET); /* If net name is valid but does not have an IP address of the * requested address family return the correct error. */ if (retval == EAFNOSUPPORT) { *h_errnop = NO_DATA; return NSS_STATUS_TRYAGAIN; } else if (retval) { *errnop = retval; *h_errnop = NETDB_INTERNAL; return NSS_STATUS_TRYAGAIN; } /* advance buffer pointer */ sss_nss_getnetent_data.ptr = sss_nss_getnetent_data.len - replen; *h_errnop = 0; return NSS_STATUS_SUCCESS; } /* release memory if any */ sss_nss_getnetent_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_GETNETENT, &rd, &repbuf, &replen, errnop); if (nret != NSS_STATUS_SUCCESS) { *h_errnop = NETDB_INTERNAL; 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 - IP_NETWORK_METADATA_COUNT == 0)) { free(repbuf); *h_errnop = HOST_NOT_FOUND; return NSS_STATUS_NOTFOUND; } sss_nss_getnetent_data.data = repbuf; sss_nss_getnetent_data.len = replen; /* skip metadata fields */ sss_nss_getnetent_data.ptr = IP_NETWORK_METADATA_COUNT; /* call again ourselves, this will return the first result */ return internal_getnetent_r(result, buffer, buflen, errnop, h_errnop); } enum nss_status _nss_sss_setnetent(void) { enum nss_status nret; int errnop; sss_nss_lock(); /* make sure we do not have leftovers, and release memory */ sss_nss_getnetent_data_clean(); nret = sss_nss_make_request(SSS_NSS_SETNETENT, NULL, NULL, NULL, &errnop); if (nret != NSS_STATUS_SUCCESS) { errno = errnop; } sss_nss_unlock(); return nret; } enum nss_status _nss_sss_getnetent_r(struct netent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { enum nss_status nret; sss_nss_lock(); nret = internal_getnetent_r(result, buffer, buflen, errnop, h_errnop); sss_nss_unlock(); return nret; } enum nss_status _nss_sss_endnetent(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_getnetent_data_clean(); nret = sss_nss_make_request(SSS_NSS_ENDNETENT, NULL, NULL, NULL, &errnop); if (nret != NSS_STATUS_SUCCESS) { errno = errnop; } else { errno = saved_errno; } sss_nss_unlock(); return nret; }