/*
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;
}