summaryrefslogtreecommitdiffstats
path: root/plugins/sudoers/cvtsudoers_csv.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/sudoers/cvtsudoers_csv.c720
1 files changed, 720 insertions, 0 deletions
diff --git a/plugins/sudoers/cvtsudoers_csv.c b/plugins/sudoers/cvtsudoers_csv.c
new file mode 100644
index 0000000..d5cb6a5
--- /dev/null
+++ b/plugins/sudoers/cvtsudoers_csv.c
@@ -0,0 +1,720 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2021-2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <sudoers.h>
+#include <cvtsudoers.h>
+#include <gram.h>
+
+static void print_member_list_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree, struct member_list *members, bool negated, short alias_type, bool expand_aliases);
+
+/*
+ * Print sudoOptions from a defaults_list.
+ */
+static bool
+print_options_csv(FILE *fp, struct defaults_list *options, bool need_comma)
+{
+ struct defaults *opt;
+ debug_decl(print_options_csv, SUDOERS_DEBUG_UTIL);
+
+ TAILQ_FOREACH(opt, options, entries) {
+ if (opt->val != NULL) {
+ /* There is no need to double quote values here. */
+ fprintf(fp, "%s%s%s%s", need_comma ? "," : "", opt->var,
+ opt->op == '+' ? "+=" : opt->op == '-' ? "-=" : "=", opt->val);
+ } else {
+ /* Boolean flag. */
+ fprintf(fp, "%s%s%s%s", need_comma ? "," : "", opt->var,
+ opt->op == false ? "!" : "", opt->var);
+ }
+ need_comma = true;
+ }
+
+ debug_return_bool(!ferror(fp));
+}
+
+/*
+ * Map a Defaults type to string.
+ */
+static const char *
+defaults_type_to_string(int defaults_type)
+{
+ switch (defaults_type) {
+ case DEFAULTS:
+ return "defaults";
+ case DEFAULTS_CMND:
+ return "defaults_command";
+ case DEFAULTS_HOST:
+ return "defaults_host";
+ case DEFAULTS_RUNAS:
+ return "defaults_runas";
+ case DEFAULTS_USER:
+ return "defaults_user";
+ default:
+ sudo_fatalx_nodebug("unexpected defaults type %d", defaults_type);
+ }
+}
+
+/*
+ * Map a Defaults type to an alias type.
+ */
+static short
+defaults_to_alias_type(int defaults_type)
+{
+ switch (defaults_type) {
+ case DEFAULTS_CMND:
+ return CMNDALIAS;
+ case DEFAULTS_HOST:
+ return HOSTALIAS;
+ case DEFAULTS_RUNAS:
+ return RUNASALIAS;
+ case DEFAULTS_USER:
+ return USERALIAS;
+ default:
+ sudo_fatalx_nodebug("unexpected defaults type %d", defaults_type);
+ }
+}
+
+/*
+ * Print a string, performing quoting as needed.
+ * If a field includes a comma it must be double-quoted.
+ * Double quotes are replaced by a pair of double-quotes.
+ * XXX - rewrite this
+ */
+static bool
+print_csv_string(FILE * restrict fp, const char * restrict str, bool quoted)
+{
+ const char *src = str;
+ char *dst, *newstr;
+ size_t len, newsize;
+ bool quote_it = false;
+ bool ret = true;
+ debug_decl(print_csv_string, SUDOERS_DEBUG_UTIL);
+
+ len = strcspn(str, quoted ? "\"" : "\",");
+ if (str[len] == '\0') {
+ /* nothing to escape */
+ debug_return_bool(fputs(str, fp) != EOF);
+ }
+
+ if (!quoted && strchr(str + len, ',') != NULL)
+ quote_it = true;
+
+ /* String includes characters we need to escape. */
+ newsize = len + 2 + (strlen(len + str) * 2) + 1;
+ if ((newstr = malloc(newsize)) == NULL)
+ debug_return_bool(false);
+ dst = newstr;
+
+ if (quote_it)
+ *dst++ = '"';
+ while (*src != '\0') {
+ if (*src == '"')
+ *dst++ = '"';
+ *dst++ = *src++;
+ }
+ if (quote_it)
+ *dst++ = '"';
+ *dst = '\0';
+
+ if (fputs(newstr, fp) == EOF)
+ ret = false;
+ free(newstr);
+
+ debug_return_bool(ret);
+}
+
+/*
+ * Format a sudo_command as a string.
+ * Returns the formatted, dynamically allocated string or dies on error.
+ */
+static char *
+format_cmnd(struct sudo_command *c, bool negated)
+{
+ struct command_digest *digest;
+ char *buf, *cp, *cmnd;
+ size_t bufsiz;
+ int len;
+ debug_decl(format_cmnd, SUDOERS_DEBUG_UTIL);
+
+ cmnd = c->cmnd ? c->cmnd : (char *)"ALL";
+ bufsiz = negated + strlen(cmnd) + 1;
+ if (c->args != NULL)
+ bufsiz += 1 + strlen(c->args);
+ TAILQ_FOREACH(digest, &c->digests, entries) {
+ bufsiz += strlen(digest_type_to_name(digest->digest_type)) + 1 +
+ strlen(digest->digest_str) + 1;
+ if (TAILQ_NEXT(digest, entries) != NULL)
+ bufsiz += 2;
+ }
+
+ if ((buf = malloc(bufsiz)) == NULL) {
+ sudo_fatalx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ }
+
+ cp = buf;
+ TAILQ_FOREACH(digest, &c->digests, entries) {
+ len = snprintf(cp, bufsiz - (size_t)(cp - buf), "%s:%s%s ",
+ digest_type_to_name(digest->digest_type), digest->digest_str,
+ TAILQ_NEXT(digest, entries) ? "," : "");
+ if (len < 0 || len >= (int)bufsiz - (cp - buf))
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+ cp += len;
+ }
+
+ len = snprintf(cp, bufsiz - (size_t)(cp - buf), "%s%s%s%s",
+ negated ? "!" : "", cmnd, c->args ? " " : "", c->args ? c->args : "");
+ if (len < 0 || len >= (int)bufsiz - (cp - buf))
+ sudo_fatalx(U_("internal error, %s overflow"), __func__);
+
+ debug_return_str(buf);
+}
+
+/*
+ * Print struct member in CSV format as the specified attribute.
+ * See print_member_int() in parse.c.
+ */
+static void
+print_member_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
+ char *name, int type, bool negated, bool quoted, short alias_type,
+ bool expand_aliases)
+{
+ struct alias *a;
+ char *str;
+ int len;
+ debug_decl(print_member_csv, SUDOERS_DEBUG_UTIL);
+
+ switch (type) {
+ case MYSELF:
+ /* Only valid for sudoRunasUser */
+ break;
+ case ALL:
+ if (name == NULL) {
+ fputs(negated ? "!ALL" : "ALL", fp);
+ break;
+ }
+ FALLTHROUGH;
+ case COMMAND:
+ str = format_cmnd((struct sudo_command *)name, negated);
+ print_csv_string(fp, str, quoted);
+ free(str);
+ break;
+ case ALIAS:
+ if (expand_aliases) {
+ if ((a = alias_get(parse_tree, name, alias_type)) != NULL) {
+ print_member_list_csv(fp, parse_tree, &a->members, negated,
+ alias_type, expand_aliases);
+ alias_put(a);
+ break;
+ }
+ }
+ FALLTHROUGH;
+ default:
+ len = asprintf(&str, "%s%s", negated ? "!" : "", name);
+ if (len == -1) {
+ sudo_fatalx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ }
+ print_csv_string(fp, str, quoted);
+ free(str);
+ break;
+ }
+
+ debug_return;
+}
+
+/*
+ * Print list of struct member in CSV format as the specified attribute.
+ * See print_member_int() in parse.c.
+ */
+static void
+print_member_list_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
+ struct member_list *members, bool negated, short alias_type,
+ bool expand_aliases)
+{
+ struct member *m, *next;
+ debug_decl(print_member_list_csv, SUDOERS_DEBUG_UTIL);
+
+ if (TAILQ_EMPTY(members))
+ debug_return;
+
+ if (TAILQ_FIRST(members) != TAILQ_LAST(members, member_list))
+ putc('"', fp);
+ TAILQ_FOREACH_SAFE(m, members, entries, next) {
+ print_member_csv(fp, parse_tree, m->name, m->type,
+ negated ? !m->negated : m->negated, true, alias_type,
+ expand_aliases);
+ if (next != NULL)
+ putc(',', fp);
+ }
+ if (TAILQ_FIRST(members) != TAILQ_LAST(members, member_list))
+ putc('"', fp);
+
+ debug_return;
+}
+
+/*
+ * Print the binding for a Defaults entry of the specified type.
+ */
+static void
+print_defaults_binding_csv(FILE *fp,
+ const struct sudoers_parse_tree *parse_tree,
+ struct defaults_binding *binding, int type, bool expand_aliases)
+{
+ short alias_type;
+ debug_decl(print_defaults_binding_csv, SUDOERS_DEBUG_UTIL);
+
+ if (type != DEFAULTS) {
+ /* Print each member object in binding. */
+ alias_type = defaults_to_alias_type(type);
+ print_member_list_csv(fp, parse_tree, &binding->members, false,
+ alias_type, expand_aliases);
+ }
+
+ debug_return;
+}
+
+/*
+ * Print all Defaults in CSV format:
+ *
+ * defaults,binding,name,operator,value
+ *
+ * where "operator" is one of +=, -=, or =
+ * and boolean flags use true/false for the value.
+ */
+static bool
+print_defaults_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
+ bool expand_aliases)
+{
+ struct defaults *def;
+ debug_decl(print_defaults_csv, SUDOERS_DEBUG_UTIL);
+
+ if (TAILQ_EMPTY(&parse_tree->defaults))
+ debug_return_bool(true);
+
+ /* Heading line. */
+ fputs("defaults_type,binding,name,operator,value\n", fp);
+
+ TAILQ_FOREACH(def, &parse_tree->defaults, entries) {
+ const char *operator;
+
+ /* Print operator */
+ switch (def->op) {
+ case '+':
+ operator = "+=";
+ break;
+ case '-':
+ operator = "-=";
+ break;
+ case true:
+ case false:
+ operator = "=";
+ break;
+ default:
+ sudo_warnx("internal error: unexpected defaults op %d", def->op);
+ continue;
+ }
+
+ /*
+ * For CSV we use a separate entry for each Defaults setting,
+ * even if they were on the same line in sudoers.
+ */
+ fprintf(fp, "%s,", defaults_type_to_string(def->type));
+
+ /* Print binding (if any), which could be a list. */
+ print_defaults_binding_csv(fp, parse_tree, def->binding, def->type,
+ expand_aliases);
+
+ /* Print Defaults name + operator. */
+ fprintf(fp, ",%s,%s,", def->var, operator);
+
+ /* Print defaults value. */
+ /* XXX - differentiate between lists and single values? */
+ if (def->val == NULL) {
+ fputs(def->op == true ? "true" : "false", fp);
+ } else {
+ /* Does not handle lists specially. */
+ print_csv_string(fp, def->val, false);
+ }
+ putc('\n', fp);
+ }
+ putc('\n', fp);
+ fflush(fp);
+
+ debug_return_bool(!ferror(fp));
+}
+
+/*
+ * Callback for alias_apply() to print an alias entry.
+ */
+static int
+print_alias_csv(struct sudoers_parse_tree *parse_tree, struct alias *a, void *v)
+{
+ FILE *fp = v;
+ const char *title;
+ debug_decl(print_alias_csv, SUDOERS_DEBUG_UTIL);
+
+ title = alias_type_to_string(a->type);
+ if (title == NULL) {
+ sudo_warnx("unexpected alias type %d", a->type);
+ debug_return_int(0);
+ }
+
+ fprintf(fp, "%s,%s,", title, a->name);
+ print_member_list_csv(fp, parse_tree, &a->members, false, a->type, false);
+ putc('\n', fp);
+ debug_return_int(0);
+}
+
+/*
+ * Print all aliases in CSV format:
+ */
+static bool
+print_aliases_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree)
+{
+ debug_decl(print_aliases_csv, SUDOERS_DEBUG_UTIL);
+
+ if (TAILQ_EMPTY(&parse_tree->defaults))
+ debug_return_bool(true);
+
+ /* Heading line. */
+ fputs("alias_type,alias_name,members\n", fp);
+
+ /* print_alias_csv() does not modify parse_tree. */
+ alias_apply((struct sudoers_parse_tree *)parse_tree, print_alias_csv, fp);
+ putc('\n', fp);
+
+ debug_return_bool(true);
+}
+
+/*
+ * Print a Cmnd_Spec in CSV format.
+ */
+static void
+print_cmndspec_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
+ struct cmndspec *cs, struct cmndspec **nextp,
+ struct defaults_list *options, bool expand_aliases)
+{
+ char timebuf[sizeof("20120727121554Z")];
+ struct cmndspec *next = *nextp;
+ bool need_comma = false;
+ struct member *m;
+ struct tm gmt;
+ bool last_one, quoted = false;
+ size_t len;
+ debug_decl(print_cmndspec_csv, SUDOERS_DEBUG_UTIL);
+
+ if (cs->runasuserlist != NULL) {
+ print_member_list_csv(fp, parse_tree, cs->runasuserlist, false,
+ RUNASALIAS, expand_aliases);
+ }
+ putc(',', fp);
+
+ if (cs->runasgrouplist != NULL) {
+ print_member_list_csv(fp, parse_tree, cs->runasgrouplist, false,
+ RUNASALIAS, expand_aliases);
+ }
+ putc(',', fp);
+
+ /* We don't know how many options there will be so always quote it. */
+ putc('"', fp);
+ if (cs->notbefore != UNSPEC) {
+ if (gmtime_r(&cs->notbefore, &gmt) == NULL) {
+ sudo_warn("%s", U_("unable to get GMT time"));
+ } else {
+ timebuf[sizeof(timebuf) - 1] = '\0';
+ len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
+ if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
+ sudo_warnx("%s", U_("unable to format timestamp"));
+ } else {
+ fprintf(fp, "%snotbefore=%s", need_comma ? "," : "", timebuf); // -V547
+ need_comma = true;
+ }
+ }
+ }
+ if (cs->notafter != UNSPEC) {
+ if (gmtime_r(&cs->notafter, &gmt) == NULL) {
+ sudo_warn("%s", U_("unable to get GMT time"));
+ } else {
+ timebuf[sizeof(timebuf) - 1] = '\0';
+ len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &gmt);
+ if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
+ sudo_warnx("%s", U_("unable to format timestamp"));
+ } else {
+ fprintf(fp, "%snotafter=%s", need_comma ? "," : "", timebuf);
+ need_comma = true;
+ }
+ }
+ }
+
+ if (cs->timeout > 0) {
+ fprintf(fp, "%scommand_timeout=%d", need_comma ? "," : "", cs->timeout);
+ need_comma = true;
+ }
+
+ /* Print tags as options */
+ if (TAGS_SET(cs->tags)) {
+ struct cmndtag tag = cs->tags;
+
+ if (tag.nopasswd != UNSPEC) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.nopasswd ? "!authenticate" : "authenticate");
+ need_comma = true;
+ }
+ if (tag.noexec != UNSPEC) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.noexec ? "noexec" : "!noexec");
+ need_comma = true;
+ }
+ if (tag.intercept != UNSPEC) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.intercept ? "intercept" : "!intercept");
+ need_comma = true;
+ }
+ if (tag.send_mail != UNSPEC) {
+ if (tag.send_mail) {
+ fprintf(fp, "%smail_all_cmnds", need_comma ? "," : "");
+ } else {
+ fprintf(fp, "%s!mail_all_cmnds,!mail_always,!mail_no_perms",
+ need_comma ? "," : "");
+ }
+ need_comma = true;
+ }
+ if (tag.setenv != UNSPEC && tag.setenv != IMPLIED) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.setenv ? "setenv" : "!setenv");
+ need_comma = true;
+ }
+ if (tag.follow != UNSPEC) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.follow ? "sudoedit_follow" : "!sudoedit_follow");
+ need_comma = true;
+ }
+ if (tag.log_input != UNSPEC) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.follow ? "log_input" : "!log_input");
+ need_comma = true;
+ }
+ if (tag.log_output != UNSPEC) {
+ fprintf(fp, "%s%s", need_comma ? "," : "",
+ tag.follow ? "log_output" : "!log_output");
+ need_comma = true;
+ }
+ }
+ print_options_csv(fp, options, need_comma);
+ if (!TAILQ_EMPTY(options))
+ need_comma = true;
+
+ /* Print runchroot and runcwd. */
+ if (cs->runchroot != NULL) {
+ fprintf(fp, "%srunchroot=%s", need_comma ? "," : "", cs->runchroot);
+ need_comma = true;
+ }
+ if (cs->runcwd != NULL) {
+ fprintf(fp, "%sruncwd=%s", need_comma ? "," : "", cs->runcwd);
+ need_comma = true;
+ }
+
+#ifdef HAVE_SELINUX
+ /* Print SELinux role/type */
+ if (cs->role != NULL && cs->type != NULL) {
+ fprintf(fp, "%srole=%s,type=%s", need_comma ? "," : "",
+ cs->role, cs->type);
+ need_comma = true;
+ }
+#endif /* HAVE_SELINUX */
+
+#ifdef HAVE_APPARMOR
+ if (cs->apparmor_profile != NULL) {
+ fprintf(fp, "%sapparmor_profile=%s,", need_comma ? "," : "",
+ cs->apparmor_profile);
+ need_comma = true;
+ }
+#endif /* HAVE_APPARMOR */
+
+#ifdef HAVE_PRIV_SET
+ /* Print Solaris privs/limitprivs */
+ if (cs->privs != NULL || cs->limitprivs != NULL) {
+ if (cs->privs != NULL) {
+ fprintf(fp, "%sprivs=%s", need_comma ? "," : "", cs->privs);
+ need_comma = true;
+ }
+ if (cs->limitprivs != NULL) {
+ fprintf(fp, "%slimitprivs=%s", need_comma ? "," : "", cs->limitprivs);
+ need_comma = true;
+ }
+ }
+#endif /* HAVE_PRIV_SET */
+#ifdef __clang_analyzer__
+ (void)&need_comma;
+#endif
+ putc('"', fp);
+ putc(',', fp);
+
+ /*
+ * Merge adjacent commands with matching tags, runas, SELinux
+ * role/type, AppArmor profiles and Solaris priv settings.
+ */
+ for (;;) {
+ /* Does the next entry differ only in the command itself? */
+ /* XXX - move into a function that returns bool */
+ /* XXX - TAG_SET does not account for implied SETENV */
+ last_one = next == NULL ||
+ RUNAS_CHANGED(cs, next) || TAGS_CHANGED(cs->tags, next->tags)
+#ifdef HAVE_PRIV_SET
+ || cs->privs != next->privs || cs->limitprivs != next->limitprivs
+#endif /* HAVE_PRIV_SET */
+#ifdef HAVE_SELINUX
+ || cs->role != next->role || cs->type != next->type
+#endif /* HAVE_SELINUX */
+#ifdef HAVE_APPARMOR
+ || cs->apparmor_profile != next->apparmor_profile
+#endif /* HAVE_APPARMOR */
+ || cs->runchroot != next->runchroot || cs->runcwd != next->runcwd;
+
+ if (!quoted && !last_one) {
+ quoted = true;
+ putc('"', fp);
+ }
+ m = cs->cmnd;
+ print_member_csv(fp, parse_tree, m->name, m->type, m->negated, quoted,
+ CMNDALIAS, expand_aliases);
+ if (last_one)
+ break;
+ putc(',', fp);
+ cs = next;
+ next = TAILQ_NEXT(cs, entries);
+ }
+ if (quoted)
+ putc('"', fp);
+
+ *nextp = next;
+
+ debug_return;
+}
+
+/*
+ * Print a single User_Spec.
+ */
+static bool
+print_userspec_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
+ struct userspec *us, bool expand_aliases)
+{
+ struct privilege *priv;
+ struct cmndspec *cs, *next;
+ debug_decl(print_userspec_csv, SUDOERS_DEBUG_UTIL);
+
+ /*
+ * Each userspec struct may contain multiple privileges for the user.
+ */
+ TAILQ_FOREACH(priv, &us->privileges, entries) {
+ TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
+ fputs("rule,", fp);
+ print_member_list_csv(fp, parse_tree, &us->users, false,
+ USERALIAS, expand_aliases);
+ putc(',', fp);
+
+ print_member_list_csv(fp, parse_tree, &priv->hostlist, false,
+ HOSTALIAS, expand_aliases);
+ putc(',', fp);
+
+ print_cmndspec_csv(fp, parse_tree, cs, &next, &priv->defaults,
+ expand_aliases);
+ putc('\n', fp);
+ }
+ }
+
+ debug_return_bool(!ferror(fp));
+}
+
+/*
+ * Print User_Specs.
+ */
+static bool
+print_userspecs_csv(FILE *fp, const struct sudoers_parse_tree *parse_tree,
+ bool expand_aliases)
+{
+ struct userspec *us;
+ debug_decl(print_userspecs_csv, SUDOERS_DEBUG_UTIL);
+
+ if (TAILQ_EMPTY(&parse_tree->userspecs))
+ debug_return_bool(true);
+
+ /* Heading line. */
+ fputs("rule,user,host,runusers,rungroups,options,command\n", fp);
+
+ TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
+ if (!print_userspec_csv(fp, parse_tree, us, expand_aliases))
+ debug_return_bool(false);
+ }
+ debug_return_bool(true);
+}
+
+/*
+ * Export the parsed sudoers file in CSV format.
+ */
+bool
+convert_sudoers_csv(const struct sudoers_parse_tree *parse_tree,
+ const char *output_file, struct cvtsudoers_config *conf)
+{
+ bool ret = true;
+ FILE *output_fp = stdout;
+ debug_decl(convert_sudoers_csv, SUDOERS_DEBUG_UTIL);
+
+ if (output_file != NULL && strcmp(output_file, "-") != 0) {
+ if ((output_fp = fopen(output_file, "w")) == NULL)
+ sudo_fatal(U_("unable to open %s"), output_file);
+ }
+
+ /* Dump Defaults in CSV format. */
+ if (!ISSET(conf->suppress, SUPPRESS_DEFAULTS))
+ print_defaults_csv(output_fp, parse_tree, conf->expand_aliases);
+
+ /* Dump Aliases in CSV format. */
+ if (!conf->expand_aliases && !ISSET(conf->suppress, SUPPRESS_ALIASES)) {
+ print_aliases_csv(output_fp, parse_tree);
+ }
+
+ /* Dump User_Specs in CSV format. */
+ if (!ISSET(conf->suppress, SUPPRESS_PRIVS))
+ print_userspecs_csv(output_fp, parse_tree, conf->expand_aliases);
+
+ (void)fflush(output_fp);
+ if (ferror(output_fp))
+ ret = false;
+ if (output_fp != stdout)
+ fclose(output_fp);
+
+ debug_return_bool(ret);
+}