/*
SSSD
Common Responder utility functions
Copyright (C) Sumit Bose <sbose@redhat.com> 2014
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 <http://www.gnu.org/licenses/>.
*/
#include <talloc.h>
#include "responder/common/responder.h"
#include "responder/common/cache_req/cache_req.h"
#include "util/util.h"
static inline bool
attr_in_list(const char **list, size_t nlist, const char *str)
{
size_t i;
for (i = 0; i < nlist; i++) {
if (strcasecmp(list[i], str) == 0) {
break;
}
}
return (i < nlist) ? true : false;
}
const char **parse_attr_list_ex(TALLOC_CTX *mem_ctx, const char *conf_str,
const char **defaults)
{
TALLOC_CTX *tmp_ctx;
errno_t ret;
const char **list = NULL;
const char **res = NULL;
int list_size;
char **conf_list = NULL;
int conf_list_size = 0;
const char **allow = NULL;
const char **deny = NULL;
int ai = 0, di = 0, li = 0;
int i;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
return NULL;
}
if (conf_str) {
ret = split_on_separator(tmp_ctx, conf_str, ',', true, true,
&conf_list, &conf_list_size);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot parse attribute ACL list %s: %d\n", conf_str, ret);
goto done;
}
allow = talloc_zero_array(tmp_ctx, const char *, conf_list_size);
deny = talloc_zero_array(tmp_ctx, const char *, conf_list_size);
if (allow == NULL || deny == NULL) {
goto done;
}
}
for (i = 0; i < conf_list_size; i++) {
switch (conf_list[i][0]) {
case '+':
allow[ai] = conf_list[i] + 1;
ai++;
continue;
case '-':
deny[di] = conf_list[i] + 1;
di++;
continue;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "ACL values must start with "
"either '+' (allow) or '-' (deny), got '%s'\n",
conf_list[i]);
goto done;
}
}
/* Assume the output will have to hold defaults and all the configured,
* values, resize later
*/
list_size = 0;
if (defaults != NULL) {
while (defaults[list_size]) {
list_size++;
}
}
list_size += conf_list_size;
list = talloc_zero_array(tmp_ctx, const char *, list_size + 1);
if (list == NULL) {
goto done;
}
/* Start by copying explicitly allowed attributes */
for (i = 0; i < ai; i++) {
/* if the attribute is explicitly denied, skip it */
if (attr_in_list(deny, di, allow[i])) {
continue;
}
list[li] = talloc_strdup(list, allow[i]);
if (list[li] == NULL) {
goto done;
}
li++;
DEBUG(SSSDBG_TRACE_INTERNAL,
"Added allowed attr %s to whitelist\n", allow[i]);
}
/* Add defaults */
if (defaults != NULL) {
for (i = 0; defaults[i]; i++) {
/* if the attribute is explicitly denied, skip it */
if (attr_in_list(deny, di, defaults[i])) {
continue;
}
list[li] = talloc_strdup(list, defaults[i]);
if (list[li] == NULL) {
goto done;
}
li++;
DEBUG(SSSDBG_TRACE_INTERNAL,
"Added default attr %s to whitelist\n", defaults[i]);
}
}
res = talloc_steal(mem_ctx, list);
done:
talloc_free(tmp_ctx);
return res;
}
char *sss_resp_create_fqname(TALLOC_CTX *mem_ctx,
struct resp_ctx *rctx,
struct sss_domain_info *dom,
bool name_is_upn,
const char *orig_name)
{
TALLOC_CTX *tmp_ctx;
char *name;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
return NULL;
}
name = sss_get_cased_name(tmp_ctx, orig_name, dom->case_sensitive);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "sss_get_cased_name failed\n");
talloc_free(tmp_ctx);
return NULL;
}
name = sss_reverse_replace_space(tmp_ctx, name, rctx->override_space);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "sss_reverse_replace_space failed\n");
talloc_free(tmp_ctx);
return NULL;
}
if (name_is_upn == false) {
name = sss_create_internal_fqname(tmp_ctx, name, dom->name);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "sss_create_internal_fqname failed\n");
talloc_free(tmp_ctx);
return NULL;
}
}
name = talloc_steal(mem_ctx, name);
talloc_free(tmp_ctx);
return name;
}
struct resp_resolve_group_names_state {
struct tevent_context *ev;
struct resp_ctx *rctx;
struct sss_domain_info *dom;
struct ldb_result *initgr_res;
bool needs_refresh;
unsigned int group_iter;
struct ldb_result *initgr_named_res;
};
static void resp_resolve_group_done(struct tevent_req *subreq);
static errno_t resp_resolve_group_next(struct tevent_req *req);
static errno_t resp_resolve_group_reread_names(struct resp_resolve_group_names_state *state);
struct tevent_req *resp_resolve_group_names_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct resp_ctx *rctx,
struct sss_domain_info *dom,
struct ldb_result *initgr_res)
{
struct resp_resolve_group_names_state *state;
struct tevent_req *req;
errno_t ret;
req = tevent_req_create(mem_ctx, &state, struct resp_resolve_group_names_state);
if (req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
return NULL;
}
state->ev = ev;
state->rctx = rctx;
state->dom = dom;
state->initgr_res = initgr_res;
ret = resp_resolve_group_next(req);
if (ret == EOK) {
goto immediate;
} else if (ret != EAGAIN) {
goto immediate;
}
return req;
immediate:
if (ret == EOK) {
tevent_req_done(req);
} else {
tevent_req_error(req, ret);
}
tevent_req_post(req, ev);
return req;
}
static bool
resp_resolve_group_needs_refresh(struct resp_resolve_group_names_state *state)
{
/* Refresh groups that have a non-zero GID,
* but are marked as non-POSIX
*/
bool is_posix;
uint64_t gid;
struct ldb_message *group_msg;
group_msg = state->initgr_res->msgs[state->group_iter];
is_posix = ldb_msg_find_attr_as_bool(group_msg, SYSDB_POSIX, false);
gid = ldb_msg_find_attr_as_uint64(group_msg, SYSDB_GIDNUM, 0);
if (is_posix == false && gid != 0) {
return true;
}
return false;
}
static errno_t resp_resolve_group_next(struct tevent_req *req)
{
struct cache_req_data *data;
uint64_t gid;
struct tevent_req *subreq;
struct resp_resolve_group_names_state *state;
state = tevent_req_data(req, struct resp_resolve_group_names_state);
while (state->group_iter < state->initgr_res->count
&& !resp_resolve_group_needs_refresh(state)) {
state->group_iter++;
}
if (state->group_iter >= state->initgr_res->count) {
/* All groups were refreshed */
return EOK;
}
/* Fire a request */
gid = ldb_msg_find_attr_as_uint64(state->initgr_res->msgs[state->group_iter],
SYSDB_GIDNUM, 0);
if (gid == 0) {
return EINVAL;
}
data = cache_req_data_id_attrs(state, CACHE_REQ_GROUP_BY_ID, gid, NULL);
if (data == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
return ENOMEM;
}
subreq = cache_req_send(state,
state->ev,
state->rctx,
state->rctx->ncache,
0,
CACHE_REQ_ANY_DOM,
NULL,
data);
if (subreq == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n");
return ENOMEM;
}
tevent_req_set_callback(subreq, resp_resolve_group_done, req);
return EAGAIN;
}
static void resp_resolve_group_done(struct tevent_req *subreq)
{
struct resp_resolve_group_names_state *state;
struct tevent_req *req;
errno_t ret;
req = tevent_req_callback_data(subreq, struct tevent_req);
state = tevent_req_data(req, struct resp_resolve_group_names_state);
ret = cache_req_single_domain_recv(state, subreq, NULL);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh group\n");
/* Try to refresh the others on error */
}
state->group_iter++;
state->needs_refresh = true;
ret = resp_resolve_group_next(req);
if (ret == EOK) {
ret = resp_resolve_group_reread_names(state);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
DEBUG(SSSDBG_TRACE_FUNC, "All groups are refreshed, done\n");
tevent_req_done(req);
return;
} else if (ret != EAGAIN) {
tevent_req_error(req, ret);
return;
}
/* Continue refreshing.. */
}
static errno_t
resp_resolve_group_reread_names(struct resp_resolve_group_names_state *state)
{
errno_t ret;
const char *username;
/* re-read reply in case any groups were renamed */
/* msgs[0] is the user entry */
username = sss_view_ldb_msg_find_attr_as_string(state->dom,
state->initgr_res->msgs[0],
SYSDB_NAME,
NULL);
if (username == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n");
return EINVAL;
}
ret = sysdb_initgroups_with_views(state,
state->dom,
username,
&state->initgr_named_res);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot re-read the group names\n");
return ret;
}
return EOK;
}
int resp_resolve_group_names_recv(TALLOC_CTX *mem_ctx,
struct tevent_req *req,
struct ldb_result **_initgr_named_res)
{
struct resp_resolve_group_names_state *state = NULL;
state = tevent_req_data(req, struct resp_resolve_group_names_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
*_initgr_named_res = talloc_steal(mem_ctx, state->initgr_named_res);
return EOK;
}
const char *
sss_resp_get_shell_override(struct ldb_message *msg,
struct resp_ctx *rctx,
struct sss_domain_info *domain)
{
const char *shell;
int i;
/* Check whether we are unconditionally overriding
* the server for the login shell. */
if (domain->override_shell) {
return domain->override_shell;
} else if (rctx->override_shell) {
return rctx->override_shell;
}
shell = sss_view_ldb_msg_find_attr_as_string(domain, msg, SYSDB_SHELL,
NULL);
if (shell == NULL) {
/* Check whether there is a default shell specified */
if (domain->default_shell) {
return domain->default_shell;
} else if (rctx->default_shell) {
return rctx->default_shell;
}
return "";
}
if (rctx->allowed_shells == NULL && rctx->vetoed_shells == NULL) {
return shell;
}
if (rctx->vetoed_shells) {
for (i = 0; rctx->vetoed_shells[i]; i++) {
if (strcmp(rctx->vetoed_shells[i], shell) == 0) {
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is vetoed. Using fallback.\n",
shell);
return rctx->shell_fallback;
}
}
}
if (rctx->etc_shells) {
for (i = 0; rctx->etc_shells[i]; i++) {
if (strcmp(shell, rctx->etc_shells[i]) == 0) {
DEBUG(SSSDBG_TRACE_ALL,
"Shell %s found in /etc/shells\n", shell);
break;
}
}
if (rctx->etc_shells[i]) {
DEBUG(SSSDBG_TRACE_ALL, "Using original shell '%s'\n", shell);
return shell;
}
}
if (rctx->allowed_shells) {
if (strcmp(rctx->allowed_shells[0], "*") == 0) {
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is allowed but does not exist. "
"Using fallback\n", shell);
return rctx->shell_fallback;
} else {
for (i = 0; rctx->allowed_shells[i]; i++) {
if (strcmp(rctx->allowed_shells[i], shell) == 0) {
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is allowed but does not exist. "
"Using fallback\n", shell);
return rctx->shell_fallback;
}
}
}
}
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is not allowed and does not exist.\n", shell);
return NOLOGIN_SHELL;
}