/* SSSD Extended NSS Responder Interface Authors: Sumit Bose Copyright (C) 2017 Red Hat 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 #include #include /* for MIN() */ #include "sss_client/sss_cli.h" #include "sss_client/nss_mc.h" #include "sss_client/nss_common.h" #include "sss_client/idmap/sss_nss_idmap.h" #include "sss_client/idmap/sss_nss_idmap_private.h" #ifndef discard_const #define discard_const(ptr) ((void *)((uintptr_t)(ptr))) #endif struct sss_nss_initgr_rep { gid_t *groups; long int *ngroups; long int *start; }; struct nss_input { union { const char *name; uid_t uid; gid_t gid; } input; struct sss_cli_req_data rd; enum sss_cli_command cmd; union { struct sss_nss_pw_rep pwrep; struct sss_nss_gr_rep grrep; struct sss_nss_initgr_rep initgrrep; } result; }; static errno_t sss_nss_mc_get(struct nss_input *inp) { switch(inp->cmd) { case SSS_NSS_GETPWNAM: case SSS_NSS_GETPWNAM_EX: return sss_nss_mc_getpwnam(inp->input.name, strlen(inp->input.name), inp->result.pwrep.result, inp->result.pwrep.buffer, inp->result.pwrep.buflen); break; case SSS_NSS_GETPWUID: case SSS_NSS_GETPWUID_EX: return sss_nss_mc_getpwuid(inp->input.uid, inp->result.pwrep.result, inp->result.pwrep.buffer, inp->result.pwrep.buflen); break; case SSS_NSS_GETGRNAM: case SSS_NSS_GETGRNAM_EX: return sss_nss_mc_getgrnam(inp->input.name, strlen(inp->input.name), inp->result.grrep.result, inp->result.grrep.buffer, inp->result.grrep.buflen); break; case SSS_NSS_GETGRGID: case SSS_NSS_GETGRGID_EX: return sss_nss_mc_getgrgid(inp->input.gid, inp->result.grrep.result, inp->result.grrep.buffer, inp->result.grrep.buflen); break; case SSS_NSS_INITGR: case SSS_NSS_INITGR_EX: return sss_nss_mc_initgroups_dyn(inp->input.name, strlen(inp->input.name), -1 /* currently ignored */, inp->result.initgrrep.start, inp->result.initgrrep.ngroups, &(inp->result.initgrrep.groups), /* no limit so that needed size can * be returned properly */ -1); break; default: return EINVAL; } } static int check_flags(struct nss_input *inp, uint32_t flags, bool *skip_mc, bool *skip_data) { bool no_data = false; /* SSS_NSS_EX_FLAG_NO_CACHE and SSS_NSS_EX_FLAG_INVALIDATE_CACHE are * mutually exclusive */ if ((flags & SSS_NSS_EX_FLAG_NO_CACHE) != 0 && (flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) { return EINVAL; } *skip_mc = false; if ((flags & SSS_NSS_EX_FLAG_NO_CACHE) != 0 || (flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) { *skip_mc = true; } switch(inp->cmd) { case SSS_NSS_GETPWNAM: case SSS_NSS_GETPWNAM_EX: case SSS_NSS_GETPWUID: case SSS_NSS_GETPWUID_EX: if (inp->result.pwrep.buffer == NULL || inp->result.pwrep.buflen == 0) { no_data = true; } break; case SSS_NSS_GETGRNAM: case SSS_NSS_GETGRNAM_EX: case SSS_NSS_GETGRGID: case SSS_NSS_GETGRGID_EX: if (inp->result.grrep.buffer == NULL || inp->result.grrep.buflen == 0) { no_data = true; } break; case SSS_NSS_INITGR: case SSS_NSS_INITGR_EX: if (inp->result.initgrrep.ngroups == 0 || inp->result.initgrrep.groups == NULL) { return EINVAL; } break; default: return EINVAL; } *skip_data = false; /* Allow empty buffer with SSS_NSS_EX_FLAG_INVALIDATE_CACHE */ if (no_data) { if ((flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) { *skip_data = true; } else { return ERANGE; } } return 0; } static int sss_get_ex(struct nss_input *inp, uint32_t flags, unsigned int timeout) { uint8_t *repbuf = NULL; size_t replen; size_t len; uint32_t num_results; int ret; int time_left; int errnop; size_t c; gid_t *new_groups; size_t idx; bool skip_mc = false; bool skip_data = false; ret = check_flags(inp, flags, &skip_mc, &skip_data); if (ret != 0) { return ret; } if (!skip_mc && !skip_data) { ret = sss_nss_mc_get(inp); switch (ret) { case 0: return 0; case ERANGE: return ERANGE; 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; } } ret = sss_nss_timedlock(timeout, &time_left); if (ret != 0) { return ret; } if (!skip_mc && !skip_data) { /* previous thread might already initialize entry in mmap cache */ ret = sss_nss_mc_get(inp); switch (ret) { case 0: ret = 0; goto out; case ERANGE: ret = ERANGE; 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; } } ret = sss_nss_make_request_timeout(inp->cmd, &inp->rd, time_left, &repbuf, &replen, &errnop); if (ret != NSS_STATUS_SUCCESS) { ret = errnop != 0 ? errnop : EIO; goto out; } /* Get number of results from repbuf. */ SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL); /* no results if not found, INITGR requests are handled separately */ if (num_results == 0 && inp->cmd != SSS_NSS_INITGR && inp->cmd != SSS_NSS_INITGR_EX) { ret = ENOENT; goto out; } if (skip_data) { /* No data requested, just return the return code */ ret = 0; goto out; } if (inp->cmd == SSS_NSS_INITGR || inp->cmd == SSS_NSS_INITGR_EX) { if ((*(inp->result.initgrrep.ngroups) - *(inp->result.initgrrep.start)) < num_results) { new_groups = realloc(inp->result.initgrrep.groups, (num_results + *(inp->result.initgrrep.start)) * sizeof(gid_t)); if (new_groups == NULL) { ret = ENOMEM; goto out; } inp->result.initgrrep.groups = new_groups; } *(inp->result.initgrrep.ngroups) = num_results + *(inp->result.initgrrep.start); idx = 2 * sizeof(uint32_t); for (c = 0; c < num_results; c++) { SAFEALIGN_COPY_UINT32( &(inp->result.initgrrep.groups[*(inp->result.initgrrep.start)]), repbuf + idx, &idx); *(inp->result.initgrrep.start) += 1; } ret = 0; goto out; } /* only 1 result is accepted for this function */ if (num_results != 1) { ret = EBADMSG; goto out; } len = replen - 8; switch(inp->cmd) { case SSS_NSS_GETPWNAM: case SSS_NSS_GETPWUID: case SSS_NSS_GETPWNAM_EX: case SSS_NSS_GETPWUID_EX: ret = sss_nss_getpw_readrep(&(inp->result.pwrep), repbuf+8, &len); break; case SSS_NSS_GETGRNAM: case SSS_NSS_GETGRGID: case SSS_NSS_GETGRNAM_EX: case SSS_NSS_GETGRGID_EX: ret = sss_nss_getgr_readrep(&(inp->result.grrep), repbuf+8, &len); break; default: ret = EINVAL; } if (ret != 0) { goto out; } if (len == 0) { /* no extra data */ ret = 0; goto out; } out: free(repbuf); sss_nss_unlock(); return ret; } static int make_name_flag_req_data(const char *name, uint32_t flags, struct sss_cli_req_data *rd) { size_t len; size_t name_len; uint8_t *data; int ret; if (name == NULL) { return EINVAL; } ret = sss_strnlen(name, SSS_NAME_MAX, &name_len); if (ret != 0) { return ret; } name_len++; len = name_len + sizeof(uint32_t); data = malloc(len); if (data == NULL) { return ENOMEM; } memcpy(data, name, name_len); SAFEALIGN_COPY_UINT32(data + name_len, &flags, NULL); rd->len = len; rd->data = data; return 0; } int sss_nss_getpwnam_timeout(const char *name, struct passwd *pwd, char *buffer, size_t buflen, struct passwd **result, uint32_t flags, unsigned int timeout) { int ret; struct nss_input inp = { .input.name = name, .cmd = SSS_NSS_GETPWNAM_EX, .result.pwrep.result = pwd, .result.pwrep.buffer = buffer, .result.pwrep.buflen = buflen}; ret = make_name_flag_req_data(name, flags, &inp.rd); if (ret != 0) { return ret; } ret = sss_get_ex(&inp, flags, timeout); free(discard_const(inp.rd.data)); if (result != NULL) { if (ret == 0) { *result = inp.result.pwrep.result; } else { *result = NULL; } } return ret; } int sss_nss_getpwuid_timeout(uid_t uid, struct passwd *pwd, char *buffer, size_t buflen, struct passwd **result, uint32_t flags, unsigned int timeout) { int ret; uint32_t req_data[2]; struct nss_input inp = { .input.uid = uid, .cmd = SSS_NSS_GETPWUID_EX, .rd.len = 2 * sizeof(uint32_t), .rd.data = &req_data, .result.pwrep.result = pwd, .result.pwrep.buffer = buffer, .result.pwrep.buflen = buflen}; SAFEALIGN_COPY_UINT32(&req_data[0], &uid, NULL); SAFEALIGN_COPY_UINT32(&req_data[1], &flags, NULL); ret = sss_get_ex(&inp, flags, timeout); if (result != NULL) { if (ret == 0) { *result = inp.result.pwrep.result; } else { *result = NULL; } } return ret; } int sss_nss_getgrnam_timeout(const char *name, struct group *grp, char *buffer, size_t buflen, struct group **result, uint32_t flags, unsigned int timeout) { int ret; struct nss_input inp = { .input.name = name, .cmd = SSS_NSS_GETGRNAM_EX, .result.grrep.result = grp, .result.grrep.buffer = buffer, .result.grrep.buflen = buflen}; ret = make_name_flag_req_data(name, flags, &inp.rd); if (ret != 0) { return ret; } ret = sss_get_ex(&inp, flags, timeout); free(discard_const(inp.rd.data)); if (result != NULL) { if (ret == 0) { *result = inp.result.grrep.result; } else { *result = NULL; } } return ret; } int sss_nss_getgrgid_timeout(gid_t gid, struct group *grp, char *buffer, size_t buflen, struct group **result, uint32_t flags, unsigned int timeout) { int ret; uint32_t req_data[2]; struct nss_input inp = { .input.gid = gid, .cmd = SSS_NSS_GETGRGID_EX, .rd.len = 2 * sizeof(uint32_t), .rd.data = &req_data, .result.grrep.result = grp, .result.grrep.buffer = buffer, .result.grrep.buflen = buflen}; SAFEALIGN_COPY_UINT32(&req_data[0], &gid, NULL); SAFEALIGN_COPY_UINT32(&req_data[1], &flags, NULL); ret = sss_get_ex(&inp, flags, timeout); if (result != NULL) { if (ret == 0) { *result = inp.result.grrep.result; } else { *result = NULL; } } return ret; } int sss_nss_getgrouplist_timeout(const char *name, gid_t group, gid_t *groups, int *ngroups, uint32_t flags, unsigned int timeout) { int ret; long int new_ngroups; long int start = 1; struct nss_input inp = { .input.name = name, .cmd = SSS_NSS_INITGR_EX}; ret = make_name_flag_req_data(name, flags, &inp.rd); if (ret != 0) { return ret; } new_ngroups = MAX(1, *ngroups); inp.result.initgrrep.groups = malloc(new_ngroups * sizeof(gid_t)); if (inp.result.initgrrep.groups == NULL) { free(discard_const(inp.rd.data)); return ENOMEM; } inp.result.initgrrep.groups[0] = group; inp.result.initgrrep.ngroups = &new_ngroups; inp.result.initgrrep.start = &start; /* inp.result.initgrrep.groups, inp.result.initgrrep.ngroups and * inp.result.initgrrep.start might be modified by sss_get_ex() */ ret = sss_get_ex(&inp, flags, timeout); free(discard_const(inp.rd.data)); if (ret != 0) { free(inp.result.initgrrep.groups); return ret; } memcpy(groups, inp.result.initgrrep.groups, MIN(*ngroups, start) * sizeof(gid_t)); free(inp.result.initgrrep.groups); if (start > *ngroups) { ret = ERANGE; } else { ret = 0; } *ngroups = start; return ret; }