From ceea24b96554951c21b1241c6ec74eb61d5cc28d Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Thu, 22 Feb 2018 09:53:12 -0700 Subject: [PATCH] Initial support for parsing sudoers LDIF files in cvtsudoers. This makes it possible to convert from LDAP sudoers to a traditional sudoers file. Semantic differences between file sudoers and LDAP sudoers mean that LDIF -> sudoers is not completely equivalent. --- doc/cvtsudoers.cat | 31 +- doc/cvtsudoers.man.in | 41 ++- doc/cvtsudoers.mdoc.in | 32 +- plugins/sudoers/Makefile.in | 38 ++- plugins/sudoers/cvtsudoers.c | 103 ++++-- plugins/sudoers/cvtsudoers_json.c | 93 ++++-- plugins/sudoers/cvtsudoers_ldif.c | 511 ++++++++++++++++++++++++++++-- plugins/sudoers/ldap.c | 5 +- plugins/sudoers/ldap_common.c | 163 ++++++++-- plugins/sudoers/sssd.c | 6 +- plugins/sudoers/sudo_ldap.h | 2 +- 11 files changed, 858 insertions(+), 167 deletions(-) diff --git a/doc/cvtsudoers.cat b/doc/cvtsudoers.cat index 76293f5ec..206164d5d 100644 --- a/doc/cvtsudoers.cat +++ b/doc/cvtsudoers.cat @@ -4,14 +4,16 @@ NNAAMMEE ccvvttssuuddooeerrss - convert between sudoers file formats SSYYNNOOPPSSIISS - ccvvttssuuddooeerrss [--eehhVV] [--bb _d_n] [--ff _f_o_r_m_a_t] [--oo _o_u_t_p_u_t___f_i_l_e] [_s_u_d_o_e_r_s___f_i_l_e] + ccvvttssuuddooeerrss [--eehhVV] [--bb _d_n] [--ii _i_n_p_u_t___f_o_r_m_a_t] [--ff _o_u_t_p_u_t___f_o_r_m_a_t] + [--oo _o_u_t_p_u_t___f_i_l_e] [_i_n_p_u_t___f_i_l_e] DDEESSCCRRIIPPTTIIOONN - ccvvttssuuddooeerrss can be used to convert a policy file in _s_u_d_o_e_r_s format to - other formats. The default output format is LDIF. It is only possible - to convert a _s_u_d_o_e_r_s file that is syntactically correct. + ccvvttssuuddooeerrss can be used to convert between _s_u_d_o_e_r_s security policy file + formats. The default input format is sudoers. The default output format + is LDIF. It is only possible to convert a _s_u_d_o_e_r_s file that is + syntactically correct. - If no _s_u_d_o_e_r_s___f_i_l_e is specified, or if it is `-', the policy is read from + If no _i_n_p_u_t___f_i_l_e is specified, or if it is `-', the policy is read from the standard input. By default, the result is written to the standard output. @@ -26,7 +28,7 @@ DDEESSCCRRIIPPTTIIOONN when converting to LDIF format. --ee, ----eexxppaanndd--aalliiaasseess - Expand aliases in _s_u_d_o_e_r_s___f_i_l_e. Aliases are preserved by + Expand aliases in _i_n_p_u_t___f_i_l_e. Aliases are preserved by default when the output _f_o_r_m_a_t is JSON or sudoers. --ff _o_u_t_p_u_t___f_o_r_m_a_t, ----ffoorrmmaatt=_o_u_t_p_u_t___f_o_r_m_a_t @@ -66,6 +68,21 @@ DDEESSCCRRIIPPTTIIOONN is specified, or if it is `-', the converted _s_u_d_o_e_r_s policy will be written to the standard output. + --ii _i_n_p_u_t___f_o_r_m_a_t, ----iinnppuutt--ffoorrmmaatt=_i_n_p_u_t___f_o_r_m_a_t + Specify the input format. The following formats are + supported: + + LDIF LDIF (LDAP Data Interchange Format) files can be + exported from an LDAP server to convert security + policies used by sudoers.ldap(4). If a base DN + (distinguished name) is specified, only sudoRole + objects that match the base DN will be processed. + Not all sudoOptions specified in a sudoRole can be + translated from LDIF to sudoers format. + + sudoers Traditional sudoers format. This is the default + input format. + --VV, ----vveerrssiioonn Print the ccvvttssuuddooeerrss and _s_u_d_o_e_r_s grammar versions and exit. @@ -98,4 +115,4 @@ DDIISSCCLLAAIIMMEERR file distributed with ssuuddoo or https://www.sudo.ws/license.html for complete details. -Sudo 1.8.23 February 18, 2018 Sudo 1.8.23 +Sudo 1.8.23 February 22, 2018 Sudo 1.8.23 diff --git a/doc/cvtsudoers.man.in b/doc/cvtsudoers.man.in index e439d742e..d2892d974 100644 --- a/doc/cvtsudoers.man.in +++ b/doc/cvtsudoers.man.in @@ -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" "February 18, 2018" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" +.TH "CVTSUDOERS" "8" "February 22, 2018" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" .nh .if n .ad l .SH "NAME" @@ -27,21 +27,23 @@ \fBcvtsudoers\fR [\fB\-ehV\fR] [\fB\-b\fR\ \fIdn\fR] -[\fB\-f\fR\ \fIformat\fR] +[\fB\-i\fR\ \fIinput_format\fR] +[\fB\-f\fR\ \fIoutput_format\fR] [\fB\-o\fR\ \fIoutput_file\fR] -[\fIsudoers_file\fR] +[\fIinput_file\fR] .SH "DESCRIPTION" \fBcvtsudoers\fR -can be used to convert a policy file in +can be used to convert between \fIsudoers\fR -format to other formats. +security policy file formats. +The default input format is sudoers. The default output format is LDIF. It is only possible to convert a \fIsudoers\fR file that is syntactically correct. .PP If no -\fIsudoers_file\fR +\fIinput_file\fR is specified, or if it is \(oq-\(cq, the policy is read from the standard input. @@ -63,7 +65,7 @@ Only necessary when converting to LDIF format. .TP 12n \fB\-e\fR, \fB\--expand-aliases\fR Expand aliases in -\fIsudoers_file\fR. +\fIinput_file\fR. Aliases are preserved by default when the output \fIformat\fR is JSON or sudoers. @@ -133,6 +135,31 @@ the converted \fIsudoers\fR policy will be written to the standard output. .TP 12n +\fB\-i\fR \fIinput_format\fR, \fB\--input-format\fR=\fIinput_format\fR +Specify the input format. +The following formats are supported: +.PP +.RS 12n +.PD 0 +.TP 10n +LDIF +LDIF (LDAP Data Interchange Format) files can be exported from an LDAP +server to convert security policies used by +sudoers.ldap(@mansectform@). +If a base DN (distinguished name) is specified, only sudoRole objects +that match the base DN will be processed. +Not all sudoOptions specified in a sudoRole can be translated from +LDIF to sudoers format. +.PD +.TP 10n +sudoers +Traditional sudoers format. +This is the default input format. +.PD 0 +.PP +.RE +.PD +.TP 12n \fB\-V\fR, \fB\--version\fR Print the \fBcvtsudoers\fR diff --git a/doc/cvtsudoers.mdoc.in b/doc/cvtsudoers.mdoc.in index f05be6430..8ff3c8008 100644 --- a/doc/cvtsudoers.mdoc.in +++ b/doc/cvtsudoers.mdoc.in @@ -14,7 +14,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd February 18, 2018 +.Dd February 22, 2018 .Dt CVTSUDOERS @mansectsu@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -24,21 +24,23 @@ .Nm cvtsudoers .Op Fl ehV .Op Fl b Ar dn -.Op Fl f Ar format +.Op Fl i Ar input_format +.Op Fl f Ar output_format .Op Fl o Ar output_file -.Op Ar sudoers_file +.Op Ar input_file .Sh DESCRIPTION .Nm -can be used to convert a policy file in +can be used to convert between .Em sudoers -format to other formats. +security policy file formats. +The default input format is sudoers. The default output format is LDIF. It is only possible to convert a .Em sudoers file that is syntactically correct. .Pp If no -.Ar sudoers_file +.Ar input_file is specified, or if it is .Ql - , the policy is read from the standard input. @@ -59,7 +61,7 @@ environment variable will be used instead. Only necessary when converting to LDIF format. .It Fl e , Fl -expand-aliases Expand aliases in -.Ar sudoers_file . +.Ar input_file . Aliases are preserved by default when the output .Ar format is JSON or sudoers. @@ -108,6 +110,22 @@ is specified, or if it is the converted .Em sudoers policy will be written to the standard output. +.It Fl i Ar input_format , Fl -input-format Ns = Ns Ar input_format +Specify the input format. +The following formats are supported: +.Bl -tag -width 8n +.It LDIF +LDIF (LDAP Data Interchange Format) files can be exported from an LDAP +server to convert security policies used by +.Xr sudoers.ldap @mansectform@ . +If a base DN (distinguished name) is specified, only sudoRole objects +that match the base DN will be processed. +Not all sudoOptions specified in a sudoRole can be translated from +LDIF to sudoers format. +.It sudoers +Traditional sudoers format. +This is the default input format. +.El .It Fl V , -version Print the .Nm diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index e77cde03c..0c9e10d4f 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -162,7 +162,7 @@ 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 cvtsudoers_ldif.o \ - fmtsudoers.o locale.o stubs.o sudo_printf.o + fmtsudoers.o locale.o stubs.o sudo_printf.o ldap_common.o REPLAY_OBJS = getdate.o sudoreplay.o @@ -735,9 +735,10 @@ cvtsudoers_ldif.o: $(srcdir)/cvtsudoers_ldif.c $(devdir)/def_data.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)/redblack.h $(srcdir)/sudo_nss.h \ - $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ - $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(srcdir)/redblack.h $(srcdir)/sudo_ldap.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 \ @@ -972,9 +973,9 @@ ldap.lo: $(srcdir)/ldap.c $(devdir)/def_data.h $(devdir)/gram.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h $(incdir)/sudo_lbuf.h \ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ - $(srcdir)/sudo_ldap.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ - $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(srcdir)/sudo_ldap.h $(srcdir)/sudo_ldap_conf.h $(srcdir)/sudo_nss.h \ + $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/ldap.c ldap_common.lo: $(srcdir)/ldap_common.c $(devdir)/def_data.h $(devdir)/gram.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ @@ -982,21 +983,24 @@ ldap_common.lo: $(srcdir)/ldap_common.c $(devdir)/def_data.h $(devdir)/gram.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ $(incdir)/sudo_lbuf.h $(incdir)/sudo_plugin.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ - $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ - $(srcdir)/sudo_ldap.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/defaults.h $(srcdir)/interfaces.h \ + $(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_ldap.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/ldap_common.c -ldap_conf.lo: $(srcdir)/ldap_conf.c $(devdir)/def_data.h $(devdir)/gram.h \ +ldap_common.o: ldap_common.lo +ldap_conf.lo: $(srcdir)/ldap_conf.c $(devdir)/def_data.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ - $(incdir)/sudo_dso.h $(incdir)/sudo_fatal.h \ - $(incdir)/sudo_gettext.h $(incdir)/sudo_lbuf.h \ - $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(srcdir)/defaults.h $(srcdir)/logging.h \ - $(srcdir)/parse.h $(srcdir)/sudo_ldap.h $(srcdir)/sudo_nss.h \ - $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ - $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_lbuf.h $(incdir)/sudo_plugin.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ + $(srcdir)/sudo_ldap.h $(srcdir)/sudo_ldap_conf.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ + $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/ldap_conf.c linux_audit.lo: $(srcdir)/linux_audit.c $(devdir)/def_data.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ diff --git a/plugins/sudoers/cvtsudoers.c b/plugins/sudoers/cvtsudoers.c index 9024ce480..c24736663 100644 --- a/plugins/sudoers/cvtsudoers.c +++ b/plugins/sudoers/cvtsudoers.c @@ -47,8 +47,10 @@ #endif /* HAVE_GETOPT_LONG */ static bool convert_sudoers_sudoers(const char *output_file, bool expand_aliases); +static bool parse_sudoers(const char *input_file); extern bool convert_sudoers_json(const char *output_file, bool expand_aliases); extern bool convert_sudoers_ldif(const char *output_file, const char *base); +extern bool parse_ldif(const char *input_file, bool store_options, const char *base); extern void get_hostname(void); /* @@ -56,15 +58,13 @@ extern void get_hostname(void); */ struct sudo_user sudo_user; struct passwd *list_pw; -static const char short_opts[] = "b:ef:ho:V"; +static const char short_opts[] = "b:ef:hi:o:V"; static struct option long_opts[] = { { "base", required_argument, NULL, 'b' }, { "expand-aliases", no_argument, NULL, 'e' }, { "format", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, -#ifdef notyet { "input-format", required_argument, NULL, 'i' }, -#endif { "output", required_argument, NULL, 'o' }, { "version", no_argument, NULL, 'V' }, { NULL, no_argument, NULL, '\0' }, @@ -74,17 +74,19 @@ __dso_public int main(int argc, char *argv[]); static void help(void) __attribute__((__noreturn__)); static void usage(int); -enum output_formats { - output_json, - output_ldif, - output_sudoers +enum sudoers_formats { + format_json, + format_ldif, + format_sudoers }; int main(int argc, char *argv[]) { int ch, exitcode = EXIT_FAILURE; - enum output_formats output_format = output_ldif; + enum sudoers_formats output_format = format_ldif; + enum sudoers_formats input_format = format_sudoers; + bool store_options = true; const char *input_file = "-"; const char *output_file = "-"; const char *sudoers_base = NULL; @@ -126,11 +128,14 @@ main(int argc, char *argv[]) break; case 'f': if (strcasecmp(optarg, "json") == 0) { - output_format = output_json; + output_format = format_json; + store_options = true; } else if (strcasecmp(optarg, "ldif") == 0) { - output_format = output_ldif; + output_format = format_ldif; + store_options = true; } else if (strcasecmp(optarg, "sudoers") == 0) { - output_format = output_sudoers; + output_format = format_sudoers; + store_options = false; } else { sudo_warnx("unsupported output format %s", optarg); usage(1); @@ -139,6 +144,16 @@ main(int argc, char *argv[]) case 'h': help(); break; + case 'i': + if (strcasecmp(optarg, "ldif") == 0) { + input_format = format_ldif; + } else if (strcasecmp(optarg, "sudoers") == 0) { + input_format = format_sudoers; + } else { + sudo_warnx("unsupported input format %s", optarg); + usage(1); + } + break; case 'o': output_file = optarg; break; @@ -156,9 +171,12 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; + /* If no base DN specified, check SUDOERS_BASE. */ + if (sudoers_base == NULL) + sudoers_base = getenv("SUDOERS_BASE"); + /* Input file (defaults to stdin). */ if (argc > 0) { - /* XXX - allow multiple input files? */ if (argc > 1) usage(1); input_file = argv[0]; @@ -189,6 +207,43 @@ main(int argc, char *argv[]) if (!init_defaults()) sudo_fatalx(U_("unable to initialize sudoers default values")); + switch (input_format) { + case format_ldif: + if (!parse_ldif(input_file, store_options, sudoers_base)) + goto done; + break; + case format_sudoers: + if (!parse_sudoers(input_file)) + goto done; + break; + default: + sudo_fatalx("error: unhandled input %d", input_format); + } + + switch (output_format) { + case format_json: + exitcode = !convert_sudoers_json(output_file, expand_aliases); + break; + case format_ldif: + exitcode = !convert_sudoers_ldif(output_file, sudoers_base); + break; + case format_sudoers: + exitcode = !convert_sudoers_sudoers(output_file, expand_aliases); + break; + default: + sudo_fatalx("error: unhandled output format %d", output_format); + } + +done: + sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode); + return exitcode; +} + +static bool +parse_sudoers(const char *input_file) +{ + debug_decl(parse_sudoers, SUDOERS_DEBUG_UTIL) + /* Open sudoers file and parse it. */ if (strcmp(input_file, "-") == 0) { sudoersin = stdin; @@ -209,26 +264,9 @@ main(int argc, char *argv[]) errorfile, errorlineno); else if (errorfile != NULL) sudo_warnx(U_("parse error in %s\n"), errorfile); - goto done; - } - - switch (output_format) { - case output_json: - exitcode = !convert_sudoers_json(output_file, expand_aliases); - break; - case output_ldif: - exitcode = !convert_sudoers_ldif(output_file, sudoers_base); - break; - case output_sudoers: - exitcode = !convert_sudoers_sudoers(output_file, expand_aliases); - break; - default: - sudo_fatalx("error: unhandled output format %d", output_format); + debug_return_bool(false); } - -done: - sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode); - return exitcode; + debug_return_bool(true); } FILE * @@ -403,7 +441,8 @@ static void usage(int fatal) { (void) fprintf(fatal ? stderr : stdout, "usage: %s [-ehV] [-b dn] " - "[-f format] [-o output_file] [sudoers_file]\n", getprogname()); + "[-f output_format] [-i input_format] [-o output_file] [input_file]\n", + getprogname()); if (fatal) exit(1); } diff --git a/plugins/sudoers/cvtsudoers_json.c b/plugins/sudoers/cvtsudoers_json.c index b8a722d58..fcdf11c53 100644 --- a/plugins/sudoers/cvtsudoers_json.c +++ b/plugins/sudoers/cvtsudoers_json.c @@ -773,10 +773,11 @@ print_aliases_json(FILE *fp, int indent, bool need_comma) */ static void print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp, - bool expand_aliases, int indent) + struct defaults_list *options, bool expand_aliases, int indent) { struct cmndspec *next = *nextp; struct json_value value; + struct defaults *def; struct member *m; struct tm *tp; bool last_one; @@ -811,18 +812,20 @@ print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp, fprintf(fp, "%*s],\n", indent, ""); } - /* Print tags */ + /* Print options and tags */ if (cs->timeout > 0 || cs->notbefore != UNSPEC || cs->notafter != UNSPEC || - TAGS_SET(cs->tags)) { + TAGS_SET(cs->tags) || !TAILQ_EMPTY(options)) { struct cmndtag tag = cs->tags; + const char *prefix = "\n"; - fprintf(fp, "%*s\"Options\": [\n", indent, ""); + fprintf(fp, "%*s\"Options\": [", indent, ""); indent += 4; if (cs->timeout > 0) { value.type = JSON_NUMBER; value.u.number = cs->timeout; - print_pair_json(fp, "{ ", "command_timeout", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "command_timeout", &value, " }", indent); + prefix = ",\n"; } if (cs->notbefore != UNSPEC) { if ((tp = gmtime(&cs->notbefore)) == NULL) { @@ -833,9 +836,9 @@ print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp, } else { value.type = JSON_STRING; value.u.string = timebuf; - print_pair_json(fp, "{ ", "notbefore", &value, - (TAGS_SET(tag) || cs->notafter != UNSPEC) ? - " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "notbefore", &value, " }", indent); + prefix = ",\n"; } } } @@ -848,60 +851,83 @@ print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp, } else { value.type = JSON_STRING; value.u.string = timebuf; - print_pair_json(fp, "{ ", "notafter", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "notafter", &value, " }", indent); + prefix = ",\n"; } } } if (tag.nopasswd != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = !tag.nopasswd; - tag.nopasswd = UNSPEC; - print_pair_json(fp, "{ ", "authenticate", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "authenticate", &value, " }", indent); + prefix = ",\n"; } if (tag.noexec != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.noexec; - tag.noexec = UNSPEC; - print_pair_json(fp, "{ ", "noexec", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "noexec", &value, " }", indent); + prefix = ",\n"; } if (tag.send_mail != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.send_mail; - tag.send_mail = UNSPEC; - print_pair_json(fp, "{ ", "send_mail", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "send_mail", &value, " }", indent); + prefix = ",\n"; } if (tag.setenv != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.setenv; - tag.setenv = UNSPEC; - print_pair_json(fp, "{ ", "setenv", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "setenv", &value, " }", indent); + prefix = ",\n"; } if (tag.follow != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.follow; - tag.follow = UNSPEC; - print_pair_json(fp, "{ ", "sudoedit_follow", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "sudoedit_follow", &value, " }", indent); + prefix = ",\n"; } if (tag.log_input != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.log_input; - tag.log_input = UNSPEC; - print_pair_json(fp, "{ ", "log_input", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "log_input", &value, " }", indent); + prefix = ",\n"; } if (tag.log_output != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.log_output; - tag.log_output = UNSPEC; - print_pair_json(fp, "{ ", "log_output", &value, - TAGS_SET(tag) ? " },\n" : " }\n", indent); + fputs(prefix, fp); + print_pair_json(fp, "{ ", "log_output", &value, " }", indent); + prefix = ",\n"; + } + TAILQ_FOREACH(def, options, entries) { + int type = get_defaults_type(def); + if (type == -1) { + sudo_warnx(U_("unknown defaults entry \"%s\""), def->var); + /* XXX - just pass it through as a string anyway? */ + continue; + } + fputs(prefix, fp); + if ((type & T_MASK) == T_FLAG || def->val == NULL) { + value.type = JSON_BOOL; + value.u.boolean = def->op; + print_pair_json(fp, "{ ", def->var, &value, " }", indent); + } else if ((type & T_MASK) == T_LIST) { + print_defaults_list_json(fp, def, indent); + } else { + value.type = JSON_STRING; + value.u.string = def->val; + print_pair_json(fp, "{ ", def->var, &value, " }", indent); + } + prefix = ",\n"; } + putc('\n', fp); indent -= 4; fprintf(fp, "%*s],\n", indent, ""); } @@ -1024,7 +1050,8 @@ print_userspec_json(FILE *fp, struct userspec *us, int indent, bool expand_alias fprintf(fp, "%*s\"Cmnd_Specs\": [\n", indent, ""); indent += 4; TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) { - print_cmndspec_json(fp, cs, &next, expand_aliases, indent); + print_cmndspec_json(fp, cs, &next, &priv->defaults, + expand_aliases, indent); } indent -= 4; fprintf(fp, "%*s]\n", indent, ""); diff --git a/plugins/sudoers/cvtsudoers_ldif.c b/plugins/sudoers/cvtsudoers_ldif.c index 6da4ca58c..28e35d55e 100644 --- a/plugins/sudoers/cvtsudoers_ldif.c +++ b/plugins/sudoers/cvtsudoers_ldif.c @@ -30,6 +30,7 @@ #include #include "sudoers.h" +#include "sudo_ldap.h" #include "parse.h" #include "redblack.h" #include @@ -60,13 +61,39 @@ seen_user_free(void *v) free(su); } +/* + * Print sudoOptions from a defaults_list. + */ +static bool +print_options_ldif(FILE *fp, struct defaults_list *options) +{ + struct defaults *opt; + debug_decl(print_options_ldif, SUDOERS_DEBUG_UTIL) + + TAILQ_FOREACH(opt, options, entries) { + if (opt->type != DEFAULTS) + continue; /* don't support bound defaults */ + + if (opt->val != NULL) { + /* There is no need to double quote values here. */ + fprintf(fp, "sudoOption: %s%s%s\n", opt->var, + opt->op == '+' ? "+=" : opt->op == '-' ? "-=" : "=", opt->val); + } else { + /* Boolean flag. */ + fprintf(fp, "sudoOption: %s%s\n", opt->op == false ? "!" : "", + opt->var); + } + } + + debug_return_bool(!ferror(fp)); +} + /* * 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)) @@ -78,20 +105,7 @@ print_global_defaults_ldif(FILE *fp, const char *base) 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); - } - } + print_options_ldif(fp, &defaults); putc('\n', fp); debug_return_bool(!ferror(fp)); @@ -172,7 +186,7 @@ print_member_ldif(FILE *fp, char *name, int type, bool negated, * merge adjacent entries that are identical in all but the command. */ static void -print_cmndspec_ldif(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp) +print_cmndspec_ldif(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp, struct defaults_list *options) { struct cmndspec *next = *nextp; struct member *m; @@ -221,13 +235,15 @@ print_cmndspec_ldif(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp) } } + /* Print timeout as a sudoOption. */ + if (cs->timeout > 0) { + fprintf(fp, "sudoOption: command_timeout=%d\n", cs->timeout); + } + /* Print tags as sudoOption attributes */ - if (cs->timeout > 0 || TAGS_SET(cs->tags)) { + if (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 ? "!" : ""); } @@ -256,6 +272,7 @@ print_cmndspec_ldif(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp) fprintf(fp, "sudoOption: %slog_output\n", tag.log_output ? "" : "!"); } } + print_options_ldif(fp, options); #ifdef HAVE_SELINUX /* Print SELinux role/type */ @@ -426,7 +443,7 @@ print_userspec_ldif(FILE *fp, struct userspec *us, const char *base) HOSTALIAS, "sudoHost"); } - print_cmndspec_ldif(fp, cs, &next); + print_cmndspec_ldif(fp, cs, &next, &priv->defaults); fprintf(fp, "sudoOrder: %d\n\n", ++sudo_order); } @@ -462,9 +479,7 @@ convert_sudoers_ldif(const char *output_file, const char *base) 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 and the -b option was not specified.")); + sudo_fatalx(U_("the SUDOERS_BASE environment variable is not set and the -b option was not specified.")); } if (strcmp(output_file, "-") != 0) { @@ -495,3 +510,451 @@ convert_sudoers_ldif(const char *output_file, const char *base) debug_return_bool(ret); } + +struct ldif_string { + STAILQ_ENTRY(ldif_string) entries; + char *str; +}; +STAILQ_HEAD(ldif_str_list, ldif_string); + +struct sudo_role { + STAILQ_ENTRY(sudo_role) entries; + char *cn; + char *notbefore; + char *notafter; + double order; + struct ldif_str_list cmnds; + struct ldif_str_list hosts; + struct ldif_str_list users; + struct ldif_str_list runasusers; + struct ldif_str_list runasgroups; + struct ldif_str_list options; +}; +STAILQ_HEAD(sudo_role_list, sudo_role); + +static struct sudo_role * +sudo_role_alloc(void) +{ + struct sudo_role *role; + debug_decl(sudo_role_alloc, SUDOERS_DEBUG_UTIL) + + role = calloc(1, sizeof(*role)); + if (role != NULL) { + STAILQ_INIT(&role->cmnds); + STAILQ_INIT(&role->hosts); + STAILQ_INIT(&role->users); + STAILQ_INIT(&role->runasusers); + STAILQ_INIT(&role->runasgroups); + STAILQ_INIT(&role->options); + } + + debug_return_ptr(role); +} + +static struct ldif_string * +ldif_string_alloc(const char *s) +{ + struct ldif_string *ls; + debug_decl(ldif_string_alloc, SUDOERS_DEBUG_UTIL) + + if ((ls = malloc(sizeof(*ls))) != NULL) { + if ((ls->str = strdup(s)) == NULL) { + free(ls); + ls = NULL; + } + } + + debug_return_ptr(ls); +} + +static void +ldif_string_free(struct ldif_string *ls) +{ + free(ls->str); + free(ls); +} + +static void +str_list_free(struct ldif_str_list *strlist) +{ + struct ldif_string *first; + debug_decl(str_list_free, SUDOERS_DEBUG_UTIL) + + while ((first = STAILQ_FIRST(strlist)) != NULL) { + STAILQ_REMOVE_HEAD(strlist, entries); + ldif_string_free(first); + } + debug_return; +} + +static void +sudo_role_free(struct sudo_role *role) +{ + debug_decl(sudo_role_free, SUDOERS_DEBUG_UTIL) + + if (role != NULL) { + free(role->cn); + free(role->notbefore); + free(role->notafter); + str_list_free(&role->cmnds); + str_list_free(&role->hosts); + str_list_free(&role->users); + str_list_free(&role->runasusers); + str_list_free(&role->runasgroups); + str_list_free(&role->options); + } + + debug_return; +} + +/* + * Allocate a struct ldif_string, store str in it and + * insert into the specified strlist. + */ +static void +ldif_store_string(const char *str, struct ldif_str_list *strlist) +{ + struct ldif_string *ls; + debug_decl(ldif_store_string, SUDOERS_DEBUG_UTIL) + + while (isblank((unsigned char)*str)) + str++; + if ((ls = ldif_string_alloc(str)) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + STAILQ_INSERT_TAIL(strlist, ls, entries); + + debug_return; +} + +/* + * Iterator for sudo_ldap_role_to_priv(). + * Takes a pointer to a struct ldif_string *. + * Returns the string or NULL if we've reached the end. + */ +static char * +ldif_string_iter(void **vp) +{ + struct ldif_string *ls = *vp; + + if (ls == NULL) + return NULL; + + *vp = STAILQ_NEXT(ls, entries); + + return ls->str; +} + +static int +role_order_cmp(const void *va, const void *vb) +{ + const struct sudo_role *a = *(const struct sudo_role **)va; + const struct sudo_role *b = *(const struct sudo_role **)vb; + debug_decl(role_order_cmp, SUDOERS_DEBUG_LDAP) + + debug_return_int(b->order < a->order ? -1 : + (b->order > a->order ? 1 : 0)); +} + +/* + * Parse list of sudoOption and store in global defaults list. + */ +static void +ldif_store_options(struct ldif_str_list *options) +{ + struct defaults *d; + struct ldif_string *ls; + char *var, *val; + debug_decl(ldif_store_options, SUDOERS_DEBUG_UTIL) + + STAILQ_FOREACH(ls, options, entries) { + if ((d = calloc(1, sizeof(*d))) == NULL || + (d->binding = malloc(sizeof(*d->binding))) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + TAILQ_INIT(d->binding); + d->type = DEFAULTS; + d->op = sudo_ldap_parse_option(ls->str, &var, &val); + d->var = strdup(var); + d->val = strdup(val); + if (d->var == NULL || d->val == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + TAILQ_INSERT_TAIL(&defaults, d, entries); + } + debug_return; +} + +/* + * Parse a sudoers file in LDIF format + * https://tools.ietf.org/html/rfc2849 + * + * TODO: order negated entries at the end (different semantics) + * include the cn it came from in comments for each new privilege + * create aliases on the fly for multiple users/hosts? + */ +bool +parse_ldif(const char *input_file, bool store_options, const char *base) +{ + struct sudo_role_list roles = STAILQ_HEAD_INITIALIZER(roles); + struct sudo_role **role_array, *role = NULL; + unsigned int n, numroles = 0; + bool in_role = false; + size_t linesize = 0; + char *line = NULL; + char *savedline = NULL; + bool mismatch = false; + ssize_t len, savedlen = 0; + FILE *fp; + char *cp; + int ch; + debug_decl(parse_ldif, SUDOERS_DEBUG_UTIL) + + /* Open LDIF file and parse it. */ + if (strcmp(input_file, "-") == 0) { + fp = stdin; + input_file = "stdin"; + } else if ((fp = fopen(input_file, "r")) == NULL) + sudo_fatal(U_("unable to open %s"), input_file); + init_parser(input_file, false); + + /* Read through input, parsing into sudo_roles and global defaults. */ + for (;;) { + len = getline(&line, &linesize, fp); + + /* Trim trailing return or newline. */ + while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n')) + line[--len] = '\0'; + + /* Blank line or EOF terminates an entry. */ + if (len <= 0) { + if (in_role) { + if (role->cn != NULL && strcmp(role->cn, "defaults") == 0) { + ldif_store_options(&role->options); + sudo_role_free(role); + } else if (STAILQ_EMPTY(&role->users) || + STAILQ_EMPTY(&role->hosts) || STAILQ_EMPTY(&role->cmnds)) { + /* Incomplete role. */ + sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"), + role->cn ? role->cn : "UNKNOWN"); + sudo_role_free(role); + } else { + /* Store finished role. */ + STAILQ_INSERT_TAIL(&roles, role, entries); + numroles++; + } + role = NULL; + in_role = false; + } + if (len == -1) + break; + mismatch = false; + continue; + } + + if (savedline != NULL) { + char *tmp; + + /* Append to saved line. */ + linesize = savedlen + len + 1; + if ((tmp = realloc(savedline, linesize)) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + memcpy(tmp + savedlen, line, len + 1); + free(line); + line = tmp; + savedline = NULL; + } else { + /* Skip comment lines or records that don't match the base. */ + if (*line == '#' || mismatch) + continue; + } + + /* Check for folded line */ + if ((ch = getc(fp)) == ' ') { + /* folded line, append to the saved portion. */ + savedlen = len; + savedline = line; + line = NULL; + linesize = 0; + continue; + } else { + /* not folded, push back ch */ + ungetc(ch, fp); + } + + /* Allocate new role as needed. */ + if (role == NULL) { + if ((role = sudo_role_alloc()) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + } + + /* Parse dn and objectClass. */ + if (strncasecmp(line, "dn:", 3) == 0) { + /* Compare dn to base, if specified. */ + if (base != NULL) { + cp = line + 3; + while (isblank((unsigned char)*cp)) + cp++; + if (strncasecmp(cp, "cn=", 3) == 0) { + cp += 3; + /* XXX - handle escaped ','? */ + while (*cp != ',' && *cp != '\0') + cp++; + if (*cp == ',') + cp++; + } + if (strcasecmp(cp, base) != 0) { + /* Doesn't match base, skip the rest of it. */ + mismatch = true; + continue; + } + } + } else if (strncmp(line, "objectClass:", 12) == 0) { + cp = line + 12; + while (isblank((unsigned char)*cp)) + cp++; + if (strcmp(cp, "sudoRole") == 0) + in_role = true; + } + + /* Not in a sudoRole, keep reading. */ + if (!in_role) + continue; + + /* Part of a sudoRole, parse it. */ + if (strncmp(line, "cn:", 3) == 0) { + cp = line + 3; + while (isblank((unsigned char)*cp)) + cp++; + free(role->cn); + /* XXX - unescape chars? */ + role->cn = strdup(cp); + if (role->cn == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + } else if (strncmp(line, "sudoUser:", 9) == 0) { + ldif_store_string(line + 9, &role->users); + } else if (strncmp(line, "sudoHost:", 9) == 0) { + ldif_store_string(line + 9, &role->hosts); + } else if (strncmp(line, "sudoRunAs:", 10) == 0) { + ldif_store_string(line + 10, &role->runasusers); + } else if (strncmp(line, "sudoRunAsUser:", 14) == 0) { + ldif_store_string(line + 14, &role->runasusers); + } else if (strncmp(line, "sudoRunAsGroup:", 15) == 0) { + ldif_store_string(line + 15, &role->runasgroups); + } else if (strncmp(line, "sudoCommand:", 12) == 0) { + ldif_store_string(line + 12, &role->cmnds); + } else if (strncmp(line, "sudoOption:", 11) == 0) { + ldif_store_string(line + 11, &role->options); + } else if (strncmp(line, "sudoNotBefore:", 14) == 0) { + cp = line + 14; + while (isblank((unsigned char)*cp)) + cp++; + free(role->notbefore); + role->notbefore = strdup(cp); + if (role->notbefore == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + } else if (strncmp(line, "sudoNotAfter:", 13) == 0) { + cp = line + 13; + while (isblank((unsigned char)*cp)) + cp++; + free(role->notafter); + role->notafter = strdup(cp); + if (role->notafter == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + } + } + + /* Convert from list of roles to array and sort by order. */ + role_array = reallocarray(NULL, numroles + 1, sizeof(*role_array)); + for (n = 0; (role = STAILQ_FIRST(&roles)) != NULL; n++) { + STAILQ_REMOVE_HEAD(&roles, entries); + role_array[n] = role; + } + role_array[n] = NULL; + qsort(role_array, numroles, sizeof(*role_array), role_order_cmp); + + /* + * Iterate over roles in sorted order, using sudo_ldap_role_to_priv() + * to convert to privilege and store in userspecs. + * TODO: merge multiple users with the same sudoOrder? + * TODO: use cn to create a UserAlias if multiple users in it? + */ + for (n = 0; n < numroles; n++) { + struct privilege *priv; + struct ldif_string *ls; + struct userspec *us; + struct member *m; + + /* Allocate a new userspec and fill in the user list. */ + if ((us = calloc(1, sizeof(*us))) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + TAILQ_INIT(&us->privileges); + TAILQ_INIT(&us->users); + + role = role_array[n]; + STAILQ_FOREACH(ls, &role->users, entries) { + char *user = ls->str; + + if ((m = calloc(1, sizeof(*m))) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + m->negated = sudo_ldap_is_negated(&user); + m->name = strdup(user); + if (m->name == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + if (strcmp(user, "ALL") == 0) { + m->type = ALL; + } else if (*user == '+') { + m->type = NETGROUP; + } else if (*user == '%') { + m->type = USERGROUP; + } else { + m->type = WORD; + } + TAILQ_INSERT_TAIL(&us->users, m, entries); + } + + /* Convert role to sudoers privilege. */ + priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(&role->hosts), + STAILQ_FIRST(&role->runasusers), STAILQ_FIRST(&role->runasgroups), + STAILQ_FIRST(&role->cmnds), STAILQ_FIRST(&role->options), + role->notbefore, role->notafter, true, store_options, + ldif_string_iter); + if (priv == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + TAILQ_INSERT_TAIL(&us->privileges, priv, entries); + + /* Add finished userspec to the list. */ + TAILQ_INSERT_TAIL(&userspecs, us, entries); + } + + /* Clean up. */ + for (n = 0; n < numroles; n++) + sudo_role_free(role_array[n]); + free(role_array); + + if (fp != stdin) + fclose(fp); + + debug_return_bool(true); +} diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c index e34ad1147..b35f1ee1b 100644 --- a/plugins/sudoers/ldap.c +++ b/plugins/sudoers/ldap.c @@ -1556,9 +1556,10 @@ ldap_to_sudoers(LDAP *ld, struct ldap_result *lres) /* Parse sudoOptions. */ opts = ldap_get_values_len(ld, entry, "sudoOption"); - priv = sudo_ldap_role_to_priv(cn, runasusers, runasgroups, + priv = sudo_ldap_role_to_priv(cn, NULL, runasusers, runasgroups, cmnds, opts, notbefore ? notbefore[0]->bv_val : NULL, - notafter ? notafter[0]->bv_val : NULL, berval_iter); + notafter ? notafter[0]->bv_val : NULL, false, long_list, + berval_iter); /* Cleanup */ if (cn != NULL) diff --git a/plugins/sudoers/ldap_common.c b/plugins/sudoers/ldap_common.c index 097568b13..9ecc847ac 100644 --- a/plugins/sudoers/ldap_common.c +++ b/plugins/sudoers/ldap_common.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #ifdef HAVE_STRING_H @@ -28,12 +29,11 @@ # include #endif /* HAVE_STRINGS_H */ #include -#ifdef HAVE_LBER_H -# include -#endif -#include +#include +#include #include "sudoers.h" +#include "interfaces.h" #include "parse.h" #include "gram.h" #include "sudo_lbuf.h" @@ -112,7 +112,7 @@ sudo_ldap_parse_option(char *optstr, char **varp, char **valp) } /* - * Convert an array to a member list. + * Convert an array of user/group names to a member list. * The caller is responsible for freeing the returned struct member_list. */ static struct member_list * @@ -180,14 +180,77 @@ bad: debug_return_ptr(NULL); } +static bool +is_address(char *host) +{ + union sudo_in_addr_un addr; + bool ret = false; + char *slash; + debug_decl(is_address, SUDOERS_DEBUG_LDAP) + + /* Check for mask, not currently parsed. */ + if ((slash = strchr(host, '/')) != NULL) + *slash = '\0'; + + if (inet_pton(AF_INET, host, &addr.ip4) == 1) + ret = true; +#ifdef HAVE_STRUCT_IN6_ADDR + else if (inet_pton(AF_INET6, host, &addr.ip6) == 1) + ret = true; +#endif + + if (slash != NULL) + *slash = '/'; + + debug_return_bool(ret); +} + +static struct member * +host_to_member(char *host) +{ + struct member *m; + debug_decl(host_to_member, SUDOERS_DEBUG_LDAP) + + if ((m = calloc(1, sizeof(*m))) == NULL) + goto oom; + m->negated = sudo_ldap_is_negated(&host); + m->name = strdup(host); + if (m->name == NULL) + goto oom; + switch (*host) { + case '+': + m->type = NETGROUP; + break; + case 'A': + if (strcmp(host, "ALL") == 0) { + m->type = ALL; + break; + } + /* FALLTHROUGH */ + default: + if (is_address(host)) { + m->type = NTWKADDR; + } else { + m->type = WORD; + } + break; + } + + debug_return_ptr(m); +oom: + free(m); + debug_return_ptr(NULL); +} + /* * Convert an LDAP sudoRole to a sudoers privilege. * Pass in struct berval ** for LDAP or char *** for SSSD. */ struct privilege * -sudo_ldap_role_to_priv(const char *cn, void *runasusers, void *runasgroups, - void *cmnds, void *opts, const char *notbefore, - const char *notafter, sudo_ldap_iter_t iter) +sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers, + void *runasgroups, void *cmnds, void *opts, const char *notbefore, + const char *notafter, bool warnings, bool store_options, + sudo_ldap_iter_t iter) { struct cmndspec *cmndspec = NULL; struct cmndspec *prev_cmndspec = NULL; @@ -207,17 +270,27 @@ sudo_ldap_role_to_priv(const char *cn, void *runasusers, void *runasgroups, if (priv->ldap_role == NULL) goto oom; - /* The host has already matched, use ALL as wildcard. */ - if ((m = calloc(1, sizeof(*m))) == NULL) - goto oom; - m->type = ALL; - TAILQ_INSERT_TAIL(&priv->hostlist, m, entries); + if (hosts == NULL) { + /* The host has already matched, use ALL as wildcard. */ + if ((m = calloc(1, sizeof(*m))) == NULL) + goto oom; + m->type = ALL; + TAILQ_INSERT_TAIL(&priv->hostlist, m, entries); + } else { + char *host; + while ((host = iter(&hosts)) != NULL) { + if ((m = host_to_member(host)) == NULL) + goto oom; + TAILQ_INSERT_TAIL(&priv->hostlist, m, entries); + } + } /* * Parse sudoCommands and add to cmndlist. */ while ((cmnd = iter(&cmnds)) != NULL) { char *args; + struct sudo_digest digest; /* Allocate storage upfront. */ cmndspec = calloc(1, sizeof(*cmndspec)); @@ -237,23 +310,30 @@ sudo_ldap_role_to_priv(const char *cn, void *runasusers, void *runasgroups, cmndspec->notafter = UNSPEC; cmndspec->timeout = UNSPEC; - /* Fill in command. */ + /* Fill in member. */ + m->type = COMMAND; + m->negated = sudo_ldap_is_negated(&cmnd); + m->name = (char *)c; + + /* Fill in command with optional digest. */ + if (sudo_ldap_extract_digest(&cmnd, &digest) != NULL) { + if ((c->digest = malloc(sizeof(*c->digest))) == NULL) { + free_member(m); + goto oom; + } + *c->digest = digest; + } if ((args = strpbrk(cmnd, " \t")) != NULL) { *args++ = '\0'; if ((c->args = strdup(args)) == NULL) { - free(c); - free(m); + free_member(m); goto oom; } } if ((c->cmnd = strdup(cmnd)) == NULL) { - free(c->args); - free(c); - free(m); + free_member(m); goto oom; } - m->type = COMMAND; - m->name = (char *)c; cmndspec->cmnd = m; if (prev_cmndspec != NULL) { @@ -322,10 +402,11 @@ sudo_ldap_role_to_priv(const char *cn, void *runasusers, void *runasgroups, goto oom; } #endif /* HAVE_PRIV_SET */ - } else if (long_list) { + } else if (store_options) { struct defaults *def = calloc(1, sizeof(*def)); if (def == NULL) goto oom; + def->type = DEFAULTS; def->op = op; if ((def->var = strdup(var)) == NULL) { free(def); @@ -341,19 +422,33 @@ sudo_ldap_role_to_priv(const char *cn, void *runasusers, void *runasgroups, TAILQ_INSERT_TAIL(&priv->defaults, def, entries); } else { /* Convert to tags. */ - if (op != true && op != false) + bool handled = true; + + if (op == true || op == false) { + if (strcmp(var, "authenticate") == 0) { + cmndspec->tags.nopasswd = op == false; + } else if (strcmp(var, "sudoedit_follow") == 0) { + cmndspec->tags.follow = op == true; + } else if (strcmp(var, "noexec") == 0) { + cmndspec->tags.noexec = op == true; + } else if (strcmp(var, "setenv") == 0) { + cmndspec->tags.setenv = op == true; + } else if (strcmp(var, "mail_all_cmnds") == 0 || + strcmp(var, "mail_always") == 0) { + cmndspec->tags.send_mail = op == true; + } else { + handled = false; + } + } else { + handled = false; + } + if (!handled && warnings) { + if (val != NULL) { + sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), var, op == '+' ? "+=" : op == '-' ? "-=" : "=", val); + } else { + sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), op == false ? "!" : "", var, ""); + } continue; - if (strcmp(var, "authenticate") == 0) { - cmndspec->tags.nopasswd = op == false; - } else if (strcmp(var, "sudoedit_follow") == 0) { - cmndspec->tags.follow = op == true; - } else if (strcmp(var, "noexec") == 0) { - cmndspec->tags.noexec = op == true; - } else if (strcmp(var, "setenv") == 0) { - cmndspec->tags.setenv = op == true; - } else if (strcmp(var, "mail_all_cmnds") == 0 || - strcmp(var, "mail_always") == 0) { - cmndspec->tags.send_mail = op == true; } } } diff --git a/plugins/sudoers/sssd.c b/plugins/sudoers/sssd.c index c0718d385..8cee7e684 100644 --- a/plugins/sudoers/sssd.c +++ b/plugins/sudoers/sssd.c @@ -1476,9 +1476,9 @@ sss_to_sudoers(struct sudo_sss_handle *handle, struct sss_sudo_result *sss_resul /* Parse sudoOptions. */ handle->fn_get_values(rule, "sudoOption", &opts); - priv = sudo_ldap_role_to_priv(cn, runasusers, runasgroups, cmnds, opts, - notbefore ? notbefore[0] : NULL, notafter ? notafter[0] : NULL, - val_array_iter); + priv = sudo_ldap_role_to_priv(cn, NULL, runasusers, runasgroups, + cmnds, opts, notbefore ? notbefore[0] : NULL, + notafter ? notafter[0] : NULL, false, long_list, val_array_iter); /* Cleanup */ if (cn_array != NULL) diff --git a/plugins/sudoers/sudo_ldap.h b/plugins/sudoers/sudo_ldap.h index 788072590..aa4a2bde6 100644 --- a/plugins/sudoers/sudo_ldap.h +++ b/plugins/sudoers/sudo_ldap.h @@ -23,7 +23,7 @@ typedef char * (*sudo_ldap_iter_t)(void **); /* ldap_common.c */ bool sudo_ldap_is_negated(char **valp); int sudo_ldap_parse_option(char *optstr, char **varp, char **valp); -struct privilege *sudo_ldap_role_to_priv(const char *cn, void *runasusers, void *runasgroups, void *cmnds, void *opts, const char *notbefore, const char *notafter, sudo_ldap_iter_t iter); +struct privilege *sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers, void *runasgroups, void *cmnds, void *opts, const char *notbefore, const char *notafter, bool warnings, bool store_options, sudo_ldap_iter_t iter); struct sudo_digest *sudo_ldap_extract_digest(char **cmnd, struct sudo_digest *digest); #endif /* SUDOERS_LDAP_H */ -- 2.40.0