diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/sss_client/nss_group.c | |
parent | Initial commit. (diff) | |
download | sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip |
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/sss_client/nss_group.c')
-rw-r--r-- | src/sss_client/nss_group.c | 769 |
1 files changed, 769 insertions, 0 deletions
diff --git a/src/sss_client/nss_group.c b/src/sss_client/nss_group.c new file mode 100644 index 0000000..fcabf8c --- /dev/null +++ b/src/sss_client/nss_group.c @@ -0,0 +1,769 @@ +/* + * System Security Services Daemon. NSS client interface + * + * Copyright (C) Simo Sorce 2007 + * + * This program 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 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* GROUP database NSS interface */ + +#include "config.h" + +#include <nss.h> +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdbool.h> +#include "sss_cli.h" +#include "nss_mc.h" +#include "nss_common.h" + +static +#ifdef HAVE_PTHREAD_EXT +__thread +#endif +struct sss_nss_getgrent_data { + size_t len; + size_t ptr; + uint8_t *data; +} sss_nss_getgrent_data; + +static void sss_nss_getgrent_data_clean(void) +{ + if (sss_nss_getgrent_data.data != NULL) { + free(sss_nss_getgrent_data.data); + sss_nss_getgrent_data.data = NULL; + } + sss_nss_getgrent_data.len = 0; + sss_nss_getgrent_data.ptr = 0; +} + +enum sss_nss_gr_type { + GETGR_NONE, + GETGR_NAME, + GETGR_GID +}; + +static +#ifdef HAVE_PTHREAD_EXT +__thread +#endif +struct sss_nss_getgr_data { + enum sss_nss_gr_type type; + union { + char *grname; + gid_t gid; + } id; + + uint8_t *repbuf; + size_t replen; +} sss_nss_getgr_data; + +static void sss_nss_getgr_data_clean(bool freebuf) +{ + if (sss_nss_getgr_data.type == GETGR_NAME) { + free(sss_nss_getgr_data.id.grname); + } + if (freebuf) { + free(sss_nss_getgr_data.repbuf); + } + memset(&sss_nss_getgr_data, 0, sizeof(struct sss_nss_getgr_data)); +} + +static enum nss_status sss_nss_get_getgr_cache(const char *name, gid_t gid, + enum sss_nss_gr_type type, + uint8_t **repbuf, + size_t *replen, + int *errnop) +{ + bool freebuf = true; + enum nss_status status; + int ret = 0; + + if (sss_nss_getgr_data.type != type) { + status = NSS_STATUS_NOTFOUND; + goto done; + } + + switch (type) { + case GETGR_NAME: + if (name != NULL) { + ret = strcmp(name, sss_nss_getgr_data.id.grname); + } else { + ret = -1; + } + if (ret != 0) { + status = NSS_STATUS_NOTFOUND; + goto done; + } + break; + case GETGR_GID: + if (sss_nss_getgr_data.id.gid != gid) { + status = NSS_STATUS_NOTFOUND; + goto done; + } + break; + default: + status = NSS_STATUS_TRYAGAIN; + ret = EINVAL; + goto done; + } + + /* ok we have it, remove from cache and pass back to the caller */ + *repbuf = sss_nss_getgr_data.repbuf; + *replen = sss_nss_getgr_data.replen; + + /* prevent _clean() from freeing the buffer */ + freebuf = false; + status = NSS_STATUS_SUCCESS; + +done: + sss_nss_getgr_data_clean(freebuf); + *errnop = ret; + return status; +} + +/* this function always takes ownership of repbuf and NULLs it before + * returning */ +static void sss_nss_save_getgr_cache(const char *name, gid_t gid, + enum sss_nss_gr_type type, + uint8_t **repbuf, size_t replen) +{ + int ret = 0; + + sss_nss_getgr_data.type = type; + sss_nss_getgr_data.repbuf = *repbuf; + sss_nss_getgr_data.replen = replen; + + switch (type) { + case GETGR_NAME: + if (name == NULL) { + ret = EINVAL; + goto done; + } + sss_nss_getgr_data.id.grname = strdup(name); + if (!sss_nss_getgr_data.id.grname) { + ret = ENOMEM; + goto done; + } + break; + case GETGR_GID: + if (gid == 0) { + ret = EINVAL; + goto done; + } + sss_nss_getgr_data.id.gid = gid; + break; + default: + ret = EINVAL; + goto done; + } + +done: + if (ret) { + sss_nss_getgr_data_clean(true); + } + *repbuf = NULL; +} + +/* GETGRNAM Request: + * + * 0-X: string with name + * + * GERTGRGID Request: + * + * 0-7: 32bit number with gid + * + * INITGROUPS Request: + * + * 0-3: 32bit number with gid + * 4-7: 32bit unsigned with max num of entries + * + * Replies: + * + * 0-3: 32bit unsigned number of results + * 4-7: 32bit unsigned (reserved/padding) + * For each result (64bit padded?): + * 0-3: 32bit number gid + * 4-7: 32bit unsigned number of members + * 8-X: sequence of 0 terminated strings (name, passwd, mem..) + * + * FIXME: do we need to pad so that each result is 32 bit aligned? + */ + +int sss_nss_getgr_readrep(struct sss_nss_gr_rep *pr, + uint8_t *buf, size_t *len) +{ + errno_t ret; + size_t i, l, slen, ptmem, pad, dlen, glen; + char *sbuf; + uint32_t mem_num; + uint32_t c; + + if (*len < 11) { /* not enough space for data, bad packet */ + return EBADMSG; + } + + SAFEALIGN_COPY_UINT32(&c, buf, NULL); + pr->result->gr_gid = c; + SAFEALIGN_COPY_UINT32(&mem_num, buf+sizeof(uint32_t), NULL); + + sbuf = (char *)&buf[8]; + slen = *len - 8; + dlen = pr->buflen; + + pr->result->gr_name = &(pr->buffer[0]); + i = 0; + + ret = sss_readrep_copy_string(sbuf, &i, + &slen, &dlen, + &pr->result->gr_name, + NULL); + if (ret != EOK) return ret; + + pr->result->gr_passwd = &(pr->buffer[i]); + ret = sss_readrep_copy_string(sbuf, &i, + &slen, &dlen, + &pr->result->gr_passwd, + NULL); + if (ret != EOK) return ret; + + /* Make sure pr->buffer[i+pad] is aligned to sizeof(char *) */ + pad = PADDING_SIZE(i, char *); + + /* now members */ + pr->result->gr_mem = DISCARD_ALIGN(&(pr->buffer[i+pad]), char **); + + ptmem = (sizeof(char *) * (mem_num + 1)) + pad; + if (ptmem > dlen) { + return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */ + } + dlen -= ptmem; + ptmem += i; + pr->result->gr_mem[mem_num] = NULL; /* terminate array */ + + for (l = 0; l < mem_num; l++) { + pr->result->gr_mem[l] = &(pr->buffer[ptmem]); + ret = sss_readrep_copy_string(sbuf, &i, + &slen, &dlen, + &pr->result->gr_mem[l], + &glen); + if (ret != EOK) return ret; + + ptmem += glen + 1; + } + + *len = slen -i; + return 0; +} + +/* INITGROUP Reply: + * + * 0-3: 32bit unsigned number of results + * 4-7: 32bit unsigned (reserved/padding) + * For each result: + * 0-4: 32bit number with gid + */ + + +enum nss_status _nss_sss_initgroups_dyn(const char *user, gid_t group, + long int *start, long int *size, + gid_t **groups, long int limit, + int *errnop) +{ + struct sss_cli_req_data rd; + uint8_t *repbuf; + size_t replen; + enum nss_status nret; + size_t buf_index = 0; + size_t user_len; + uint32_t num_ret; + long int l, max_ret; + int ret; + + ret = sss_strnlen(user, SSS_NAME_MAX, &user_len); + if (ret != 0) { + *errnop = EINVAL; + return NSS_STATUS_NOTFOUND; + } + + ret = sss_nss_mc_initgroups_dyn(user, user_len, group, start, size, + groups, limit); + switch (ret) { + case 0: + *errnop = 0; + return NSS_STATUS_SUCCESS; + case ERANGE: + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + case ENOENT: + /* fall through, we need to actively ask the parent + * if no entry is found */ + break; + default: + /* if using the mmapped cache failed, + * fall back to socket based comms */ + break; + } + + rd.len = user_len + 1; + rd.data = user; + + sss_nss_lock(); + + /* previous thread might already initialize entry in mmap cache */ + ret = sss_nss_mc_initgroups_dyn(user, user_len, group, start, size, + groups, limit); + switch (ret) { + case 0: + *errnop = 0; + nret = NSS_STATUS_SUCCESS; + goto out; + case ERANGE: + *errnop = ERANGE; + nret = NSS_STATUS_TRYAGAIN; + goto out; + case ENOENT: + /* fall through, we need to actively ask the parent + * if no entry is found */ + break; + default: + /* if using the mmapped cache failed, + * fall back to socket based comms */ + break; + } + + nret = sss_nss_make_request(SSS_NSS_INITGR, &rd, + &repbuf, &replen, errnop); + if (nret != NSS_STATUS_SUCCESS) { + goto out; + } + + /* no results if not found */ + SAFEALIGN_COPY_UINT32(&num_ret, repbuf, NULL); + if (num_ret == 0) { + free(repbuf); + nret = NSS_STATUS_NOTFOUND; + goto out; + } + max_ret = num_ret; + + /* check we have enough space in the buffer */ + if ((*size - *start) < num_ret) { + long int newsize; + gid_t *newgroups; + + newsize = *size + num_ret; + if ((limit > 0) && (newsize > limit)) { + newsize = limit; + max_ret = newsize - *start; + } + + newgroups = (gid_t *)realloc((*groups), newsize * sizeof(**groups)); + if (!newgroups) { + *errnop = ENOMEM; + free(repbuf); + nret = NSS_STATUS_TRYAGAIN; + goto out; + } + *groups = newgroups; + *size = newsize; + } + + /* Skip first two 32 bit values (number of results and + * reserved padding) */ + buf_index = 2 * sizeof(uint32_t); + + for (l = 0; l < max_ret; l++) { + SAFEALIGN_COPY_UINT32(&(*groups)[*start], repbuf + buf_index, + &buf_index); + *start += 1; + } + + free(repbuf); + nret = NSS_STATUS_SUCCESS; + +out: + sss_nss_unlock(); + return nret; +} + + +enum nss_status _nss_sss_getgrnam_r(const char *name, struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + struct sss_cli_req_data rd; + struct sss_nss_gr_rep grrep; + uint8_t *repbuf; + size_t replen, len, name_len; + uint32_t num_results; + enum nss_status nret; + int ret; + + /* Caught once glibc passing in buffer == 0x0 */ + if (!buffer || !buflen) { + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + } + + ret = sss_strnlen(name, SSS_NAME_MAX, &name_len); + if (ret != 0) { + *errnop = EINVAL; + return NSS_STATUS_NOTFOUND; + } + + ret = sss_nss_mc_getgrnam(name, name_len, result, buffer, buflen); + switch (ret) { + case 0: + *errnop = 0; + return NSS_STATUS_SUCCESS; + case ERANGE: + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + case ENOENT: + /* fall through, we need to actively ask the parent + * if no entry is found */ + break; + default: + /* if using the mmapped cache failed, + * fall back to socket based comms */ + break; + } + + rd.len = name_len + 1; + rd.data = name; + + sss_nss_lock(); + + /* previous thread might already initialize entry in mmap cache */ + ret = sss_nss_mc_getgrnam(name, name_len, result, buffer, buflen); + switch (ret) { + case 0: + *errnop = 0; + nret = NSS_STATUS_SUCCESS; + goto out; + case ERANGE: + *errnop = ERANGE; + nret = NSS_STATUS_TRYAGAIN; + goto out; + case ENOENT: + /* fall through, we need to actively ask the parent + * if no entry is found */ + break; + default: + /* if using the mmapped cache failed, + * fall back to socket based comms */ + break; + } + + nret = sss_nss_get_getgr_cache(name, 0, GETGR_NAME, + &repbuf, &replen, errnop); + if (nret == NSS_STATUS_NOTFOUND) { + nret = sss_nss_make_request(SSS_NSS_GETGRNAM, &rd, + &repbuf, &replen, errnop); + } + if (nret != NSS_STATUS_SUCCESS) { + goto out; + } + + grrep.result = result; + grrep.buffer = buffer; + grrep.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; + goto out; + } + + /* only 1 result is accepted for this function */ + if (num_results != 1) { + *errnop = EBADMSG; + free(repbuf); + nret = NSS_STATUS_TRYAGAIN; + goto out; + } + + len = replen - 8; + ret = sss_nss_getgr_readrep(&grrep, repbuf+8, &len); + if (ret == ERANGE) { + sss_nss_save_getgr_cache(name, 0, GETGR_NAME, &repbuf, replen); + } else { + free(repbuf); + } + if (ret) { + *errnop = ret; + nret = NSS_STATUS_TRYAGAIN; + goto out; + } + + nret = NSS_STATUS_SUCCESS; + +out: + sss_nss_unlock(); + return nret; +} + +enum nss_status _nss_sss_getgrgid_r(gid_t gid, struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + struct sss_cli_req_data rd; + struct sss_nss_gr_rep grrep; + uint8_t *repbuf; + size_t replen, len; + uint32_t num_results; + enum nss_status nret; + uint32_t group_gid; + int ret; + + /* Caught once glibc passing in buffer == 0x0 */ + if (!buffer || !buflen) { + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + } + + ret = sss_nss_mc_getgrgid(gid, result, buffer, buflen); + switch (ret) { + case 0: + *errnop = 0; + return NSS_STATUS_SUCCESS; + case ERANGE: + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + case ENOENT: + /* fall through, we need to actively ask the parent + * if no entry is found */ + break; + default: + /* if using the mmapped cache failed, + * fall back to socket based comms */ + break; + } + + group_gid = gid; + rd.len = sizeof(uint32_t); + rd.data = &group_gid; + + sss_nss_lock(); + + /* previous thread might already initialize entry in mmap cache */ + ret = sss_nss_mc_getgrgid(gid, result, buffer, buflen); + switch (ret) { + case 0: + *errnop = 0; + nret = NSS_STATUS_SUCCESS; + goto out; + case ERANGE: + *errnop = ERANGE; + nret = NSS_STATUS_TRYAGAIN; + goto out; + case ENOENT: + /* fall through, we need to actively ask the parent + * if no entry is found */ + break; + default: + /* if using the mmapped cache failed, + * fall back to socket based comms */ + break; + } + + nret = sss_nss_get_getgr_cache(NULL, gid, GETGR_GID, + &repbuf, &replen, errnop); + if (nret == NSS_STATUS_NOTFOUND) { + nret = sss_nss_make_request(SSS_NSS_GETGRGID, &rd, + &repbuf, &replen, errnop); + } + if (nret != NSS_STATUS_SUCCESS) { + goto out; + } + + grrep.result = result; + grrep.buffer = buffer; + grrep.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; + goto out; + } + + /* only 1 result is accepted for this function */ + if (num_results != 1) { + *errnop = EBADMSG; + free(repbuf); + nret = NSS_STATUS_TRYAGAIN; + goto out; + } + + len = replen - 8; + ret = sss_nss_getgr_readrep(&grrep, repbuf+8, &len); + if (ret == ERANGE) { + sss_nss_save_getgr_cache(NULL, gid, GETGR_GID, &repbuf, replen); + } else { + free(repbuf); + } + if (ret) { + *errnop = ret; + nret = NSS_STATUS_TRYAGAIN; + goto out; + } + + nret = NSS_STATUS_SUCCESS; + +out: + sss_nss_unlock(); + return nret; +} + +enum nss_status _nss_sss_setgrent(void) +{ + enum nss_status nret; + int errnop; + + sss_nss_lock(); + + /* make sure we do not have leftovers, and release memory */ + sss_nss_getgrent_data_clean(); + + nret = sss_nss_make_request(SSS_NSS_SETGRENT, + NULL, NULL, NULL, &errnop); + if (nret != NSS_STATUS_SUCCESS) { + errno = errnop; + } + + sss_nss_unlock(); + return nret; +} + +static enum nss_status internal_getgrent_r(struct group *result, + char *buffer, size_t buflen, + int *errnop) +{ + struct sss_cli_req_data rd; + struct sss_nss_gr_rep grrep; + uint8_t *repbuf; + size_t replen; + uint32_t num_results; + enum nss_status nret; + uint32_t num_entries; + int ret; + + /* Caught once glibc passing in buffer == 0x0 */ + if (!buffer || !buflen) { + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + } + + /* if there are leftovers return the next one */ + if (sss_nss_getgrent_data.data != NULL && + sss_nss_getgrent_data.ptr < sss_nss_getgrent_data.len) { + + repbuf = (uint8_t *)sss_nss_getgrent_data.data + + sss_nss_getgrent_data.ptr; + replen = sss_nss_getgrent_data.len - + sss_nss_getgrent_data.ptr; + + grrep.result = result; + grrep.buffer = buffer; + grrep.buflen = buflen; + + ret = sss_nss_getgr_readrep(&grrep, repbuf, &replen); + if (ret) { + *errnop = ret; + return NSS_STATUS_TRYAGAIN; + } + + /* advance buffer pointer */ + sss_nss_getgrent_data.ptr = sss_nss_getgrent_data.len - replen; + + return NSS_STATUS_SUCCESS; + } + + /* release memory if any */ + sss_nss_getgrent_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_GETGRENT, &rd, + &repbuf, &replen, errnop); + if (nret != NSS_STATUS_SUCCESS) { + 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 - 8 == 0)) { + free(repbuf); + return NSS_STATUS_NOTFOUND; + } + + sss_nss_getgrent_data.data = repbuf; + sss_nss_getgrent_data.len = replen; + sss_nss_getgrent_data.ptr = 8; /* skip metadata fields */ + + /* call again ourselves, this will return the first result */ + return internal_getgrent_r(result, buffer, buflen, errnop); +} + +enum nss_status _nss_sss_getgrent_r(struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + enum nss_status nret; + + sss_nss_lock(); + nret = internal_getgrent_r(result, buffer, buflen, errnop); + sss_nss_unlock(); + + return nret; +} + +enum nss_status _nss_sss_endgrent(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_getgrent_data_clean(); + + nret = sss_nss_make_request(SSS_NSS_ENDGRENT, + NULL, NULL, NULL, &errnop); + if (nret != NSS_STATUS_SUCCESS) { + errno = errnop; + } else { + errno = saved_errno; + } + + sss_nss_unlock(); + return nret; +} |