]> granicus.if.org Git - sudo/commitdiff
Initial support for parsing sudoers LDIF files in cvtsudoers.
authorTodd C. Miller <Todd.Miller@sudo.ws>
Thu, 22 Feb 2018 16:53:12 +0000 (09:53 -0700)
committerTodd C. Miller <Todd.Miller@sudo.ws>
Thu, 22 Feb 2018 16:53:12 +0000 (09:53 -0700)
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
doc/cvtsudoers.man.in
doc/cvtsudoers.mdoc.in
plugins/sudoers/Makefile.in
plugins/sudoers/cvtsudoers.c
plugins/sudoers/cvtsudoers_json.c
plugins/sudoers/cvtsudoers_ldif.c
plugins/sudoers/ldap.c
plugins/sudoers/ldap_common.c
plugins/sudoers/sssd.c
plugins/sudoers/sudo_ldap.h

index 76293f5ecea7bbbf38f98d806697f61165b0c402..206164d5ddb8071c5f69afa6b653617cac49a368 100644 (file)
@@ -4,14 +4,16 @@ N\bNA\bAM\bME\bE
      c\bcv\bvt\bts\bsu\bud\bdo\boe\ber\brs\bs - convert between sudoers file formats
 
 S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS
-     c\bcv\bvt\bts\bsu\bud\bdo\boe\ber\brs\bs [-\b-e\beh\bhV\bV] [-\b-b\bb _\bd_\bn] [-\b-f\bf _\bf_\bo_\br_\bm_\ba_\bt] [-\b-o\bo _\bo_\bu_\bt_\bp_\bu_\bt_\b__\bf_\bi_\bl_\be] [_\bs_\bu_\bd_\bo_\be_\br_\bs_\b__\bf_\bi_\bl_\be]
+     c\bcv\bvt\bts\bsu\bud\bdo\boe\ber\brs\bs [-\b-e\beh\bhV\bV] [-\b-b\bb _\bd_\bn] [-\b-i\bi _\bi_\bn_\bp_\bu_\bt_\b__\bf_\bo_\br_\bm_\ba_\bt] [-\b-f\bf _\bo_\bu_\bt_\bp_\bu_\bt_\b__\bf_\bo_\br_\bm_\ba_\bt]
+                [-\b-o\bo _\bo_\bu_\bt_\bp_\bu_\bt_\b__\bf_\bi_\bl_\be] [_\bi_\bn_\bp_\bu_\bt_\b__\bf_\bi_\bl_\be]
 
 D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
-     c\bcv\bvt\bts\bsu\bud\bdo\boe\ber\brs\bs can be used to convert a policy file in _\bs_\bu_\bd_\bo_\be_\br_\bs format to
-     other formats.  The default output format is LDIF.  It is only possible
-     to convert a _\bs_\bu_\bd_\bo_\be_\br_\bs file that is syntactically correct.
+     c\bcv\bvt\bts\bsu\bud\bdo\boe\ber\brs\bs can be used to convert between _\bs_\bu_\bd_\bo_\be_\br_\bs security policy file
+     formats.  The default input format is sudoers.  The default output format
+     is LDIF.  It is only possible to convert a _\bs_\bu_\bd_\bo_\be_\br_\bs file that is
+     syntactically correct.
 
-     If no _\bs_\bu_\bd_\bo_\be_\br_\bs_\b__\bf_\bi_\bl_\be is specified, or if it is `-', the policy is read from
+     If no _\bi_\bn_\bp_\bu_\bt_\b__\bf_\bi_\bl_\be 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 @@ D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
                  when converting to LDIF format.
 
      -\b-e\be, -\b--\b-e\bex\bxp\bpa\ban\bnd\bd-\b-a\bal\bli\bia\bas\bse\bes\bs
-                 Expand aliases in _\bs_\bu_\bd_\bo_\be_\br_\bs_\b__\bf_\bi_\bl_\be.  Aliases are preserved by
+                 Expand aliases in _\bi_\bn_\bp_\bu_\bt_\b__\bf_\bi_\bl_\be.  Aliases are preserved by
                  default when the output _\bf_\bo_\br_\bm_\ba_\bt is JSON or sudoers.
 
      -\b-f\bf _\bo_\bu_\bt_\bp_\bu_\bt_\b__\bf_\bo_\br_\bm_\ba_\bt, -\b--\b-f\bfo\bor\brm\bma\bat\bt=_\bo_\bu_\bt_\bp_\bu_\bt_\b__\bf_\bo_\br_\bm_\ba_\bt
@@ -66,6 +68,21 @@ D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
                  is specified, or if it is `-', the converted _\bs_\bu_\bd_\bo_\be_\br_\bs policy
                  will be written to the standard output.
 
+     -\b-i\bi _\bi_\bn_\bp_\bu_\bt_\b__\bf_\bo_\br_\bm_\ba_\bt, -\b--\b-i\bin\bnp\bpu\but\bt-\b-f\bfo\bor\brm\bma\bat\bt=_\bi_\bn_\bp_\bu_\bt_\b__\bf_\bo_\br_\bm_\ba_\bt
+                 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.
+
      -\b-V\bV, -\b--\b-v\bve\ber\brs\bsi\bio\bon\bn
                  Print the c\bcv\bvt\bts\bsu\bud\bdo\boe\ber\brs\bs and _\bs_\bu_\bd_\bo_\be_\br_\bs grammar versions and exit.
 
@@ -98,4 +115,4 @@ D\bDI\bIS\bSC\bCL\bLA\bAI\bIM\bME\bER\bR
      file distributed with s\bsu\bud\bdo\bo 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
index e439d742eaa1dfa7e8ca71ed6f9130dea5f03e98..d2892d9741bba6123cd7086b9f3056bb27b2ed58 100644 (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" "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"
 \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
index f05be64305cde48e8538e42ff00fc0da068dd203..8ff3c800838b59fc13d34490c1ff2e554c6450ee 100644 (file)
@@ -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
 .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
index e77cde03c29956757ad05d656a2b8ea0640853bf..0c9e10d4f7cc6e8880d939694a40958d9bd6299f 100644 (file)
@@ -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 \
index 9024ce48054ae0c116da3131d0920f71f12f3cec..c247366638bfb662193578b44d9b81bf025c13df 100644 (file)
 #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);
 }
index b8a722d58dd69856507d5bc438dfdad4bb71deec..fcdf11c53f93e8198ea0bb5a53181249183e668b 100644 (file)
@@ -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, "");
index 6da4ca58ce690c60cf471a0eb9fe9d056446614e..28e35d55ed673a6551f7aba5eee12e26ddac5007 100644 (file)
@@ -30,6 +30,7 @@
 #include <ctype.h>
 
 #include "sudoers.h"
+#include "sudo_ldap.h"
 #include "parse.h"
 #include "redblack.h"
 #include <gram.h>
@@ -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);
+}
index e34ad1147e2816117747496e1b2a24168a4c9d96..b35f1ee1b450d59bfe4bc80dd440f617474fea16 100644 (file)
@@ -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)
index 097568b1309d0b7b3fe91349beb4c0a9cb4bd783..9ecc847aca9a4d7e79ecfc11d6a941e7125a6e60 100644 (file)
@@ -19,6 +19,7 @@
 #include <config.h>
 
 #include <sys/types.h>
+#include <sys/socket.h>
 #include <stdio.h>
 #include <stdlib.h>
 #ifdef HAVE_STRING_H
 # include <strings.h>
 #endif /* HAVE_STRINGS_H */
 #include <ctype.h>
-#ifdef HAVE_LBER_H
-# include <lber.h>
-#endif
-#include <ldap.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
 
 #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;
                        }
                    }
                }
index c0718d38540168bebeebd5bf3acfaafc7a29b599..8cee7e68436113a7a83efb4afb046dc7b80dc707 100644 (file)
@@ -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)
index 7880725906e508e8dbdaf193f119a8d4ac29407c..aa4a2bde672f0d5365f365e12fc9d91d1b13f0ce 100644 (file)
@@ -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 */