/* Copyright (C) 2021 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 #include "sss_cli.h" /* This shadow-utils plugin contains partial SSSD implementation * of `subid_nss_ops` API as described in * https://github.com/shadow-maint/shadow/blob/d4b6d1549b2af48ce3cb6ff78d9892095fb8fdd9/lib/prototypes.h#L271 */ /* Find all subid ranges delegated to a user. * * Usage in shadow-utils: * libsubid: get_sub?id_ranges() -> list_owner_ranges() * * SUBID_RANGES Reply: * * 0-3: 32bit unsigned number of UID results * 4-7: 32bit unsigned number of GID results * For each result (sub-uid ranges first): * 0-3: 32bit number with "start" id * 4-7: 32bit number with "count" (range size) */ enum subid_status shadow_subid_list_owner_ranges(const char *user, enum subid_type id_type, struct subid_range **ranges, int *count) { size_t user_len; enum sss_status ret; uint8_t *repbuf = NULL; size_t index = 0; size_t replen; int errnop; struct sss_cli_req_data rd; uint32_t num_results = 0; uint32_t val; if ( !user || !ranges || !count || ((id_type != ID_TYPE_UID) && (id_type != ID_TYPE_GID)) ) { return SUBID_STATUS_ERROR; } ret = sss_strnlen(user, SSS_NAME_MAX, &user_len); if (ret != 0) { return SUBID_STATUS_UNKNOWN_USER; } rd.len = user_len + 1; rd.data = user; sss_nss_lock(); /* Anticipated workflow will always request both * sub-uid and sub-gid ranges anyway. * So don't bother with dedicated commands - * just request everything in one shot. * The second request will get data from the cache. */ ret = sss_cli_make_request_with_checks(SSS_NSS_GET_SUBID_RANGES, &rd, SSS_CLI_SOCKET_TIMEOUT, &repbuf, &replen, &errnop, SSS_NSS_SOCKET_NAME); sss_nss_unlock(); if ( (ret != SSS_STATUS_SUCCESS) || (errnop != EOK) /* response must contain at least the "payload header" */ || (replen < 2*sizeof(uint32_t)) /* and even number of 'uint32_t' */ || (replen % (2*sizeof(uint32_t)) != 0) ) { free(repbuf); if (ret == SSS_STATUS_UNAVAIL) { return SUBID_STATUS_ERROR_CONN; } return SUBID_STATUS_ERROR; } SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL); if (num_results > (replen/sizeof(uint32_t) - 2)/2) { free(repbuf); return SUBID_STATUS_ERROR; } if (id_type == ID_TYPE_UID) { index = 2 * sizeof(uint32_t); } else { index = (2 + 2*num_results) * sizeof(uint32_t); SAFEALIGN_COPY_UINT32(&num_results, repbuf + sizeof(uint32_t), NULL); if (num_results > ((replen - index)/sizeof(uint32_t)/2)) { free(repbuf); return SUBID_STATUS_ERROR; } } if (num_results == 0) { /* TODO: how to distinguish "user not found" vs "user doesn't have ranges defined" here? * Options: * - special "fake" entry in the cache * - provide 'nss_protocol_done_fn' to 'nss_getby_name' to avoid "ENOENT -> "empty packet" logic * - add custom error code for this case and handle in generic 'nss_protocol_done' * * Note: at the moment this is not important, since shadow-utils doesn't use return code internally * and returns -1 from libsubid on any error anyway. */ free(repbuf); return SUBID_STATUS_UNKNOWN_USER; } *count = num_results; if (*count < 0) { free(repbuf); return SUBID_STATUS_ERROR; } *ranges = malloc(num_results * sizeof(struct subid_range)); if (!*ranges) { free(repbuf); return SUBID_STATUS_ERROR; } for (uint32_t c = 0; c < num_results; ++c) { SAFEALIGN_COPY_UINT32(&val, repbuf + index, &index); (*ranges)[c].start = val; SAFEALIGN_COPY_UINT32(&val, repbuf + index, &index); (*ranges)[c].count = val; } free(repbuf); return SUBID_STATUS_SUCCESS; } /* Does a user own a given subid range? * * Usage in shadow-utils: * newuidmap/user busy : have_sub_uids() -> has_range() */ enum subid_status shadow_subid_has_range(const char *owner, unsigned long start, unsigned long count, enum subid_type id_type, bool *result) { enum subid_status ret; struct subid_range *range; int amount; unsigned long end = start + count; if (!result || (end < start)) { return SUBID_STATUS_ERROR; } if (count == 0) { *result = true; return SUBID_STATUS_SUCCESS; } /* Anticipated workflow is the following: * * 1) Podman figures out ranges available for a user: * libsubid::get_subid_ranges() -> ... -> list_owner_ranges() * * 2) Podman maps available ranges: * newuidmap -> have_sub_uids() -> has_range() * At this point all ranges are available in a cache from step (1) * so it doesn't make sense to try "smart" LDAP searches (even if possible) * Let's just reuse list_owner_ranges() and do a check. * * It might have some sense to do a check at responder's side (i.e. without * fetching all ranges), but range is just a couple of numbers (and FreeIPA * only supports a single range per user anyway), so this optimization * wouldn't save much traffic anyway, but would introduce new * `sss_cli_command`/responder handler. */ ret = shadow_subid_list_owner_ranges(owner, id_type, &range, &amount); if (ret != SUBID_STATUS_SUCCESS) { return ret; } *result = false; for (int i = 0; i < amount; ++i) { if ((range[i].start <= start) && (range[i].start + range[i].count >= end)) { *result = true; } /* TODO: handle coverage via multiple ranges (once IPA supports this) */ } free(range); return ret; } /* Find uids who own a given subid. * * Usage in shadow-utils: * libsubid: get_sub?id_owners() -> find_subid_owners() */ enum subid_status shadow_subid_find_subid_owners(unsigned long subid, enum subid_type id_type, uid_t **uids, int *count) { /* Not yet implemented. * Currently there are no users of this function. */ return SUBID_STATUS_ERROR; }