root/usr/src/cmd/ndmpadm/ndmpadm_main.c
/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 * Copyright (c) 2018, Joyent, Inc.
 */

/*
 * BSD 3 Clause License
 *
 * Copyright (c) 2007, The Storage Networking Industry Association.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *      - Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in
 *        the documentation and/or other materials provided with the
 *        distribution.
 *
 *      - Neither the name of The Storage Networking Industry Association (SNIA)
 *        nor the names of its contributors may be used to endorse or promote
 *        products derived from this software without specific prior written
 *        permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <assert.h>
#include <ctype.h>
#include <libgen.h>
#include <libintl.h>
#include <locale.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <door.h>
#include <sys/mman.h>
#include <libndmp.h>
#include "ndmpadm.h"

typedef enum {
        HELP_GET_CONFIG,
        HELP_SET_CONFIG,
        HELP_SHOW_DEVICES,
        HELP_SHOW_SESSIONS,
        HELP_KILL_SESSIONS,
        HELP_ENABLE_AUTH,
        HELP_DISABLE_AUTH
} ndmp_help_t;

typedef struct ndmp_command {
        const char      *nc_name;
        int             (*func)(int argc, char **argv,
                            struct ndmp_command *cur_cmd);
        ndmp_help_t     nc_usage;
} ndmp_command_t;

static int ndmp_get_config(int, char **, ndmp_command_t *);
static int ndmp_set_config(int, char **, ndmp_command_t *);
static int ndmp_show_devices(int, char **, ndmp_command_t *);
static int ndmp_show_sessions(int, char **, ndmp_command_t *);
static int ndmp_kill_sessions(int, char **, ndmp_command_t *);
static int ndmp_enable_auth(int, char **, ndmp_command_t *);
static int ndmp_disable_auth(int, char **, ndmp_command_t *);
static void ndmp_get_config_process(char *);
static void ndmp_set_config_process(char *arg);
static int ndmp_get_password(char **);

static ndmp_command_t command_table[] = {
        { "get",                ndmp_get_config,        HELP_GET_CONFIG },
        { "set",                ndmp_set_config,        HELP_SET_CONFIG },
        { "show-devices",       ndmp_show_devices,      HELP_SHOW_DEVICES },
        { "show-sessions",      ndmp_show_sessions,     HELP_SHOW_SESSIONS },
        { "kill-sessions",      ndmp_kill_sessions,     HELP_KILL_SESSIONS },
        { "enable",             ndmp_enable_auth,       HELP_ENABLE_AUTH },
        { "disable",            ndmp_disable_auth,      HELP_DISABLE_AUTH }
};

#define NCOMMAND        (sizeof (command_table) / sizeof (command_table[0]))

static char *prop_table[] = {
        "debug-path",
        "dump-pathnode",
        "tar-pathnode",
        "ignore-ctime",
        "token-maxseq",
        "version",
        "dar-support",
        "tcp-port",
        "backup-quarantine",
        "restore-quarantine",
        "overwrite-quarantine",
        "zfs-force-override",
        "drive-type",
        "debug-mode"
};

#define NDMPADM_NPROP   (sizeof (prop_table) / sizeof (prop_table[0]))

typedef struct ndmp_auth {
        const char *auth_type;
        const char *username;
        const char *password;
} ndmp_auth_t;

static ndmp_auth_t ndmp_auth_table[] = {
        { "cram-md5", "cram-md5-username", "cram-md5-password" },
        { "cleartext", "cleartext-username", "cleartext-password" }
};
#define NAUTH   (sizeof (ndmp_auth_table) / sizeof (ndmp_auth_table[0]))
#define NDMP_PASSWORD_RETRIES   3

#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN     "SYS_TEST"
#endif

static const char *
get_usage(ndmp_help_t idx)
{
        switch (idx) {
        case HELP_SET_CONFIG:
                return ("\tset [-p] <property=value> [[-p] property=value] "
                    "...\n");
        case HELP_GET_CONFIG:
                return ("\tget [-p] [property] [[-p] property] ...\n");
        case HELP_SHOW_DEVICES:
                return ("\tshow-devices\n");
        case HELP_SHOW_SESSIONS:
                return ("\tshow-sessions [-i tape,scsi,data,mover] [id] ...\n");
        case HELP_KILL_SESSIONS:
                return ("\tkill-sessions <id ...>\n");
        case HELP_ENABLE_AUTH:
                return ("\tenable <-a auth-type> <-u username>\n");
        case HELP_DISABLE_AUTH:
                return ("\tdisable <-a auth-type>\n");
        }

        return (NULL);
}

/*
 * Display usage message.  If we're inside a command, display only the usage for
 * that command.  Otherwise, iterate over the entire command table and display
 * a complete usage message.
 */
static void
usage(boolean_t requested, ndmp_command_t *current_command)
{
        int i;
        boolean_t show_properties = B_FALSE;
        FILE *fp = requested ? stdout : stderr;

        if (current_command == NULL) {
                (void) fprintf(fp,
                    gettext("Usage: ndmpadm subcommand args ...\n"));
                (void) fprintf(fp,
                    gettext("where 'command' is one of the following:\n\n"));

                for (i = 0; i < NCOMMAND; i++) {
                        (void) fprintf(fp, "%s",
                            get_usage(command_table[i].nc_usage));
                }
                (void) fprintf(fp, gettext("\t\twhere %s can be either "
                    "%s or %s\n"), "'auth-type'", "'cram-md5'", "'cleartext'");
        } else {
                (void) fprintf(fp, gettext("Usage:\n"));
                (void) fprintf(fp, "%s", get_usage(current_command->nc_usage));
                if ((current_command->nc_usage == HELP_ENABLE_AUTH) ||
                    (current_command->nc_usage == HELP_DISABLE_AUTH))
                        (void) fprintf(fp, gettext("\t\twhere %s can be either "
                            "%s or %s\n"),
                            "'auth-type'", "'cram-md5'", "'cleartext'");
        }

        if (current_command != NULL &&
            (strcmp(current_command->nc_name, "set") == 0))
                show_properties = B_TRUE;

        if (show_properties) {
                (void) fprintf(fp,
                    gettext("\nThe following properties are supported:\n"));

                (void) fprintf(fp, gettext("\n\tPROPERTY"));
                (void) fprintf(fp, "\n\t%s", "-------------");
                for (i = 0; i < NDMPADM_NPROP; i++)
                        (void) fprintf(fp, "\n\t%s", prop_table[i]);
                (void) fprintf(fp, "\n");
        }

        exit(requested ? 0 : 2);
}

/*ARGSUSED*/
static int
ndmp_get_config(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        char *propval;
        int i, c;

        if (argc == 1) {
                /*
                 * Get all the properties and variables ndmpadm is allowed
                 * to see.
                 */
                for (i = 0; i < NDMPADM_NPROP; i++) {
                        if (ndmp_get_prop(prop_table[i], &propval)) {
                                (void) fprintf(stdout, "\t%s=\n",
                                    prop_table[i]);
                        } else {
                                (void) fprintf(stdout, "\t%s=%s\n",
                                    prop_table[i], propval);
                                free(propval);
                        }
                }
        } else if (argc > 1) {
                while ((c = getopt(argc, argv, ":p:")) != -1) {
                        switch (c) {
                        case 'p':
                                ndmp_get_config_process(optarg);
                                break;
                        case ':':
                                (void) fprintf(stderr, gettext("Option -%c "
                                    "requires an operand\n"), optopt);
                                break;
                        case '?':
                                (void) fprintf(stderr, gettext("Unrecognized "
                                    "option: -%c\n"), optopt);
                        }
                }
                /*
                 * optind is initialized to 1 if the -p option is not used,
                 * otherwise index to argv.
                 */
                argc -= optind;
                argv += optind;

                for (i = 0; i < argc; i++) {
                        if (strncmp(argv[i], "-p", 2) == 0)
                                continue;

                        ndmp_get_config_process(argv[i]);
                }
        }
        return (0);
}

static void
ndmp_get_config_process(char *arg)
{
        int j;
        char *propval;

        for (j = 0; j < NDMPADM_NPROP; j++) {
                if (strcmp(arg, prop_table[j]) == 0) {
                        if (ndmp_get_prop(arg, &propval)) {
                                (void) fprintf(stdout, "\t%s=\n", arg);
                        } else {
                                (void) fprintf(stdout, "\t%s=%s\n",
                                    arg, propval);
                                free(propval);
                        }
                        break;
                }
        }
        if (j == NDMPADM_NPROP) {
                (void) fprintf(stdout, gettext("\t%s is invalid property "
                    "or variable\n"), arg);
        }
}

/*ARGSUSED*/
static int
ndmp_set_config(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        int c, i;

        if (argc < 2) {
                (void) fprintf(stderr, gettext("Missing property=value "
                    "argument\n"));
                usage(B_FALSE, cur_cmd);
        }
        while ((c = getopt(argc, argv, ":p:")) != -1) {
                switch (c) {
                case 'p':
                        ndmp_set_config_process(optarg);
                        break;
                case ':':
                        (void) fprintf(stderr, gettext("Option -%c "
                            "requires an operand\n"), optopt);
                        break;
                case '?':
                        (void) fprintf(stderr, gettext("Unrecognized "
                            "option: -%c\n"), optopt);
                }
        }
        /*
         * optind is initialized to 1 if the -p option is not used,
         * otherwise index to argv.
         */
        argc -= optind;
        argv += optind;

        for (i = 0; i < argc; i++) {
                if (strncmp(argv[i], "-p", 2) == 0)
                        continue;

                ndmp_set_config_process(argv[i]);
        }
        return (0);
}

static void
ndmp_set_config_process(char *propname)
{
        char *propvalue;
        int ret, j;

        if ((propvalue = strchr(propname, '=')) == NULL) {
                (void) fprintf(stderr, gettext("Missing value in "
                    "property=value argument for %s\n"), propname);
                return;
        }
        *propvalue = '\0';
        propvalue++;

        if (*propname == '\0') {
                (void) fprintf(stderr, gettext("Missing property in "
                    "property=value argument for %s\n"), propname);
                return;
        }
        for (j = 0; j < NDMPADM_NPROP; j++) {
                if (strcmp(propname, prop_table[j]) == 0)
                        break;
        }
        if (j == NDMPADM_NPROP) {
                (void) fprintf(stdout, gettext("%s is invalid property or "
                    "variable\n"), propname);
                return;
        }
        ret = ndmp_set_prop(propname, propvalue);
        if (ret != -1) {
                if (!ndmp_door_status()) {
                        if (ndmp_service_refresh() != 0)
                                (void) fprintf(stdout, gettext("Could not "
                                    "refesh property of service ndmpd\n"));
                }
        } else {
                (void) fprintf(stdout, gettext("Could not set property for "
                    "%s - %s\n"), propname, ndmp_strerror(ndmp_errno));
        }
}

/*ARGSUSED*/
static int
ndmp_show_devices(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        int ret;
        ndmp_devinfo_t *dip = NULL;
        size_t size;

        if (ndmp_door_status()) {
                (void) fprintf(stdout,
                    gettext("Service ndmpd not running\n"));
                return (-1);
        }

        ret = ndmp_get_devinfo(&dip, &size);

        if (ret == -1)
                (void) fprintf(stdout,
                    gettext("Could not get device information\n"));
        else
                ndmp_devinfo_print(dip, size);

        ndmp_get_devinfo_free(dip, size);
        return (0);
}

static int
ndmp_show_sessions(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        ndmp_session_info_t *sinfo = NULL;
        ndmp_session_info_t *sp = NULL;
        uint_t num;
        int c, ret, i, j;
        int statarg = 0;
        char *value;
        char *type_subopts[] = { "tape", "scsi", "data", "mover", NULL };

        if (ndmp_door_status()) {
                (void) fprintf(stdout,
                    gettext("Service ndmpd not running\n"));
                return (-1);
        }

        /* Detail output if no option is specified */
        if (argc == 1) {
                statarg = NDMP_CAT_ALL;
        } else {
                statarg = 0;
                while ((c = getopt(argc, argv, ":i:")) != -1) {
                        switch (c) {
                        case 'i':
                                while (*optarg != '\0') {
                                        switch (getsubopt(&optarg, type_subopts,
                                            &value)) {
                                        case 0:
                                                statarg |= NDMP_CAT_TAPE;
                                                break;
                                        case 1:
                                                statarg |= NDMP_CAT_SCSI;
                                                break;
                                        case 2:
                                                statarg |= NDMP_CAT_DATA;
                                                break;
                                        case 3:
                                                statarg |= NDMP_CAT_MOVER;
                                                break;
                                        default:
                                                (void) fprintf(stderr,
                                                    gettext("Invalid object "
                                                    "type '%s'\n"), value);
                                                usage(B_FALSE, cur_cmd);
                                        }
                                }
                                break;
                        case ':':
                                (void) fprintf(stderr,
                                    gettext("Missing argument for "
                                    "'%c' option\n"), optopt);
                                usage(B_FALSE, cur_cmd);
                                break;
                        case '?':
                                (void) fprintf(stderr,
                                    gettext("Invalid option '%c'\n"), optopt);
                                usage(B_FALSE, cur_cmd);
                        }
                }
                /* if -i and its argument are not specified, display all */
                if (statarg == 0)
                        statarg = NDMP_CAT_ALL;
        }
        /*
         * optind is initialized to 1 if the -i option is not used, otherwise
         * index to argv.
         */
        argc -= optind;
        argv += optind;

        ret = ndmp_get_session_info(&sinfo, &num);
        if (ret == -1) {
                (void) fprintf(stdout,
                    gettext("Could not get session information\n"));
        } else {
                if (argc == 0) {
                        ndmp_session_all_print(statarg, sinfo, num);
                } else {
                        for (i = 0; i < argc; i++) {
                                sp = sinfo;
                                for (j = 0; j < num; j++, sp++) {
                                        if (sp->nsi_sid == atoi(argv[i])) {
                                                ndmp_session_print(statarg, sp);
                                                (void) fprintf(stdout, "\n");
                                                break;
                                        }
                                }
                                if (j == num) {
                                        (void) fprintf(stdout,
                                            gettext("Session %d not "
                                            "found\n"), atoi(argv[i]));
                                }
                        }
                }
                ndmp_get_session_info_free(sinfo, num);
        }
        return (0);
}

/*ARGSUSED*/
static int
ndmp_kill_sessions(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        int ret, i;

        if (ndmp_door_status()) {
                (void) fprintf(stdout,
                    gettext("Service ndmpd not running.\n"));
                return (-1);
        }

        /* If no arg is specified, print the usage and exit */
        if (argc == 1)
                usage(B_FALSE, cur_cmd);

        for (i = 1; i < argc; i++) {
                if (atoi(argv[i]) > 0) {
                        ret = ndmp_terminate_session(atoi(argv[i]));
                } else {
                        (void) fprintf(stderr,
                            gettext("Invalid argument %s\n"), argv[i]);
                        continue;
                }
                if (ret == -1)
                        (void) fprintf(stdout,
                            gettext("Session id %d not found.\n"),
                            atoi(argv[i]));
        }
        return (0);
}

static int
ndmp_get_password(char **password)
{
        char *pw1, pw2[257];
        int i;

        for (i = 0; i < NDMP_PASSWORD_RETRIES; i++) {
                /*
                 * getpassphrase use the same buffer to return password, so
                 * copy the result in different buffer, before calling the
                 * getpassphrase again.
                 */
                if ((pw1 =
                    getpassphrase(gettext("Enter new password: "))) != NULL) {
                        (void) strlcpy(pw2, pw1, sizeof (pw2));
                        if ((pw1 =
                            getpassphrase(gettext("Re-enter  password: ")))
                            != NULL) {
                                if (strncmp(pw1, pw2, strlen(pw1)) == 0) {
                                        *password = pw1;
                                        return (0);
                                } else {
                                        (void) fprintf(stderr,
                                            gettext("Both password did not "
                                            "match.\n"));
                                }
                        }
                }
        }
        return (-1);
}

static int
ndmp_enable_auth(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        char *auth_type, *username, *password;
        int c, i, auth_type_flag = 0;
        char *enc_password;

        /* enable <-a auth-type> <-u username> */
        if (argc != 5) {
                usage(B_FALSE, cur_cmd);
        }

        while ((c = getopt(argc, argv, ":a:u:")) != -1) {
                switch (c) {
                case 'a':
                        auth_type = strdup(optarg);
                        break;
                case 'u':
                        username = strdup(optarg);
                        break;
                case ':':
                        (void) fprintf(stderr, gettext("Option -%c "
                            "requires an operand\n"), optopt);
                        usage(B_FALSE, cur_cmd);
                        break;
                case '?':
                        (void) fprintf(stderr, gettext("Unrecognized "
                            "option: -%c\n"), optopt);
                        usage(B_FALSE, cur_cmd);
                }
        }

        if ((auth_type) && (username)) {
                if (ndmp_get_password(&password)) {
                        (void) fprintf(stderr, gettext("Could not get correct "
                            "password, exiting..."));
                        free(auth_type);
                        free(username);
                        exit(-1);
                }
        } else {
                (void) fprintf(stderr, gettext("%s or %s can not be blank"),
                    "'auth-type'", "'username'");
                free(auth_type);
                free(username);
                exit(-1);
        }

        if ((enc_password = ndmp_base64_encode(password)) == NULL) {
                (void) fprintf(stdout,
                    gettext("Could not encode password - %s\n"),
                    ndmp_strerror(ndmp_errno));
                free(auth_type);
                free(username);
                exit(-1);
        }

        for (i = 0; i < NAUTH; i++) {
                if (strncmp(auth_type, ndmp_auth_table[i].auth_type,
                    strlen(ndmp_auth_table[i].auth_type)) == 0) {
                        auth_type_flag = 1;
                        if ((ndmp_set_prop(ndmp_auth_table[i].username,
                            username)) == -1) {
                                (void) fprintf(stdout,
                                    gettext("Could not set username - %s\n"),
                                    ndmp_strerror(ndmp_errno));
                                continue;
                        }
                        if ((ndmp_set_prop(ndmp_auth_table[i].password,
                            enc_password)) == -1) {
                                (void) fprintf(stdout,
                                    gettext("Could not set password - %s\n"),
                                    ndmp_strerror(ndmp_errno));
                                continue;
                        }
                        if (!ndmp_door_status() &&
                            (ndmp_service_refresh()) != 0) {
                                (void) fprintf(stdout,
                                    gettext("Could not refesh ndmpd service "
                                    "properties\n"));
                        }
                }
        }
        free(auth_type);
        free(username);
        free(enc_password);

        if (!auth_type_flag)
                usage(B_FALSE, cur_cmd);

        return (0);
}

static int
ndmp_disable_auth(int argc, char **argv, ndmp_command_t *cur_cmd)
{
        char *auth_type;
        int c, i, auth_type_flag = 0;

        /* disable <-a auth-type> */
        if (argc != 3) {
                usage(B_FALSE, cur_cmd);
        }

        while ((c = getopt(argc, argv, ":a:")) != -1) {
                switch (c) {
                case 'a':
                        auth_type = strdup(optarg);
                        break;
                case ':':
                        (void) fprintf(stderr, gettext("Option -%c "
                            "requires an operand\n"), optopt);
                        break;
                case '?':
                        (void) fprintf(stderr, gettext("Unrecognized "
                            "option: -%c\n"), optopt);
                }
        }
        for (i = 0; i < NAUTH; i++) {
                if (strncmp(auth_type, ndmp_auth_table[i].auth_type,
                    strlen(ndmp_auth_table[i].auth_type)) == 0) {
                        auth_type_flag = 1;
                        if ((ndmp_set_prop(ndmp_auth_table[i].username,
                            "")) == -1) {
                                (void) fprintf(stdout,
                                    gettext("Could not clear username - %s\n"),
                                    ndmp_strerror(ndmp_errno));
                                continue;
                        }
                        if ((ndmp_set_prop(ndmp_auth_table[i].password,
                            "")) == -1) {
                                (void) fprintf(stdout,
                                    gettext("Could not clear password - %s\n"),
                                    ndmp_strerror(ndmp_errno));
                                continue;
                        }
                        if (!ndmp_door_status() &&
                            (ndmp_service_refresh()) != 0) {
                                (void) fprintf(stdout, gettext("Could not "
                                    "refesh ndmpd service properties\n"));
                        }
                }
        }
        free(auth_type);

        if (!auth_type_flag)
                usage(B_FALSE, cur_cmd);

        return (0);
}

int
main(int argc, char **argv)
{
        int ret;
        int i;
        char *cmdname;
        ndmp_command_t  *current_command = NULL;

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        opterr = 0;

        /* Make sure the user has specified some command. */
        if (argc < 2) {
                (void) fprintf(stderr, gettext("Missing command.\n"));
                usage(B_FALSE, current_command);
        }

        cmdname = argv[1];

        /*
         * Special case '-?'
         */
        if (strcmp(cmdname, "-?") == 0)
                usage(B_TRUE, current_command);

        /*
         * Run the appropriate sub-command.
         */
        for (i = 0; i < NCOMMAND; i++) {
                if (strcmp(cmdname, command_table[i].nc_name) == 0) {
                        current_command = &command_table[i];
                        ret = command_table[i].func(argc - 1, argv + 1,
                            current_command);
                        break;
                }
        }

        if (i == NCOMMAND) {
                (void) fprintf(stderr, gettext("Unrecognized "
                    "command '%s'\n"), cmdname);
                usage(B_FALSE, current_command);
        }

        return (ret);
}