>From 698d2b2878b0e21e3ec978839e92efb20c373ae9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Wed, 22 Jul 2015 10:02:02 +0200
Subject: [PATCH 3/4] TOOLS: add common command framework

Add general framework to simplify creating "cmd COMMAND [OPTIONS...]"
style tools.

Preparation for:
https://fedorahosted.org/sssd/ticket/2584
---
 Makefile.am                  |   5 +-
 src/tools/common/sss_tools.c | 406 +++++++++++++++++++++++++++++++++++++++++++
 src/tools/common/sss_tools.h |  91 ++++++++++
 3 files changed, 501 insertions(+), 1 deletion(-)
 create mode 100644 src/tools/common/sss_tools.c
 create mode 100644 src/tools/common/sss_tools.h

diff --git a/Makefile.am b/Makefile.am
index b8cbc6df23ded1edb945a709b6dbe1c44eb54017..1edecc483c61d04562b7bfd9086146e93963b74e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -445,7 +445,9 @@ SSSD_TOOLS_OBJ = \
     src/tools/tools_util.c \
     src/tools/files.c \
     src/tools/selinux.c \
-    src/util/nscd.c
+    src/tools/common/sss_tools.c \
+    src/util/nscd.c \
+    $(NULL)
 
 SSSD_LCL_TOOLS_OBJ = \
     src/sss_client/common.c \
@@ -641,6 +643,7 @@ dist_noinst_HEADERS = \
     src/lib/idmap/sss_idmap_private.h \
     src/lib/sifp/sss_sifp_private.h \
     src/tests/cmocka/test_utils.h \
+    src/tools/common/sss_tools.h \
     $(NULL)
 
 
diff --git a/src/tools/common/sss_tools.c b/src/tools/common/sss_tools.c
new file mode 100644
index 0000000000000000000000000000000000000000..6bbce3a25ddddc0b23ebc108a917a38e94981b65
--- /dev/null
+++ b/src/tools/common/sss_tools.c
@@ -0,0 +1,406 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <stdlib.h>
+#include <string.h>
+#include <popt.h>
+
+#include "config.h"
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include "tools/common/sss_tools.h"
+
+struct sss_cmdline {
+    const char *exec; /* argv[0] */
+    const char *command; /* command name */
+    int argc; /* rest of arguments */
+    const char **argv;
+};
+
+static void sss_tool_common_opts(struct sss_tool_ctx *tool_ctx,
+                                 int *argc, const char **argv)
+{
+    poptContext pc;
+    int debug = SSSDBG_DEFAULT;
+    int orig_argc = *argc;
+    int opt;
+
+    struct poptOption options[] = {
+        {"debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_STRIP, &debug,
+            0, _("The debug level to run with"), NULL },
+        POPT_TABLEEND
+    };
+
+    pc = poptGetContext(argv[0], orig_argc, argv, options, 0);
+    while ((opt = poptGetNextOpt(pc)) != -1) {
+        /* do nothing */
+    }
+
+    /* Strip common options from arguments. We will discard_const here,
+     * since it is not worth the trouble to convert it back and forth. */
+    *argc = poptStrippedArgv(pc, orig_argc, discard_const_p(char *, argv));
+
+    DEBUG_CLI_INIT(debug);
+
+    poptFreeContext(pc);
+}
+
+static errno_t sss_tool_confdb_init(TALLOC_CTX *mem_ctx,
+                                    struct confdb_ctx **_confdb)
+{
+    struct confdb_ctx *confdb;
+    char *path;
+    errno_t ret;
+
+    path = talloc_asprintf(mem_ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+    if (path == NULL) {
+        return ENOMEM;
+    }
+
+    ret = confdb_init(mem_ctx, &confdb, path);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Could not initialize connection to the confdb\n");
+        talloc_free(path);
+        return ret;
+    }
+
+    if (_confdb != NULL) {
+        *_confdb = confdb;
+    }
+
+    return EOK;
+}
+
+static errno_t sss_tool_domains_init(TALLOC_CTX *mem_ctx,
+                                     struct confdb_ctx *confdb,
+                                     struct sss_domain_info **_domains)
+{
+    struct sss_domain_info *domains;
+    struct sss_domain_info *dom;
+    errno_t ret;
+
+    ret = confdb_get_domains(confdb, &domains);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup domains [%d]: %s\n",
+                                   ret, sss_strerror(ret));
+        return ret;
+    }
+
+    ret = sysdb_init(mem_ctx, domains, false);
+    SYSDB_VERSION_ERROR(ret);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Could not initialize connection to the sysdb\n");
+        return ret;
+    }
+
+    for (dom = domains; dom != NULL; dom = get_next_domain(dom, true)) {
+        if (!IS_SUBDOMAIN(dom)) {
+            /* Update list of subdomains for this domain */
+            ret = sysdb_update_subdomains(dom);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      "Failed to update subdomains for domain %s.\n",
+                      dom->name);
+            }
+        }
+    }
+
+    for (dom = domains; dom != NULL; dom = get_next_domain(dom, true)) {
+        ret = sss_names_init(mem_ctx, confdb, dom->name, &dom->names);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "sss_names_init() failed\n");
+            return ret;
+        }
+    }
+
+    *_domains = domains;
+
+    return ret;
+}
+
+struct sss_tool_ctx *sss_tool_init(TALLOC_CTX *mem_ctx,
+                                   int *argc, const char **argv)
+{
+    struct sss_tool_ctx *tool_ctx;
+    errno_t ret;
+
+    tool_ctx = talloc_zero(mem_ctx, struct sss_tool_ctx);
+    if (tool_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n");
+        return NULL;
+    }
+
+    sss_tool_common_opts(tool_ctx, argc, argv);
+
+    /* Connect to confdb. */
+    ret = sss_tool_confdb_init(tool_ctx, &tool_ctx->confdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to open confdb [%d]: %s\n",
+                                   ret, sss_strerror(ret));
+        goto done;
+    }
+
+    /* Setup domains. */
+    ret = sss_tool_domains_init(tool_ctx, tool_ctx->confdb, &tool_ctx->domains);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup domains [%d]: %s\n",
+                                   ret, sss_strerror(ret));
+        goto done;
+    }
+
+    ret = confdb_get_string(tool_ctx->confdb, tool_ctx,
+                            CONFDB_MONITOR_CONF_ENTRY,
+                            CONFDB_MONITOR_DEFAULT_DOMAIN,
+                            NULL, &tool_ctx->default_domain);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get the default domain [%d]: %s\n",
+                                 ret, strerror(ret));
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_zfree(tool_ctx);
+    }
+
+    return tool_ctx;
+}
+
+int sss_tool_usage(const char *tool_name,
+                   struct sss_route_cmd *commands)
+{
+    int i;
+
+    fprintf(stderr, _("Usage:\n%s COMMAND COMMAND-ARGS\n\n"), tool_name);
+    fprintf(stderr, _("Available commands:\n"));
+
+    for (i = 0; commands[i].command != NULL; i++) {
+        fprintf(stderr, "* %s\n", commands[i].command);
+    }
+
+    return EXIT_FAILURE;
+}
+
+int sss_tool_route(int argc, const char **argv,
+                   struct sss_tool_ctx *tool_ctx,
+                   struct sss_route_cmd *commands,
+                   void *pvt)
+{
+    struct sss_cmdline cmdline;
+    const char *cmd;
+    int i;
+
+    if (commands == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Bug: commands can't be NULL!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (argc < 2) {
+        return sss_tool_usage(argv[0], commands);
+    }
+
+    cmd = argv[1];
+    for (i = 0; commands[i].command != NULL; i++) {
+        if (strcmp(commands[i].command, cmd) == 0) {
+            cmdline.exec = argv[0];
+            cmdline.command = argv[1];
+            cmdline.argc = argc - 2;
+            cmdline.argv = argv + 2;
+
+            return commands[i].fn(&cmdline, tool_ctx, pvt);
+        }
+    }
+
+    return sss_tool_usage(argv[0], commands);
+}
+
+int sss_tool_popt_ex(struct sss_cmdline *cmdline,
+                     struct poptOption *options,
+                     enum sss_tool_opt require_option,
+                     sss_popt_fn popt_fn,
+                     void *popt_fn_pvt,
+                     const char *fopt_name,
+                     const char *fopt_help,
+                     const char **_fopt)
+{
+    const char *optstr;
+    char *help;
+    poptContext pc;
+    int ret;
+
+    /* Create help option string. We always need to append command name since
+     * we use POPT_CONTEXT_KEEP_FIRST. */
+    optstr = options == NULL ? "" : _(" [OPTIONS...]");
+    if (fopt_name == NULL) {
+        help = talloc_asprintf(NULL, "%s %s%s",
+                               cmdline->exec, cmdline->command, optstr);
+    } else {
+        help = talloc_asprintf(NULL, "%s %s %s%s",
+                           cmdline->exec, cmdline->command, fopt_name, optstr);
+    }
+    if (help == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n");
+        return EXIT_FAILURE;
+    }
+
+    /* Create popt context. This function is supposed to be called on
+     * command argv which does not contain executable (argv[0]), therefore
+     * we need to use KEEP_FIRST that ensures argv[0] is also processed. */
+    pc = poptGetContext(cmdline->exec, cmdline->argc, cmdline->argv,
+                        options, POPT_CONTEXT_KEEP_FIRST);
+
+    poptSetOtherOptionHelp(pc, help);
+
+    /* Parse options. Invoke custom function if provided. If no parsing
+     * function is provided, print error on unknown option. */
+    while ((ret = poptGetNextOpt(pc)) != -1) {
+        if (popt_fn != NULL) {
+            ret = popt_fn(pc, ret, popt_fn_pvt);
+            if (ret != EOK) {
+                ret = EXIT_FAILURE;
+                goto done;
+            }
+        } else {
+            fprintf(stderr, _("Invalid option %s: %s\n\n"),
+                    poptBadOption(pc, 0), poptStrerror(ret));
+            poptPrintHelp(pc, stderr, 0);
+            ret = EXIT_FAILURE;
+            goto done;
+        }
+    }
+
+    /* Parse free option which is always required if requested. */
+    if (_fopt != NULL) {
+        *_fopt = poptGetArg(pc);
+        if (*_fopt == NULL) {
+            fprintf(stderr, _("Missing option: %s\n\n"), fopt_help);
+            poptPrintHelp(pc, stderr, 0);
+            ret = EXIT_FAILURE;
+            goto done;
+        }
+
+        /* No more arguments expected. If something follows it is an error. */
+        if (poptGetArg(pc)) {
+            fprintf(stderr, _("Only one free argument is expected!\n\n"));
+            poptPrintHelp(pc, stderr, 0);
+            ret = EXIT_FAILURE;
+            goto done;
+        }
+    }
+
+    /* If at least one option is required and not provided, print error. */
+    if (require_option == SSS_TOOL_OPT_REQUIRED
+            && ((_fopt != NULL && cmdline->argc < 2) || cmdline->argc < 1)) {
+        fprintf(stderr, _("At least one option is required!\n\n"));
+        poptPrintHelp(pc, stderr, 0);
+        ret = EXIT_FAILURE;
+        goto done;
+    }
+
+    ret = EXIT_SUCCESS;
+
+done:
+    poptFreeContext(pc);
+    talloc_free(help);
+    return ret;
+}
+
+int sss_tool_popt(struct sss_cmdline *cmdline,
+                  struct poptOption *options,
+                  enum sss_tool_opt require_option,
+                  sss_popt_fn popt_fn,
+                  void *popt_fn_pvt)
+{
+    return sss_tool_popt_ex(cmdline, options, require_option,
+                            popt_fn, popt_fn_pvt, NULL, NULL, NULL);
+}
+
+int sss_tool_main(int argc, const char **argv,
+                  struct sss_route_cmd *commands,
+                  void *pvt)
+{
+    struct sss_tool_ctx *tool_ctx;
+    uid_t uid;
+    int ret;
+
+    uid = getuid();
+    if (uid != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Running under %d, must be root\n", uid);
+        ERROR("%1$s must be run as root\n", argv[0]);
+        return EXIT_FAILURE;
+    }
+
+    tool_ctx = sss_tool_init(NULL, &argc, argv);
+    if (tool_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tool context\n");
+        return EXIT_FAILURE;
+    }
+
+    ret = sss_tool_route(argc, argv, tool_ctx, commands, pvt);
+    talloc_free(tool_ctx);
+
+    return ret;
+}
+
+int sss_tool_parse_name(TALLOC_CTX *mem_ctx,
+                        struct sss_tool_ctx *tool_ctx,
+                        const char *input,
+                        const char **_username,
+                        struct sss_domain_info **_domain)
+{
+    char *username = NULL;
+    char *domname = NULL;
+    struct sss_domain_info *domain;
+    int ret;
+
+    ret = sss_parse_name_for_domains(mem_ctx, tool_ctx->domains,
+                                     tool_ctx->default_domain, input,
+                                     &domname, &username);
+    if (ret == EAGAIN) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to find domain. The domain name may "
+              "be a subdomain that was not yet found.\n");
+        goto done;
+    } else if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name [%d]: %s\n",
+              ret, sss_strerror(ret));
+        goto done;
+    }
+
+    domain = find_domain_by_name(tool_ctx->domains, domname, true);
+
+    *_username = username;
+    *_domain = domain;
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_zfree(username);
+        talloc_zfree(domname);
+    }
+
+    return ret;
+}
diff --git a/src/tools/common/sss_tools.h b/src/tools/common/sss_tools.h
new file mode 100644
index 0000000000000000000000000000000000000000..cfe11d06e1dadf8e49efe155c8a53f99a31e97fb
--- /dev/null
+++ b/src/tools/common/sss_tools.h
@@ -0,0 +1,91 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SSS_TOOLS_H_
+#define _SSS_TOOLS_H_
+
+#include <talloc.h>
+#include <popt.h>
+
+#include "confdb/confdb.h"
+
+struct sss_tool_ctx {
+    struct confdb_ctx *confdb;
+
+    char *default_domain;
+    struct sss_domain_info *domains;
+};
+
+struct sss_tool_ctx *sss_tool_init(TALLOC_CTX *mem_ctx,
+                                   int *argc, const char **argv);
+
+struct sss_cmdline;
+
+typedef int
+(*sss_route_fn)(struct sss_cmdline *cmdline,
+                struct sss_tool_ctx *tool_ctx,
+                void *pvt);
+
+struct sss_route_cmd {
+    const char *command;
+    sss_route_fn fn;
+};
+
+int sss_tool_usage(const char *tool_name,
+                   struct sss_route_cmd *commands);
+
+int sss_tool_route(int argc, const char **argv,
+                   struct sss_tool_ctx *tool_ctx,
+                   struct sss_route_cmd *commands,
+                   void *pvt);
+
+typedef int (*sss_popt_fn)(poptContext pc, char option, void *pvt);
+
+enum sss_tool_opt {
+    SSS_TOOL_OPT_REQUIRED,
+    SSS_TOOL_OPT_OPTIONAL
+};
+
+int sss_tool_popt_ex(struct sss_cmdline *cmdline,
+                     struct poptOption *options,
+                     enum sss_tool_opt require_option,
+                     sss_popt_fn popt_fn,
+                     void *popt_fn_pvt,
+                     const char *free_opt_name,
+                     const char *free_opt_help,
+                     const char **_free_opt);
+
+int sss_tool_popt(struct sss_cmdline *cmdline,
+                  struct poptOption *options,
+                  enum sss_tool_opt require_option,
+                  sss_popt_fn popt_fn,
+                  void *popt_fn_pvt);
+
+int sss_tool_main(int argc, const char **argv,
+                  struct sss_route_cmd *commands,
+                  void *pvt);
+
+int sss_tool_parse_name(TALLOC_CTX *mem_ctx,
+                        struct sss_tool_ctx *tool_ctx,
+                        const char *input,
+                        const char **_username,
+                        struct sss_domain_info **_domain);
+
+#endif /* SRC_TOOLS_COMMON_SSS_TOOLS_H_ */
-- 
1.9.3

