2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-22 01:49:11 +00:00

Add ldif backend to cvtsudoers, to replace sudoers2ldif

This commit is contained in:
Todd C. Miller 2018-01-27 20:08:02 -07:00
parent 7638e71730
commit 681fb2e76e
7 changed files with 491 additions and 21 deletions

View File

@ -266,6 +266,7 @@ plugins/sudoers/check.c
plugins/sudoers/check.h
plugins/sudoers/cvtsudoers.c
plugins/sudoers/cvtsudoers_json.c
plugins/sudoers/cvtsudoers_ldif.c
plugins/sudoers/def_data.c
plugins/sudoers/def_data.h
plugins/sudoers/def_data.in

View File

@ -17,12 +17,18 @@ DDEESSCCRRIIPPTTIIOONN
The options are as follows:
--ff, ----ffoorrmmaatt
Specify the output format. Currently, JSON is the only
supported output format. The JSON format is intended to be
easier for third-party applications to parse than the
traditional _s_u_d_o_e_r_s format. The various values have explicit
types which removes much of the ambiguity of the _s_u_d_o_e_r_s
format.
Specify the output format. The following formats are
supported:
JSON JSON (JavaScript Object Notation) files are usually
easier for third-party applications to consume than
the traditional _s_u_d_o_e_r_s format. The various values
have explicit types which removes much of the
ambiguity of the _s_u_d_o_e_r_s format.
LDIF LDIF (LDAP Data Interchange Format) files can be
imported into an LDAP server for use with
sudoers.ldap(4).
--hh, ----hheellpp Display a short help message to the standard output and exit.
@ -35,7 +41,7 @@ DDEESSCCRRIIPPTTIIOONN
Print the ccvvttssuuddooeerrss and _s_u_d_o_e_r_s grammar versions and exit.
SSEEEE AALLSSOO
sudoers(4), sudo(1m)
sudoers(4), sudoers.ldap(4), sudo(1m)
AAUUTTHHOORRSS
Many people have worked on ssuuddoo over the years; this version consists of
@ -63,4 +69,4 @@ DDIISSCCLLAAIIMMEERR
file distributed with ssuuddoo or https://www.sudo.ws/license.html for
complete details.
Sudo 1.8.22 January 25, 2018 Sudo 1.8.22
Sudo 1.8.22 January 27, 2018 Sudo 1.8.22

View File

@ -16,7 +16,7 @@
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.TH "CVTSUDOERS" "8" "January 25, 2018" "Sudo @PACKAGE_VERSION@" "System Manager's Manual"
.TH "CVTSUDOERS" "8" "January 27, 2018" "Sudo @PACKAGE_VERSION@" "System Manager's Manual"
.nh
.if n .ad l
.SH "NAME"
@ -52,15 +52,30 @@ The options are as follows:
.TP 12n
\fB\-f\fR, \fB\--format\fR
Specify the output format.
Currently, JSON is the only supported output format.
The JSON format is intended to be easier for third-party
applications to parse than the traditional
The following formats are supported:
.PP
.RS 12n
.PD 0
.TP 10n
JSON
JSON (JavaScript Object Notation) files are usually easier for
third-party applications to consume than the traditional
\fIsudoers\fR
format.
The various values have explicit types which removes much of the
ambiguity of the
\fIsudoers\fR
format.
.PD
.TP 10n
LDIF
LDIF (LDAP Data Interchange Format) files can be imported into an LDAP
server for use with
sudoers.ldap(@mansectform@).
.PD 0
.PP
.RE
.PD
.TP 12n
\fB\-h\fR, \fB\--help\fR
Display a short help message to the standard output and exit.
@ -84,6 +99,7 @@ and
grammar versions and exit.
.SH "SEE ALSO"
sudoers(@mansectform@),
sudoers.ldap(@mansectform@),
sudo(@mansectsu@)
.SH "AUTHORS"
Many people have worked on

View File

@ -14,7 +14,7 @@
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd January 25, 2018
.Dd January 27, 2018
.Dt CVTSUDOERS @mansectsu@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@ -49,15 +49,22 @@ The options are as follows:
.Bl -tag -width Fl
.It Fl f , -format
Specify the output format.
Currently, JSON is the only supported output format.
The JSON format is intended to be easier for third-party
applications to parse than the traditional
The following formats are supported:
.Bl -tag -width 8n
.It JSON
JSON (JavaScript Object Notation) files are usually easier for
third-party applications to consume than the traditional
.Em sudoers
format.
The various values have explicit types which removes much of the
ambiguity of the
.Em sudoers
format.
.It LDIF
LDIF (LDAP Data Interchange Format) files can be imported into an LDAP
server for use with
.Xr sudoers.ldap @mansectform@ .
.El
.It Fl h , -help
Display a short help message to the standard output and exit.
.It Fl o Ar output_file , Fl -output Ns = Ns Ar output_file
@ -80,6 +87,7 @@ grammar versions and exit.
.El
.Sh SEE ALSO
.Xr sudoers @mansectform@ ,
.Xr sudoers.ldap @mansectform@ ,
.Xr sudo @mansectsu@
.Sh AUTHORS
Many people have worked on

View File

@ -161,7 +161,8 @@ SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo editor.lo env.lo \
VISUDO_OBJS = editor.o find_path.o goodpath.o locale.o stubs.o sudo_printf.o visudo.o
CVTSUDOERS_OBJS = cvtsudoers.o cvtsudoers_json.o locale.o stubs.o sudo_printf.o
CVTSUDOERS_OBJS = cvtsudoers.o cvtsudoers_json.o cvtsudoers_ldif.o \
locale.o stubs.o sudo_printf.o
REPLAY_OBJS = getdate.o sudoreplay.o
@ -705,6 +706,17 @@ cvtsudoers_json.o: $(srcdir)/cvtsudoers_json.c $(devdir)/def_data.h \
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/cvtsudoers_json.c
cvtsudoers_ldif.o: $(srcdir)/cvtsudoers_ldif.c $(devdir)/def_data.h \
$(devdir)/gram.h $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \
$(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/cvtsudoers_ldif.c
dce.lo: $(authdir)/dce.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \

View File

@ -50,6 +50,7 @@
#endif /* HAVE_GETOPT_LONG */
extern bool convert_sudoers_json(const char *output_file);
extern bool convert_sudoers_ldif(const char *output_file, const char *base);
extern void parse_sudoers_options(void);
extern void get_hostname(void);
@ -74,13 +75,19 @@ __dso_public int main(int argc, char *argv[]);
static void help(void) __attribute__((__noreturn__));
static void usage(int);
enum output_formats {
output_invalid,
output_json,
output_ldif
};
int
main(int argc, char *argv[])
{
int ch, exitcode = EXIT_FAILURE;
enum output_formats output_format = output_json;
const char *input_file = NULL;
const char *output_file = "-";
const char *output_format = "JSON";
debug_decl(main, SUDOERS_DEBUG_MAIN)
#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
@ -119,11 +126,14 @@ main(int argc, char *argv[])
while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
switch (ch) {
case 'f':
if (strcasecmp(optarg, "json") != 0) {
if (strcasecmp(optarg, "json") == 0) {
output_format = output_json;
} else if (strcasecmp(optarg, "ldif") == 0) {
output_format = output_ldif;
} else {
sudo_warnx("unsupported output format %s", optarg);
usage(1);
}
output_format = optarg;
break;
case 'h':
help();
@ -203,7 +213,16 @@ main(int argc, char *argv[])
goto done;
}
exitcode = convert_sudoers_json(output_file) ? EXIT_SUCCESS : EXIT_FAILURE;
switch (output_format) {
case output_json:
exitcode = !convert_sudoers_json(output_file);
break;
case output_ldif:
exitcode = !convert_sudoers_ldif(output_file, NULL);
break;
default:
sudo_fatalx("error: unhandled output format %d", output_format);
}
done:
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);

View File

@ -0,0 +1,408 @@
/*
* Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
#include <stdarg.h>
#include <ctype.h>
#include "sudoers.h"
#include "parse.h"
#include "redblack.h"
#include <gram.h>
struct seen_user {
const char *name;
long count;
};
static int sudo_order;
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);
}
/*
* Print global Defaults in a single sudoRole object.
*/
static bool
print_global_defaults_ldif(FILE *fp, const char *base)
{
struct defaults *def;
debug_decl(print_global_defaults_ldif, SUDOERS_DEBUG_UTIL)
if (TAILQ_EMPTY(&defaults))
debug_return_bool(true);
fprintf(fp, "dn: cn=defaults,%s\n", base);
fputs("objectClass: top\n", fp);
fputs("objectClass: sudoRole\n", fp);
fputs("cn: defaults\n", fp);
fputs("description: Default sudoOption's go here\n", fp);
TAILQ_FOREACH(def, &defaults, entries) {
if (def->type != DEFAULTS)
continue; /* only want global defaults */
if (def->val != NULL) {
/* There is no need to double quote values here. */
fprintf(fp, "sudoOption: %s%s%s\n", def->var,
def->op == '+' ? "+=" : def->op == '-' ? "-=" : "=", def->val);
} else {
/* Boolean flag. */
fprintf(fp, "sudoOption: %s%s\n", def->op == false ? "!" : "",
def->var);
}
}
putc('\n', fp);
debug_return_bool(!ferror(fp));
}
static void
warn_bound_defaults_ldif(FILE *fp)
{
struct defaults *def;
debug_decl(warn_bound_defaults_ldif, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(def, &defaults, entries) {
if (def->type == DEFAULTS)
continue; /* only want bound defaults */
/* XXX - print Defaults line */
sudo_warnx(U_("%s:%d unable to translate Defaults line"),
def->file, def->lineno);
}
debug_return;
}
/*
* Print struct member in LDIF format, with specified prefix.
* See print_member_int() in parse.c.
*/
static void
print_member_ldif(FILE *fp, char *name, int type, int negated,
int alias_type, const char *prefix)
{
struct alias *a;
struct member *m;
struct sudo_command *c;
debug_decl(print_member_ldif, SUDOERS_DEBUG_UTIL)
switch (type) {
case ALL:
fprintf(fp, "%s: %sALL\n", prefix, negated ? "!" : "");
break;
case MYSELF:
/* Only valid for sudoRunasUser */
fprintf(fp, "%s:\n", prefix);
break;
case COMMAND:
c = (struct sudo_command *)name;
fprintf(fp, "%s: ", prefix);
if (c->digest != NULL)
fprintf(fp, "%s:", digest_type_to_name(c->digest->digest_type));
fprintf(fp, "%s%s", negated ? "!" : "", c->cmnd);
if (c->args != NULL)
fprintf(fp, " %s", c->args);
putc('\n', fp);
break;
case ALIAS:
if ((a = alias_get(name, alias_type)) != NULL) {
TAILQ_FOREACH(m, &a->members, entries) {
print_member_ldif(fp, m->name, m->type,
negated ? !m->negated : m->negated, alias_type, prefix);
}
alias_put(a);
break;
}
/* FALLTHROUGH */
default:
fprintf(fp, "%s: %s%s\n", prefix, negated ? "!" : "", name);
break;
}
debug_return;
}
/*
* Print a Cmnd_Spec in LDIF format.
* A pointer to the next Cmnd_Spec is passed in to make it possible to
* merge adjacent entries that are identical in all but the command.
*/
static void
print_cmndspec_ldif(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp)
{
struct cmndspec *next = *nextp;
struct member *m;
bool last_one;
debug_decl(print_cmndspec_ldif, SUDOERS_DEBUG_UTIL)
/* Print runasuserlist as sudoRunAsUser attributes */
if (cs->runasuserlist != NULL) {
TAILQ_FOREACH(m, cs->runasuserlist, entries) {
print_member_ldif(fp, m->name, m->type, m->negated,
RUNASALIAS, "sudoRunAsUser");
}
}
/* Print runasgrouplist as sudoRunAsGroup attributes */
if (cs->runasgrouplist != NULL) {
TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
print_member_ldif(fp, m->name, m->type, m->negated,
RUNASALIAS, "sudoRunAsGroup");
}
}
/* Print tags as sudoOption attributes */
if (cs->timeout > 0 || TAGS_SET(cs->tags)) {
struct cmndtag tag = cs->tags;
if (cs->timeout > 0) {
fprintf(fp, "sudoOption: command_timeout=%d\n", cs->timeout);
}
if (tag.nopasswd != UNSPEC) {
fprintf(fp, "sudoOption: %sauthenticate\n", tag.nopasswd ? "!" : "");
}
if (tag.noexec != UNSPEC) {
fprintf(fp, "sudoOption: %snoexec\n", tag.noexec ? "" : "!");
}
if (tag.send_mail != UNSPEC) {
if (tag.send_mail) {
fprintf(fp, "sudoOption: mail_all_cmnds\n");
} else {
fprintf(fp, "sudoOption: !mail_all_cmnds\n");
fprintf(fp, "sudoOption: !mail_always\n");
fprintf(fp, "sudoOption: !mail_no_perms\n");
}
}
if (tag.setenv != UNSPEC && tag.setenv != IMPLIED) {
fprintf(fp, "sudoOption: %ssetenv\n", tag.setenv ? "" : "!");
}
if (tag.follow != UNSPEC) {
fprintf(fp, "sudoOption: %ssudoedit_follow\n", tag.follow ? "" : "!");
}
if (tag.log_input != UNSPEC) {
fprintf(fp, "sudoOption: %slog_input\n", tag.log_input ? "" : "!");
}
if (tag.log_output != UNSPEC) {
fprintf(fp, "sudoOption: %slog_output\n", tag.log_output ? "" : "!");
}
}
#ifdef HAVE_SELINUX
/* Print SELinux role/type */
if (cs->role != NULL && cs->type != NULL) {
fprintf(fp, "sudoOption: role=%s\n", cs->role);
fprintf(fp, "sudoOption: type=%s\n", cs->type);
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_PRIV_SET
/* Print Solaris privs/limitprivs */
if (cs->privs != NULL || cs->limitprivs != NULL) {
if (cs->privs != NULL)
fprintf(fp, "sudoOption: privs=%s\n", cs->privs);
if (cs->limitprivs != NULL)
fprintf(fp, "sudoOption: limitprivs=%s\n", cs->limitprivs);
}
#endif /* HAVE_PRIV_SET */
/*
* Merge adjacent commands with matching tags, runas, SELinux
* role/type and Solaris priv settings.
*/
for (;;) {
/* Does the next entry differ only in the command itself? */
/* XXX - move into a function that returns bool */
/* XXX - TAG_SET does not account for implied SETENV */
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 /* HAVE_PRIV_SET */
#ifdef HAVE_SELINUX
|| cs->role != next->role || cs->type != next->type
#endif /* HAVE_SELINUX */
;
print_member_ldif(fp, 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;
}
/*
* Print a single User_Spec.
*/
static bool
print_userspec_ldif(FILE *fp, struct userspec *us, const char *base)
{
struct privilege *priv;
struct member *m;
struct cmndspec *cs, *next;
struct rbnode *node;
debug_decl(print_userspec_ldif, SUDOERS_DEBUG_UTIL)
/*
* Each userspec struct may contain multiple privileges for
* the user. We export each privilege as a separate sudoRole
* object for simplicity's sake.
*/
TAILQ_FOREACH(priv, &us->privileges, entries) {
TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
char *cn;
struct seen_user *su, key;
/*
* Increment the number of times we have seen this user.
* If more than one user is listed, just use the first one.
*/
m = TAILQ_FIRST(&us->users);
key.name = m->name ? m->name : "ALL";
node = rbfind(seen_users, &key);
if (node != NULL) {
su = node->data;
if (asprintf(&cn, "%s_%ld", key.name, su->count) == -1)
cn = NULL;
su->count++;
} else {
if ((su = malloc(sizeof(*su))) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
su->name = key.name;
su->count = 1;
if (rbinsert(seen_users, su, NULL) != 0) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
cn = strdup(key.name);
}
if (cn == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
fprintf(fp, "dn: cn=%s,%s\n", cn, base);
fprintf(fp, "objectClass: top\n");
fprintf(fp, "objectClass: sudoRole\n");
fprintf(fp, "cn: %s\n", cn);
free(cn);
TAILQ_FOREACH(m, &us->users, entries) {
print_member_ldif(fp, m->name, m->type, m->negated,
USERALIAS, "sudoUser");
}
TAILQ_FOREACH(m, &priv->hostlist, entries) {
print_member_ldif(fp, m->name, m->type, m->negated,
HOSTALIAS, "sudoHost");
}
print_cmndspec_ldif(fp, cs, &next);
fprintf(fp, "sudoOrder: %d\n\n", ++sudo_order);
}
}
debug_return_bool(!ferror(fp));
}
/*
* Print User_Specs.
*/
static bool
print_userspecs_ldif(FILE *fp, const char *base)
{
struct userspec *us;
debug_decl(print_userspecs_ldif, SUDOERS_DEBUG_UTIL)
TAILQ_FOREACH(us, &userspecs, entries) {
if (!print_userspec_ldif(fp, us, base))
debug_return_bool(false);
}
debug_return_bool(true);
}
/*
* Export the parsed sudoers file in LDIF format.
*/
bool
convert_sudoers_ldif(const char *output_file, const char *base)
{
bool ret = true;
FILE *output_fp = stdout;
debug_decl(convert_sudoers_ldif, SUDOERS_DEBUG_UTIL)
if (base == NULL) {
base = getenv("SUDOERS_BASE");
if (base == NULL)
sudo_fatalx(U_("The SUDOERS_BASE environment variable is not set"));
}
if (strcmp(output_file, "-") != 0) {
if ((output_fp = fopen(output_file, "w")) == NULL)
sudo_fatal(U_("unable to open %s"), output_file);
}
/* Create a dictionary of already-seen users. */
seen_users = rbcreate(seen_user_compare);
/* Dump global Defaults in LDIF format. */
print_global_defaults_ldif(output_fp, base);
/* Dump User_Specs in LDIF format, expanding Aliases. */
print_userspecs_ldif(output_fp, base);
/* Warn about non-translatable Defaults entries. */
warn_bound_defaults_ldif(output_fp);
/* Clean up. */
rbdestroy(seen_users, free);
(void)fflush(output_fp);
if (ferror(output_fp))
ret = false;
if (output_fp != stdout)
fclose(output_fp);
debug_return_bool(ret);
}