#include <config.h>
#include <sys/types.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 <stdarg.h>
#include "sudoers.h"
#include "sudo_ldap.h"
#include "redblack.h"
#include "cvtsudoers.h"
#include "sudo_lbuf.h"
#include <gram.h>
struct seen_user {
const char *name;
unsigned long count;
};
static struct rbtree *seen_users;
static int
seen_user_compare(const void *aa, const void *bb)
{
const struct seen_user *a = aa;
const struct seen_user *b = bb;
return strcasecmp(a->name, b->name);
}
static void
seen_user_free(void *v)
{
struct seen_user *su = v;
free((void *)su->name);
free(su);
}
static bool
safe_string(const char *str)
{
unsigned int ch = *str++;
debug_decl(safe_string, SUDOERS_DEBUG_UTIL)
switch (ch) {
case '\0':
debug_return_bool(true);
case '\n':
case '\r':
case ' ':
case ':':
case '<':
debug_return_bool(false);
default:
if (ch > 127)
debug_return_bool(false);
}
while ((ch = *str++) != '\0') {
if (ch > 127 || ch == '\n' || ch == '\r')
debug_return_bool(false);
}
debug_return_bool(true);
}
static bool
print_attribute_ldif(FILE *fp, const char *name, const char *value)
{
const unsigned char *uvalue = (unsigned char *)value;
char *encoded = NULL;
size_t esize;
debug_decl(print_attribute_ldif, SUDOERS_DEBUG_UTIL)
if (!safe_string(value)) {
const size_t vlen = strlen(value);
esize = ((vlen + 2) / 3 * 4) + 1;
if ((encoded = malloc(esize)) == NULL)
debug_return_bool(false);
if (base64_encode(uvalue, vlen, encoded, esize) == (size_t)-1) {
free(encoded);
debug_return_bool(false);
}
fprintf(fp, "%s:: %s\n", name, encoded);
free(encoded);
} else if (*value != '\0') {
fprintf(fp, "%s: %s\n", name, value);
} else {
fprintf(fp, "%s:\n", name);
}
debug_return_bool(true);
}
static bool
print_options_ldif(FILE *fp, struct defaults_list *options)
{
struct defaults *opt;
char *attr_val;
int len;
debug_decl(print_options_ldif, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(opt, options, entries) {
if (opt->type != DEFAULTS)
continue;
if (opt->val != NULL) {
len = asprintf(&attr_val, "%s%s%s", opt->var,
opt->op == '+' ? "+=" : opt->op == '-' ? "-=" : "=", opt->val);
} else {
len = asprintf(&attr_val, "%s%s",
opt->op == false ? "!" : "", opt->var);
}
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
}
debug_return_bool(!ferror(fp));
}
static bool
print_global_defaults_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree,
const char *base)
{
unsigned int count = 0;
struct sudo_lbuf lbuf;
struct defaults *opt;
char *dn;
debug_decl(print_global_defaults_ldif, SUDOERS_DEBUG_UTIL)
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 80);
TAILQ_FOREACH(opt, &parse_tree->defaults, entries) {
if (opt->type == DEFAULTS) {
count++;
} else {
lbuf.len = 0;
sudo_lbuf_append(&lbuf, "# ");
sudoers_format_default_line(&lbuf, parse_tree, opt, false, true);
fprintf(fp, "# Unable to translate %s:%d\n%s\n",
opt->file, opt->lineno, lbuf.buf);
}
}
sudo_lbuf_destroy(&lbuf);
if (count == 0)
debug_return_bool(true);
if (asprintf(&dn, "cn=defaults,%s", base) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "dn", dn);
free(dn);
print_attribute_ldif(fp, "objectClass", "top");
print_attribute_ldif(fp, "objectClass", "sudoRole");
print_attribute_ldif(fp, "cn", "defaults");
print_attribute_ldif(fp, "description", "Default sudoOption's go here");
print_options_ldif(fp, &parse_tree->defaults);
putc('\n', fp);
debug_return_bool(!ferror(fp));
}
static void
print_member_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree, char *name,
int type, bool negated, int alias_type, const char *attr_name)
{
struct alias *a;
struct member *m;
struct sudo_command *c;
char *attr_val;
int len;
debug_decl(print_member_ldif, SUDOERS_DEBUG_UTIL)
switch (type) {
case ALL:
print_attribute_ldif(fp, attr_name, negated ? "!ALL" : "ALL");
break;
case MYSELF:
print_attribute_ldif(fp, attr_name, "");
break;
case COMMAND:
c = (struct sudo_command *)name;
len = asprintf(&attr_val, "%s%s%s%s%s%s%s%s",
c->digest ? digest_type_to_name(c->digest->digest_type) : "",
c->digest ? ":" : "", c->digest ? c->digest->digest_str : "",
c->digest ? " " : "", negated ? "!" : "", c->cmnd,
c->args ? " " : "", c->args ? c->args : "");
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, attr_name, attr_val);
free(attr_val);
break;
case ALIAS:
if ((a = alias_get(parse_tree, name, alias_type)) != NULL) {
TAILQ_FOREACH(m, &a->members, entries) {
print_member_ldif(fp, parse_tree, m->name, m->type,
negated ? !m->negated : m->negated, alias_type, attr_name);
}
alias_put(a);
break;
}
default:
len = asprintf(&attr_val, "%s%s", negated ? "!" : "", name);
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, attr_name, attr_val);
free(attr_val);
break;
}
debug_return;
}
static void
print_cmndspec_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree,
struct cmndspec *cs, struct cmndspec **nextp, struct defaults_list *options)
{
struct cmndspec *next = *nextp;
struct member *m;
struct tm *tp;
bool last_one;
char timebuf[sizeof("20120727121554Z")];
debug_decl(print_cmndspec_ldif, SUDOERS_DEBUG_UTIL)
if (cs->runasuserlist != NULL) {
TAILQ_FOREACH(m, cs->runasuserlist, entries) {
print_member_ldif(fp, parse_tree, m->name, m->type, m->negated,
RUNASALIAS, "sudoRunAsUser");
}
}
if (cs->runasgrouplist != NULL) {
TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
print_member_ldif(fp, parse_tree, m->name, m->type, m->negated,
RUNASALIAS, "sudoRunAsGroup");
}
}
if (cs->notbefore != UNSPEC) {
if ((tp = gmtime(&cs->notbefore)) == NULL) {
sudo_warn(U_("unable to get GMT time"));
} else {
if (strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", tp) == 0) {
sudo_warnx(U_("unable to format timestamp"));
} else {
print_attribute_ldif(fp, "sudoNotBefore", timebuf);
}
}
}
if (cs->notafter != UNSPEC) {
if ((tp = gmtime(&cs->notafter)) == NULL) {
sudo_warn(U_("unable to get GMT time"));
} else {
if (strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", tp) == 0) {
sudo_warnx(U_("unable to format timestamp"));
} else {
print_attribute_ldif(fp, "sudoNotAfter", timebuf);
}
}
}
if (cs->timeout > 0) {
char *attr_val;
if (asprintf(&attr_val, "command_timeout=%d", cs->timeout) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
}
if (TAGS_SET(cs->tags)) {
struct cmndtag tag = cs->tags;
if (tag.nopasswd != UNSPEC) {
print_attribute_ldif(fp, "sudoOption",
tag.nopasswd ? "!authenticate" : "authenticate");
}
if (tag.noexec != UNSPEC) {
print_attribute_ldif(fp, "sudoOption",
tag.noexec ? "noexec" : "!noexec");
}
if (tag.send_mail != UNSPEC) {
if (tag.send_mail) {
print_attribute_ldif(fp, "sudoOption", "mail_all_cmnds");
} else {
print_attribute_ldif(fp, "sudoOption", "!mail_all_cmnds");
print_attribute_ldif(fp, "sudoOption", "!mail_always");
print_attribute_ldif(fp, "sudoOption", "!mail_no_perms");
}
}
if (tag.setenv != UNSPEC && tag.setenv != IMPLIED) {
print_attribute_ldif(fp, "sudoOption",
tag.setenv ? "setenv" : "!setenv");
}
if (tag.follow != UNSPEC) {
print_attribute_ldif(fp, "sudoOption",
tag.follow ? "sudoedit_follow" : "!sudoedit_follow");
}
if (tag.log_input != UNSPEC) {
print_attribute_ldif(fp, "sudoOption",
tag.log_input ? "log_input" : "!log_input");
}
if (tag.log_output != UNSPEC) {
print_attribute_ldif(fp, "sudoOption",
tag.log_output ? "log_output" : "!log_output");
}
}
print_options_ldif(fp, options);
#ifdef HAVE_SELINUX
if (cs->role != NULL && cs->type != NULL) {
char *attr_val;
int len;
len = asprintf(&attr_val, "role=%s", cs->role);
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
len = asprintf(&attr_val, "type=%s", cs->type);
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
}
#endif
#ifdef HAVE_PRIV_SET
if (cs->privs != NULL || cs->limitprivs != NULL) {
char *attr_val;
int len;
if (cs->privs != NULL) {
len = asprintf(&attr_val, "privs=%s", cs->privs);
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
}
if (cs->limitprivs != NULL) {
len = asprintf(&attr_val, "limitprivs=%s", cs->limitprivs);
if (len == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
}
}
#endif
for (;;) {
last_one = next == NULL ||
RUNAS_CHANGED(cs, next) || TAGS_CHANGED(cs->tags, next->tags)
#ifdef HAVE_PRIV_SET
|| cs->privs != next->privs || cs->limitprivs != next->limitprivs
#endif
#ifdef HAVE_SELINUX
|| cs->role != next->role || cs->type != next->type
#endif
;
print_member_ldif(fp, parse_tree, cs->cmnd->name, cs->cmnd->type,
cs->cmnd->negated, CMNDALIAS, "sudoCommand");
if (last_one)
break;
cs = next;
next = TAILQ_NEXT(cs, entries);
}
*nextp = next;
debug_return;
}
static char *
user_to_cn(const char *user)
{
struct seen_user key, *su = NULL;
struct rbnode *node;
const char *src;
char *cn, *dst;
size_t size;
debug_decl(user_to_cn, SUDOERS_DEBUG_UTIL)
size = (2 * strlen(user)) + 64 + 1;
if ((cn = malloc(size)) == NULL)
goto bad;
key.name = user;
node = rbfind(seen_users, &key);
if (node != NULL) {
su = node->data;
} else {
if ((su = malloc(sizeof(*su))) == NULL)
goto bad;
su->count = 0;
if ((su->name = strdup(user)) == NULL)
goto bad;
if (rbinsert(seen_users, su, NULL) != 0)
goto bad;
}
for (src = user, dst = cn; *src != '\0'; src++) {
switch (*src) {
case ',':
case '+':
case '"':
case '\\':
case '<':
case '>':
case '#':
case ';':
*dst++ = '\\';
break;
case ' ':
if (src == user || src[1] == '\0')
*dst++ = '\\';
break;
default:
break;
}
*dst++ = *src;
}
*dst = '\0';
if (su->count != 0) {
size -= (size_t)(dst - cn);
if ((size_t)snprintf(dst, size, "_%lu", su->count) >= size) {
sudo_warnx(U_("internal error, %s overflow"), __func__);
goto bad;
}
}
su->count++;
debug_return_str(cn);
bad:
if (su != NULL && su->count == 0)
seen_user_free(su);
free(cn);
debug_return_str(NULL);
}
static bool
print_userspec_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree,
struct userspec *us, struct cvtsudoers_config *conf)
{
struct privilege *priv;
struct member *m;
struct cmndspec *cs, *next;
debug_decl(print_userspec_ldif, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(priv, &us->privileges, entries) {
TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
const char *base = conf->sudoers_base;
char *cn, *dn;
m = TAILQ_FIRST(&us->users);
cn = user_to_cn(m->type == ALL ? "ALL" : m->name);
if (cn == NULL || asprintf(&dn, "cn=%s,%s", cn, base) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "dn", dn);
print_attribute_ldif(fp, "objectClass", "top");
print_attribute_ldif(fp, "objectClass", "sudoRole");
print_attribute_ldif(fp, "cn", cn);
free(cn);
free(dn);
TAILQ_FOREACH(m, &us->users, entries) {
print_member_ldif(fp, parse_tree, m->name, m->type, m->negated,
USERALIAS, "sudoUser");
}
TAILQ_FOREACH(m, &priv->hostlist, entries) {
print_member_ldif(fp, parse_tree, m->name, m->type, m->negated,
HOSTALIAS, "sudoHost");
}
print_cmndspec_ldif(fp, parse_tree, cs, &next, &priv->defaults);
if (conf->sudo_order != 0) {
char numbuf[(((sizeof(conf->sudo_order) * 8) + 2) / 3) + 2];
if (conf->order_max != 0 && conf->sudo_order > conf->order_max) {
sudo_fatalx(U_("too many sudoers entries, maximum %u"),
conf->order_padding);
}
(void)snprintf(numbuf, sizeof(numbuf), "%u", conf->sudo_order);
print_attribute_ldif(fp, "sudoOrder", numbuf);
putc('\n', fp);
conf->sudo_order += conf->order_increment;
}
}
}
debug_return_bool(!ferror(fp));
}
static bool
print_userspecs_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree,
struct cvtsudoers_config *conf)
{
struct userspec *us;
debug_decl(print_userspecs_ldif, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
if (!print_userspec_ldif(fp, parse_tree, us, conf))
debug_return_bool(false);
}
debug_return_bool(true);
}
bool
convert_sudoers_ldif(struct sudoers_parse_tree *parse_tree,
const char *output_file, struct cvtsudoers_config *conf)
{
bool ret = true;
FILE *output_fp = stdout;
debug_decl(convert_sudoers_ldif, SUDOERS_DEBUG_UTIL)
if (conf->sudoers_base == NULL) {
sudo_fatalx(U_("the SUDOERS_BASE environment variable is not set and the -b option was not specified."));
}
if (output_file != NULL && strcmp(output_file, "-") != 0) {
if ((output_fp = fopen(output_file, "w")) == NULL)
sudo_fatal(U_("unable to open %s"), output_file);
}
seen_users = rbcreate(seen_user_compare);
if (!ISSET(conf->suppress, SUPPRESS_DEFAULTS))
print_global_defaults_ldif(output_fp, parse_tree, conf->sudoers_base);
if (!ISSET(conf->suppress, SUPPRESS_PRIVS))
print_userspecs_ldif(output_fp, parse_tree, conf);
rbdestroy(seen_users, seen_user_free);
(void)fflush(output_fp);
if (ferror(output_fp))
ret = false;
if (output_fp != stdout)
fclose(output_fp);
debug_return_bool(ret);
}