#include <config.h>
#ifdef HAVE_SSSD
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <unistd.h>
#include <time.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <stdint.h>
#include "sudoers.h"
#include "gram.h"
#include "sudo_lbuf.h"
#include "sudo_ldap.h"
#include "sudo_dso.h"
struct sss_sudo_attr {
char *name;
char **values;
unsigned int num_values;
};
struct sss_sudo_rule {
unsigned int num_attrs;
struct sss_sudo_attr *attrs;
};
struct sss_sudo_result {
unsigned int num_rules;
struct sss_sudo_rule *rules;
};
typedef int (*sss_sudo_send_recv_t)(uid_t, const char*, const char*,
uint32_t*, struct sss_sudo_result**);
typedef int (*sss_sudo_send_recv_defaults_t)(uid_t, const char*, uint32_t*,
char**, struct sss_sudo_result**);
typedef void (*sss_sudo_free_result_t)(struct sss_sudo_result*);
typedef int (*sss_sudo_get_values_t)(struct sss_sudo_rule*, const char*,
char***);
typedef void (*sss_sudo_free_values_t)(char**);
struct sudo_sss_handle {
char *domainname;
char *ipa_host;
char *ipa_shost;
struct passwd *pw;
void *ssslib;
struct sudoers_parse_tree parse_tree;
sss_sudo_send_recv_t fn_send_recv;
sss_sudo_send_recv_defaults_t fn_send_recv_defaults;
sss_sudo_free_result_t fn_free_result;
sss_sudo_get_values_t fn_get_values;
sss_sudo_free_values_t fn_free_values;
};
static int
get_ipa_hostname(char **shostp, char **lhostp)
{
size_t linesize = 0;
char *lhost = NULL;
char *shost = NULL;
char *line = NULL;
int ret = false;
ssize_t len;
FILE *fp;
debug_decl(get_ipa_hostname, SUDOERS_DEBUG_SSSD)
fp = fopen(_PATH_SSSD_CONF, "r");
if (fp != NULL) {
while ((len = getdelim(&line, &linesize, '\n', fp)) != -1) {
char *cp = line;
while (len > 0 && isspace((unsigned char)line[len - 1]))
line[--len] = '\0';
while (isspace((unsigned char)*cp))
cp++;
if (strncmp(cp, "ipa_hostname", 12) == 0) {
cp += 12;
while (isblank((unsigned char)*cp))
cp++;
if (*cp++ != '=')
continue;
while (isblank((unsigned char)*cp))
cp++;
if (*cp == '\0')
continue;
lhost = strdup(cp);
if (lhost != NULL && (cp = strchr(lhost, '.')) != NULL) {
shost = strndup(lhost, (size_t)(cp - lhost));
} else {
shost = lhost;
}
if (shost != NULL && lhost != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"ipa_hostname %s overrides %s", lhost, user_host);
*shostp = shost;
*lhostp = lhost;
ret = true;
} else {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
free(shost);
free(lhost);
ret = -1;
}
break;
}
}
fclose(fp);
free(line);
}
debug_return_int(ret);
}
static bool
sudo_sss_check_user(struct sudo_sss_handle *handle, struct sss_sudo_rule *rule)
{
const char *host = handle->ipa_host ? handle->ipa_host : user_runhost;
const char *shost = handle->ipa_shost ? handle->ipa_shost : user_srunhost;
char **val_array;
int i, ret = false;
debug_decl(sudo_sss_check_user, SUDOERS_DEBUG_SSSD);
if (rule == NULL)
debug_return_bool(false);
switch (handle->fn_get_values(rule, "sudoUser", &val_array)) {
case 0:
break;
case ENOENT:
sudo_debug_printf(SUDO_DEBUG_INFO, "No result.");
debug_return_bool(false);
default:
sudo_debug_printf(SUDO_DEBUG_ERROR,
"handle->fn_get_values(sudoUser): != 0");
debug_return_bool(false);
}
for (i = 0; val_array[i] != NULL && !ret; ++i) {
const char *val = val_array[i];
sudo_debug_printf(SUDO_DEBUG_DEBUG, "val[%d]=%s", i, val);
switch (*val) {
case '+':
if (netgr_matches(val, def_netgroup_tuple ? host : NULL,
def_netgroup_tuple ? shost : NULL, handle->pw->pw_name)) {
ret = true;
}
break;
case '%':
if (usergr_matches(val, handle->pw->pw_name, handle->pw)) {
ret = true;
}
break;
default:
if (strcmp(val, "ALL") == 0 ||
userpw_matches(val, handle->pw->pw_name, handle->pw)) {
ret = true;
}
break;
}
sudo_debug_printf(SUDO_DEBUG_DIAG,
"sssd/ldap sudoUser '%s' ... %s (%s)", val,
ret ? "MATCH!" : "not", handle->pw->pw_name);
}
handle->fn_free_values(val_array);
debug_return_bool(ret);
}
static char *
val_array_iter(void **vp)
{
char **val_array = *vp;
*vp = val_array + 1;
return *val_array;
}
static bool
sss_to_sudoers(struct sudo_sss_handle *handle,
struct sss_sudo_result *sss_result)
{
struct userspec *us;
struct member *m;
unsigned int i;
debug_decl(sss_to_sudoers, SUDOERS_DEBUG_SSSD)
if ((us = calloc(1, sizeof(*us))) == NULL)
goto oom;
TAILQ_INIT(&us->users);
TAILQ_INIT(&us->privileges);
STAILQ_INIT(&us->comments);
TAILQ_INSERT_TAIL(&handle->parse_tree.userspecs, us, entries);
if ((m = calloc(1, sizeof(*m))) == NULL)
goto oom;
m->type = ALL;
TAILQ_INSERT_TAIL(&us->users, m, entries);
for (i = sss_result->num_rules; i-- > 0; ) {
struct sss_sudo_rule *rule = sss_result->rules + i;
char **cmnds, **runasusers = NULL, **runasgroups = NULL;
char **opts = NULL, **notbefore = NULL, **notafter = NULL;
char **hosts = NULL, **cn_array = NULL, *cn = NULL;
struct privilege *priv = NULL;
if (!sudo_sss_check_user(handle, rule))
continue;
switch (handle->fn_get_values(rule, "sudoCommand", &cmnds)) {
case 0:
break;
case ENOENT:
continue;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "cn", &cn_array)) {
case 0:
cn = cn_array[0];
break;
case ENOENT:
break;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "sudoHost", &hosts)) {
case 0:
case ENOENT:
break;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "sudoRunAsUser", &runasusers)) {
case 0:
break;
case ENOENT:
switch (handle->fn_get_values(rule, "sudoRunAs", &runasusers)) {
case 0:
case ENOENT:
break;
default:
goto cleanup;
}
break;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "sudoRunAsGroup", &runasgroups)) {
case 0:
case ENOENT:
break;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "sudoNotBefore", ¬before)) {
case 0:
case ENOENT:
break;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "sudoNotAfter", ¬after)) {
case 0:
case ENOENT:
break;
default:
goto cleanup;
}
switch (handle->fn_get_values(rule, "sudoOption", &opts)) {
case 0:
case ENOENT:
break;
default:
goto cleanup;
}
priv = sudo_ldap_role_to_priv(cn, hosts, runasusers, runasgroups,
cmnds, opts, notbefore ? notbefore[0] : NULL,
notafter ? notafter[0] : NULL, false, true, val_array_iter);
cleanup:
if (cn_array != NULL)
handle->fn_free_values(cn_array);
if (cmnds != NULL)
handle->fn_free_values(cmnds);
if (hosts != NULL)
handle->fn_free_values(hosts);
if (runasusers != NULL)
handle->fn_free_values(runasusers);
if (runasgroups != NULL)
handle->fn_free_values(runasgroups);
if (opts != NULL)
handle->fn_free_values(opts);
if (notbefore != NULL)
handle->fn_free_values(notbefore);
if (notafter != NULL)
handle->fn_free_values(notafter);
if (priv == NULL)
goto oom;
TAILQ_INSERT_TAIL(&us->privileges, priv, entries);
}
debug_return_bool(true);
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
free_userspecs(&handle->parse_tree.userspecs);
debug_return_bool(false);
}
static bool
sudo_sss_parse_options(struct sudo_sss_handle *handle, struct sss_sudo_rule *rule, struct defaults_list *defs)
{
int i;
char *source = NULL;
bool ret = false;
char **val_array = NULL;
char **cn_array = NULL;
debug_decl(sudo_sss_parse_options, SUDOERS_DEBUG_SSSD);
if (rule == NULL)
debug_return_bool(true);
switch (handle->fn_get_values(rule, "sudoOption", &val_array)) {
case 0:
break;
case ENOENT:
sudo_debug_printf(SUDO_DEBUG_INFO, "No result.");
debug_return_bool(true);
case ENOMEM:
goto oom;
default:
sudo_debug_printf(SUDO_DEBUG_ERROR, "handle->fn_get_values(sudoOption): != 0");
debug_return_bool(false);
}
if (handle->fn_get_values(rule, "cn", &cn_array) == 0) {
if (cn_array[0] != NULL) {
char *cp;
if (asprintf(&cp, "sudoRole %s", cn_array[0]) == -1)
goto oom;
source = rcstr_dup(cp);
free(cp);
if (source == NULL)
goto oom;
}
handle->fn_free_values(cn_array);
cn_array = NULL;
}
if (source == NULL) {
if ((source = rcstr_dup("sudoRole UNKNOWN")) == NULL)
goto oom;
}
for (i = 0; val_array[i] != NULL; i++) {
char *var, *val;
int op;
op = sudo_ldap_parse_option(val_array[i], &var, &val);
if (!sudo_ldap_add_default(var, val, op, source, defs))
goto oom;
}
ret = true;
goto done;
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
done:
rcstr_delref(source);
handle->fn_free_values(val_array);
debug_return_bool(ret);
}
static struct sss_sudo_result *
sudo_sss_result_get(struct sudo_nss *nss, struct passwd *pw)
{
struct sudo_sss_handle *handle = nss->handle;
struct sss_sudo_result *sss_result = NULL;
uint32_t sss_error = 0, rc;
debug_decl(sudo_sss_result_get, SUDOERS_DEBUG_SSSD);
sudo_debug_printf(SUDO_DEBUG_DIAG, " username=%s", pw->pw_name);
sudo_debug_printf(SUDO_DEBUG_DIAG, "domainname=%s",
handle->domainname ? handle->domainname : "NULL");
rc = handle->fn_send_recv(pw->pw_uid, pw->pw_name,
handle->domainname, &sss_error, &sss_result);
switch (rc) {
case 0:
switch (sss_error) {
case 0:
if (sss_result != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO, "Received %u rule(s)",
sss_result->num_rules);
} else {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"Internal error: sss_result == NULL && sss_error == 0");
debug_return_ptr(NULL);
}
break;
case ENOENT:
sudo_debug_printf(SUDO_DEBUG_INFO, "The user was not found in SSSD.");
debug_return_ptr(NULL);
default:
sudo_debug_printf(SUDO_DEBUG_ERROR, "sss_error=%u\n", sss_error);
debug_return_ptr(NULL);
}
break;
case ENOMEM:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
default:
sudo_debug_printf(SUDO_DEBUG_ERROR, "handle->fn_send_recv: rc=%d", rc);
debug_return_ptr(NULL);
}
debug_return_ptr(sss_result);
}
static int
sudo_sss_close(struct sudo_nss *nss)
{
struct sudo_sss_handle *handle = nss->handle;
debug_decl(sudo_sss_close, SUDOERS_DEBUG_SSSD);
if (handle != NULL) {
sudo_dso_unload(handle->ssslib);
if (handle->pw != NULL)
sudo_pw_delref(handle->pw);
free(handle->ipa_host);
if (handle->ipa_host != handle->ipa_shost)
free(handle->ipa_shost);
free_parse_tree(&handle->parse_tree);
free(handle);
nss->handle = NULL;
}
debug_return_int(0);
}
static int
sudo_sss_open(struct sudo_nss *nss)
{
struct sudo_sss_handle *handle;
static const char path[] = _PATH_SSSD_LIB"/libsss_sudo.so";
debug_decl(sudo_sss_open, SUDOERS_DEBUG_SSSD);
if (nss->handle != NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: called with non-NULL handle %p", __func__, nss->handle);
sudo_sss_close(nss);
}
handle = calloc(1, sizeof(struct sudo_sss_handle));
if (handle == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(ENOMEM);
}
handle->ssslib = sudo_dso_load(path, SUDO_DSO_LAZY);
if (handle->ssslib == NULL) {
const char *errstr = sudo_dso_strerror();
sudo_warnx(U_("unable to load %s: %s"), path,
errstr ? errstr : "unknown error");
sudo_warnx(U_("unable to initialize SSS source. Is SSSD installed on your machine?"));
free(handle);
debug_return_int(EFAULT);
}
handle->fn_send_recv =
sudo_dso_findsym(handle->ssslib, "sss_sudo_send_recv");
if (handle->fn_send_recv == NULL) {
sudo_warnx(U_("unable to find symbol \"%s\" in %s"), path,
"sss_sudo_send_recv");
free(handle);
debug_return_int(EFAULT);
}
handle->fn_send_recv_defaults =
sudo_dso_findsym(handle->ssslib, "sss_sudo_send_recv_defaults");
if (handle->fn_send_recv_defaults == NULL) {
sudo_warnx(U_("unable to find symbol \"%s\" in %s"), path,
"sss_sudo_send_recv_defaults");
free(handle);
debug_return_int(EFAULT);
}
handle->fn_free_result =
sudo_dso_findsym(handle->ssslib, "sss_sudo_free_result");
if (handle->fn_free_result == NULL) {
sudo_warnx(U_("unable to find symbol \"%s\" in %s"), path,
"sss_sudo_free_result");
free(handle);
debug_return_int(EFAULT);
}
handle->fn_get_values =
sudo_dso_findsym(handle->ssslib, "sss_sudo_get_values");
if (handle->fn_get_values == NULL) {
sudo_warnx(U_("unable to find symbol \"%s\" in %s"), path,
"sss_sudo_get_values");
free(handle);
debug_return_int(EFAULT);
}
handle->fn_free_values =
sudo_dso_findsym(handle->ssslib, "sss_sudo_free_values");
if (handle->fn_free_values == NULL) {
sudo_warnx(U_("unable to find symbol \"%s\" in %s"), path,
"sss_sudo_free_values");
free(handle);
debug_return_int(EFAULT);
}
if (strcasecmp(user_runhost, user_host) == 0) {
if (get_ipa_hostname(&handle->ipa_shost, &handle->ipa_host) == -1) {
free(handle);
debug_return_int(ENOMEM);
}
}
init_parse_tree(&handle->parse_tree, handle->ipa_host, handle->ipa_shost);
nss->handle = handle;
sudo_debug_printf(SUDO_DEBUG_DEBUG, "handle=%p", handle);
debug_return_int(0);
}
static int
sudo_sss_query(struct sudo_nss *nss, struct passwd *pw)
{
struct sudo_sss_handle *handle = nss->handle;
struct sss_sudo_result *sss_result = NULL;
int ret = 0;
debug_decl(sudo_sss_query, SUDOERS_DEBUG_SSSD);
if (handle == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: called with NULL handle", __func__);
debug_return_int(-1);
}
if (handle->pw != NULL) {
if (pw == handle->pw)
goto done;
sudo_pw_delref(handle->pw);
handle->pw = NULL;
}
free_userspecs(&handle->parse_tree.userspecs);
sss_result = sudo_sss_result_get(nss, pw);
sudo_debug_printf(SUDO_DEBUG_DIAG,
"searching SSSD/LDAP for sudoers entries for user %s, host %s",
pw->pw_name, user_runhost);
sudo_pw_addref(pw);
handle->pw = pw;
if (sss_result != NULL) {
if (!sss_to_sudoers(handle, sss_result)) {
ret = -1;
goto done;
}
}
done:
handle->fn_free_result(sss_result);
if (ret == -1) {
free_userspecs(&handle->parse_tree.userspecs);
if (handle->pw != NULL) {
sudo_pw_delref(handle->pw);
handle->pw = NULL;
}
}
sudo_debug_printf(SUDO_DEBUG_DIAG, "Done with LDAP searches");
debug_return_int(ret);
}
static struct sudoers_parse_tree *
sudo_sss_parse(struct sudo_nss *nss)
{
struct sudo_sss_handle *handle = nss->handle;
debug_decl(sudo_sss_parse, SUDOERS_DEBUG_SSSD);
if (handle == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: called with NULL handle", __func__);
debug_return_ptr(NULL);
}
debug_return_ptr(&handle->parse_tree);
}
static int
sudo_sss_getdefs(struct sudo_nss *nss)
{
struct sudo_sss_handle *handle = nss->handle;
struct sss_sudo_result *sss_result = NULL;
static bool cached;
uint32_t sss_error;
unsigned int i;
int rc;
debug_decl(sudo_sss_getdefs, SUDOERS_DEBUG_SSSD);
if (handle == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: called with NULL handle", __func__);
debug_return_int(-1);
}
if (cached)
debug_return_int(0);
sudo_debug_printf(SUDO_DEBUG_DIAG, "Looking for cn=defaults");
rc = handle->fn_send_recv_defaults(sudo_user.pw->pw_uid,
sudo_user.pw->pw_name, &sss_error, &handle->domainname, &sss_result);
switch (rc) {
case 0:
break;
case ENOMEM:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
default:
sudo_debug_printf(SUDO_DEBUG_ERROR,
"handle->fn_send_recv_defaults: rc=%d, sss_error=%u", rc, sss_error);
debug_return_int(-1);
}
switch (sss_error) {
case 0:
for (i = 0; i < sss_result->num_rules; ++i) {
struct sss_sudo_rule *sss_rule = sss_result->rules + i;
sudo_debug_printf(SUDO_DEBUG_DIAG,
"Parsing cn=defaults, %d/%d", i, sss_result->num_rules);
if (!sudo_sss_parse_options(handle, sss_rule,
&handle->parse_tree.defaults))
goto bad;
}
break;
case ENOENT:
sudo_debug_printf(SUDO_DEBUG_INFO,
"No global defaults entry found in SSSD.");
break;
default:
sudo_debug_printf(SUDO_DEBUG_ERROR, "sss_error=%u\n", sss_error);
goto bad;
}
handle->fn_free_result(sss_result);
cached = true;
debug_return_int(0);
bad:
handle->fn_free_result(sss_result);
debug_return_int(-1);
}
struct sudo_nss sudo_nss_sss = {
{ NULL, NULL },
sudo_sss_open,
sudo_sss_close,
sudo_sss_parse,
sudo_sss_query,
sudo_sss_getdefs
};
#endif