Blame tools/tools.c

Packit 8586cb
/*
Packit 8586cb
 * adcli
Packit 8586cb
 *
Packit 8586cb
 * Copyright (C) 2012 Red Hat Inc.
Packit 8586cb
 *
Packit 8586cb
 * This program is free software; you can redistribute it and/or modify
Packit 8586cb
 * it under the terms of the GNU Lesser General Public License as
Packit 8586cb
 * published by the Free Software Foundation; either version 2.1 of
Packit 8586cb
 * the License, or (at your option) any later version.
Packit 8586cb
 *
Packit 8586cb
 * This program is distributed in the hope that it will be useful, but
Packit 8586cb
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8586cb
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 8586cb
 * Lesser General Public License for more details.
Packit 8586cb
 *
Packit 8586cb
 * You should have received a copy of the GNU Lesser General Public
Packit 8586cb
 * License along with this program; if not, write to the Free Software
Packit 8586cb
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
Packit 8586cb
 * MA 02110-1301 USA
Packit 8586cb
 *
Packit 8586cb
 * Author: Stef Walter <stefw@gnome.org>
Packit 8586cb
 */
Packit 8586cb
Packit 8586cb
#include "config.h"
Packit 8586cb
Packit 8586cb
#include "adcli.h"
Packit 8586cb
#include "adprivate.h"
Packit 8586cb
#include "tools.h"
Packit 8586cb
Packit 8586cb
#include <sys/stat.h>
Packit 8586cb
Packit 8586cb
#include <assert.h>
Packit 8586cb
#include <ctype.h>
Packit 8586cb
#include <err.h>
Packit 8586cb
#include <errno.h>
Packit 8586cb
#include <paths.h>
Packit 8586cb
#include <stdio.h>
Packit 8586cb
#include <unistd.h>
Packit 8586cb
Packit 8586cb
Packit 8586cb
static char *adcli_temp_directory = NULL;
Packit 8586cb
static char *adcli_krb5_conf_filename = NULL;
Packit 8586cb
static char *adcli_krb5_d_directory = NULL;
Packit 8586cb
Packit 8586cb
enum {
Packit 8586cb
	CONNECTION_LESS = 1<<0,
Packit 8586cb
};
Packit 8586cb
Packit 8586cb
struct {
Packit 8586cb
	const char *name;
Packit 8586cb
	int (*function) (adcli_conn *, int, char *[]);
Packit 8586cb
	const char *text;
Packit 8586cb
	int flags;
Packit 8586cb
} commands[] = {
Packit 8586cb
	{ "info", adcli_tool_info, "Print information about a domain", CONNECTION_LESS },
Packit 8586cb
	{ "join", adcli_tool_computer_join, "Join this machine to a domain", },
Packit 8586cb
	{ "update", adcli_tool_computer_update, "Update machine membership in a domain", },
Packit Service dff23e
	{ "testjoin", adcli_tool_computer_testjoin, "Test if machine account password is valid", },
Packit 8586cb
	{ "preset-computer", adcli_tool_computer_preset, "Pre setup computers accounts", },
Packit 8586cb
	{ "reset-computer", adcli_tool_computer_reset, "Reset a computer account", },
Packit Service 4aed93
	{ "delete-computer", adcli_tool_computer_delete, "Delete a computer account", },
Packit 8586cb
	{ "create-user", adcli_tool_user_create, "Create a user account", },
Packit 8586cb
	{ "delete-user", adcli_tool_user_delete, "Delete a user account", },
Packit 8586cb
	{ "create-group", adcli_tool_group_create, "Create a group", },
Packit 8586cb
	{ "delete-group", adcli_tool_group_delete, "Delete a group", },
Packit 8586cb
	{ "add-member", adcli_tool_member_add, "Add users to a group", },
Packit 8586cb
	{ "remove-member", adcli_tool_member_remove, "Remove users from a group", },
Packit 8586cb
	{ 0, }
Packit 8586cb
};
Packit 8586cb
Packit 8586cb
static char
Packit 8586cb
short_option (int opt)
Packit 8586cb
{
Packit 8586cb
	if (isalpha (opt) || isdigit (opt))
Packit 8586cb
		return (char)opt;
Packit 8586cb
	return 0;
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
static const struct option *
Packit 8586cb
find_option (const struct option *longopts,
Packit 8586cb
             int opt)
Packit 8586cb
{
Packit 8586cb
	int i;
Packit 8586cb
Packit 8586cb
	for (i = 0; longopts[i].name != NULL; i++) {
Packit 8586cb
		if (longopts[i].val == opt)
Packit 8586cb
			return longopts + i;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	return NULL;
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
void
Packit 8586cb
adcli_tool_usage (const struct option *longopts,
Packit 8586cb
                  const adcli_tool_desc *usages)
Packit 8586cb
{
Packit 8586cb
	const struct option *longopt;
Packit 8586cb
	const int indent = 28;
Packit 8586cb
	const char *description;
Packit 8586cb
	const char *next;
Packit 8586cb
	char short_name;
Packit 8586cb
	int spaces;
Packit 8586cb
	int len;
Packit 8586cb
	int i;
Packit 8586cb
Packit 8586cb
	for (i = 0; usages[i].text != NULL; i++) {
Packit 8586cb
Packit 8586cb
		/* If no option, then this is a heading */
Packit 8586cb
		if (!usages[i].option) {
Packit 8586cb
			printf ("%s\n\n", usages[i].text);
Packit 8586cb
			continue;
Packit 8586cb
		}
Packit 8586cb
Packit 8586cb
		/* Only print out options we can actually parse */
Packit 8586cb
		longopt = find_option (longopts, usages[i].option);
Packit 8586cb
		if (!longopt)
Packit 8586cb
			continue;
Packit 8586cb
Packit 8586cb
		short_name = short_option (usages[i].option);
Packit 8586cb
		if (short_name && longopt->name)
Packit 8586cb
			len = printf ("  -%c, --%s", (int)short_name, longopt->name);
Packit 8586cb
		else if (longopt->name)
Packit 8586cb
			len = printf ("  --%s", longopt->name);
Packit 8586cb
		else
Packit 8586cb
			len = printf ("  -%c", (int)short_name);
Packit 8586cb
		if (longopt->has_arg)
Packit 8586cb
			len += printf ("%s<%s>",
Packit 8586cb
			               longopt->name ? "=" : " ",
Packit 8586cb
			               usages[i].arg ? usages[i].arg : "...");
Packit 8586cb
		if (len < indent) {
Packit 8586cb
			spaces = indent - len;
Packit 8586cb
		} else {
Packit 8586cb
			printf ("\n");
Packit 8586cb
			spaces = indent;
Packit 8586cb
		}
Packit 8586cb
		description = usages[i].text;
Packit 8586cb
		while (description) {
Packit 8586cb
			while (spaces-- > 0)
Packit 8586cb
				fputc (' ', stdout);
Packit 8586cb
			next = strchr (description, '\n');
Packit 8586cb
			if (next) {
Packit 8586cb
				next += 1;
Packit 8586cb
				printf ("%.*s", (int)(next - description), description);
Packit 8586cb
				description = next;
Packit 8586cb
				spaces = indent;
Packit 8586cb
			} else {
Packit 8586cb
				printf ("%s\n", description);
Packit 8586cb
				break;
Packit 8586cb
			}
Packit 8586cb
		}
Packit 8586cb
Packit 8586cb
	}
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
int
Packit 8586cb
adcli_tool_getopt (int argc,
Packit 8586cb
                   char *argv[],
Packit 8586cb
                   const struct option *options)
Packit 8586cb
{
Packit 8586cb
	int count = 0;
Packit 8586cb
	char *shorts;
Packit 8586cb
	char *p;
Packit 8586cb
	int ret;
Packit 8586cb
	char opt;
Packit 8586cb
	int i;
Packit 8586cb
Packit 8586cb
	/* Number of characters */
Packit 8586cb
	for (i = 0; options[i].name != NULL; i++)
Packit 8586cb
		count++;
Packit 8586cb
Packit 8586cb
	p = shorts = malloc ((count * 2) + 1);
Packit 8586cb
	return_val_if_fail (shorts != NULL, -1);
Packit 8586cb
Packit 8586cb
	for (i = 0; i < count; i++) {
Packit 8586cb
		opt = short_option (options[i].val);
Packit 8586cb
		if (opt != 0) {
Packit 8586cb
			*(p++) = (char)options[i].val;
Packit 8586cb
			if (options[i].has_arg == required_argument)
Packit 8586cb
				*(p++) = ':';
Packit 8586cb
		}
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	*(p++) = '\0';
Packit 8586cb
Packit 8586cb
	ret = getopt_long (argc, argv, shorts, options, NULL);
Packit 8586cb
	free (shorts);
Packit 8586cb
Packit 8586cb
	return ret;
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
static void
Packit 8586cb
command_usage (void)
Packit 8586cb
{
Packit 8586cb
	int i;
Packit 8586cb
Packit 8586cb
	printf ("usage: adcli command <args>...\n");
Packit 8586cb
	printf ("\nCommon adcli commands are:\n");
Packit 8586cb
	for (i = 0; commands[i].name != NULL; i++)
Packit 8586cb
		printf ("  %-15s  %s\n", commands[i].name, commands[i].text);
Packit 8586cb
	printf ("\nSee 'adcli <command> --help' for more information\n");
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
char *
Packit 8586cb
adcli_prompt_password_func (adcli_login_type login_type,
Packit 8586cb
                            const char *name,
Packit 8586cb
                            int flags,
Packit 8586cb
                            void *unused_data)
Packit 8586cb
{
Packit 8586cb
	char *prompt;
Packit 8586cb
	char *password;
Packit 8586cb
	char *result;
Packit 8586cb
Packit 8586cb
	if (asprintf (&prompt, "Password for %s: ", name) < 0)
Packit 8586cb
		return_val_if_reached (NULL);
Packit 8586cb
Packit 8586cb
	password = getpass (prompt);
Packit 8586cb
	free (prompt);
Packit 8586cb
Packit 8586cb
	if (password == NULL)
Packit 8586cb
		return NULL;
Packit 8586cb
Packit 8586cb
	result = strdup (password);
Packit 8586cb
	adcli_mem_clear (password, strlen (password));
Packit 8586cb
Packit 8586cb
	return result;
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
char *
Packit 8586cb
adcli_read_password_func (adcli_login_type login_type,
Packit 8586cb
                          const char *name,
Packit 8586cb
                          int flags,
Packit 8586cb
                          void *unused_data)
Packit 8586cb
{
Packit 8586cb
	char *buffer = NULL;
Packit 8586cb
	size_t length = 0;
Packit 8586cb
	size_t offset = 0;
Packit 8586cb
	ssize_t res;
Packit 8586cb
Packit 8586cb
	for (;;) {
Packit 8586cb
		if (offset >= length) {
Packit 8586cb
			length += 4096;
Packit 8586cb
			buffer = realloc (buffer, length + 1);
Packit 8586cb
			return_val_if_fail (buffer != NULL, NULL);
Packit 8586cb
		}
Packit 8586cb
Packit 8586cb
		res = read (0, buffer + offset, length - offset);
Packit 8586cb
		if (res < 0) {
Packit 8586cb
			if (errno == EAGAIN || errno == EINTR)
Packit 8586cb
				continue;
Packit Service 959362
			err (EFAIL, "couldn't read password from stdin");
Packit 8586cb
Packit 8586cb
		} else if (res == 0) {
Packit 8586cb
			buffer[offset] = '\0';
Packit 8586cb
			/* remove new line character */
Packit 8586cb
			if (offset > 0 && buffer[offset - 1] == '\n') {
Packit 8586cb
				buffer[offset - 1] = '\0';
Packit 8586cb
				if (offset > 1 && buffer[offset - 2] == '\r') {
Packit 8586cb
					buffer[offset - 2] = '\0';
Packit 8586cb
				}
Packit 8586cb
			}
Packit 8586cb
			return buffer;
Packit 8586cb
Packit 8586cb
		} else {
Packit Service 959362
			if (memchr (buffer + offset, 0, res))
Packit Service 959362
				errx (EUSAGE, "unsupported null character present in password");
Packit 8586cb
			offset += res;
Packit 8586cb
		}
Packit 8586cb
	}
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
static void
Packit 8586cb
cleanup_krb5_conf_directory (void)
Packit 8586cb
{
Packit 8586cb
	if (adcli_krb5_d_directory) {
Packit 8586cb
		rmdir (adcli_krb5_d_directory);
Packit 8586cb
		free (adcli_krb5_d_directory);
Packit 8586cb
		adcli_krb5_d_directory = NULL;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	if (adcli_krb5_conf_filename) {
Packit 8586cb
		unlink (adcli_krb5_conf_filename);
Packit 8586cb
		free (adcli_krb5_conf_filename);
Packit 8586cb
		adcli_krb5_conf_filename = NULL;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	if (adcli_temp_directory) {
Packit 8586cb
		rmdir (adcli_temp_directory);
Packit 8586cb
		free (adcli_temp_directory);
Packit 8586cb
		adcli_temp_directory = NULL;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	unsetenv ("KRB5_CONFIG");
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
static void
Packit 8586cb
setup_krb5_conf_directory (adcli_conn *conn)
Packit 8586cb
{
Packit 8586cb
	const char *parent;
Packit 8586cb
	const char *krb5_conf;
Packit 8586cb
	char *filename = NULL;
Packit 8586cb
	char *snippets = NULL;
Packit 8586cb
	char *contents = NULL;
Packit 8586cb
	char *directory = NULL;
Packit 8586cb
	struct stat sb;
Packit 8586cb
	int failed = 0;
Packit 8586cb
	int errn = 0;
Packit 8586cb
	FILE *fo;
Packit 8586cb
Packit 8586cb
	krb5_conf = getenv ("KRB5_CONFIG");
Packit 8586cb
	if (!krb5_conf || !krb5_conf[0])
Packit 8586cb
		krb5_conf = KRB5_CONFIG;
Packit 8586cb
Packit 8586cb
	parent = getenv ("TMPDIR");
Packit 8586cb
	if (!parent || !*parent)
Packit 8586cb
		parent = _PATH_TMP;
Packit 8586cb
Packit 8586cb
	/* Check that the config file exists, don't include if not */
Packit 8586cb
	if (stat (krb5_conf, &sb) < 0) {
Packit 8586cb
		if (errno != ENOENT)
Packit 8586cb
			warn ("couldn't access file: %s", krb5_conf);
Packit 8586cb
		krb5_conf = NULL;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	if (asprintf (&directory, "%s%sadcli-krb5-XXXXXX", parent,
Packit Service 959362
	              (parent[0] && parent[strlen(parent) - 1] == '/') ? "" : "/") < 0)
Packit Service 959362
		errx (1, "unexpected: out of memory");
Packit d73ee5
Packit Service 959362
	if (mkdtemp (directory) == NULL) {
Packit Service 959362
		errn = errno;
Packit Service 959362
		failed = 1;
Packit Service 959362
		warnx ("couldn't create temporary directory in: %s: %s",
Packit Service 959362
		       parent, strerror (errn));
Packit Service 959362
	} else {
Packit Service 959362
		if (asprintf (&filename, "%s/krb5.conf", directory) < 0 ||
Packit Service 959362
		    asprintf (&snippets, "%s/krb5.d", directory) < 0 ||
Packit Service 959362
		    asprintf (&contents, "includedir %s\n%s%s\n", snippets,
Packit Service 959362
		              krb5_conf ? "include " : "",
Packit Service 959362
		              krb5_conf ? krb5_conf : "") < 0)
Packit Service 959362
			errx (1, "unexpected: out of memory");
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	if (!failed) {
Packit 8586cb
		fo = fopen (filename, "wb");
Packit 8586cb
		if (fo == NULL) {
Packit 8586cb
			errn = errno;
Packit 8586cb
			failed = 1;
Packit 8586cb
		} else {
Packit 8586cb
			fwrite (contents, 1, strlen (contents), fo);
Packit 8586cb
			if (ferror (fo)) {
Packit 8586cb
				errn = errno;
Packit 8586cb
				failed = 1;
Packit 8586cb
				fclose (fo);
Packit 8586cb
			} else {
Packit 8586cb
				if (fclose (fo) != 0) {
Packit 8586cb
					failed = 1;
Packit 8586cb
					errn = errno;
Packit 8586cb
				}
Packit 8586cb
			}
Packit 8586cb
		}
Packit 8586cb
Packit 8586cb
		if (failed) {
Packit 8586cb
			warnx ("couldn't write new krb5.conf file: %s: %s",
Packit 8586cb
			       filename, strerror (errn));
Packit 8586cb
		}
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
Packit 8586cb
	if (!failed && mkdir (snippets, 0700) < 0) {
Packit 8586cb
		errn = errno;
Packit 8586cb
		failed = 1;
Packit 8586cb
		warnx ("couldn't write new krb5.d directory: %s: %s",
Packit 8586cb
		       snippets, strerror (errn));
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	if (!failed) {
Packit 8586cb
		adcli_conn_set_krb5_conf_dir (conn, snippets);
Packit 8586cb
		adcli_temp_directory = directory;
Packit 8586cb
		adcli_krb5_conf_filename = filename;
Packit 8586cb
		adcli_krb5_d_directory = snippets;
Packit 8586cb
		setenv ("KRB5_CONFIG", adcli_krb5_conf_filename, 1);
Packit 8586cb
Packit 8586cb
	} else {
Packit 8586cb
		free (filename);
Packit 8586cb
		free (snippets);
Packit 8586cb
		free (directory);
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	free (contents);
Packit 8586cb
	atexit (cleanup_krb5_conf_directory);
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
static void
Packit 8586cb
message_func (adcli_message_type type,
Packit 8586cb
              const char *message)
Packit 8586cb
{
Packit 8586cb
	const char *prefix = "";
Packit 8586cb
Packit 8586cb
	switch (type) {
Packit 8586cb
	case ADCLI_MESSAGE_INFO:
Packit 8586cb
		prefix = " * ";
Packit 8586cb
		break;
Packit 8586cb
	case ADCLI_MESSAGE_WARNING:
Packit 8586cb
	case ADCLI_MESSAGE_ERROR:
Packit 8586cb
		prefix = " ! ";
Packit 8586cb
		break;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	fprintf (stderr, "%s%s\n", prefix, message);
Packit 8586cb
}
Packit 8586cb
Packit 8586cb
int
Packit 8586cb
main (int argc,
Packit 8586cb
      char *argv[])
Packit 8586cb
{
Packit 8586cb
	adcli_conn *conn = NULL;
Packit 8586cb
	char *command = NULL;
Packit 8586cb
	int skip;
Packit 8586cb
	int in, out;
Packit 8586cb
	int ret;
Packit 8586cb
	int i;
Packit 8586cb
Packit 8586cb
	/*
Packit 8586cb
	 * Parse the global options. We rearrange the options as
Packit 8586cb
	 * necessary, in order to pass relevant options through
Packit 8586cb
	 * to the commands, but also have them take effect globally.
Packit 8586cb
	 */
Packit 8586cb
Packit 8586cb
	for (in = 1, out = 1; in < argc; in++, out++) {
Packit 8586cb
		skip = 0;
Packit 8586cb
Packit 8586cb
		/* The non-option is the command, take it out of the arguments */
Packit 8586cb
		if (argv[in][0] != '-') {
Packit 8586cb
			if (!command) {
Packit 8586cb
				skip = 1;
Packit 8586cb
				command = argv[in];
Packit 8586cb
			}
Packit 8586cb
Packit 8586cb
		/* The global long options */
Packit 8586cb
		} else if (argv[in][1] == '-') {
Packit 8586cb
			skip = 0;
Packit 8586cb
Packit 8586cb
			if (strcmp (argv[in], "--") == 0) {
Packit 8586cb
				if (!command)
Packit 8586cb
					errx (2, "no command specified");
Packit 8586cb
Packit 8586cb
			} else if (strcmp (argv[in], "--verbose") == 0) {
Packit 8586cb
				adcli_set_message_func (message_func);
Packit 8586cb
Packit 8586cb
			} else if (strcmp (argv[in], "--help") == 0) {
Packit 8586cb
				if (!command) {
Packit 8586cb
					command_usage ();
Packit 8586cb
					return 0;
Packit 8586cb
				}
Packit 8586cb
Packit 8586cb
			} else {
Packit 8586cb
				if (!command)
Packit 8586cb
					errx (2, "unknown option: %s", argv[in]);
Packit 8586cb
			}
Packit 8586cb
Packit 8586cb
		/* The global short options */
Packit 8586cb
		} else {
Packit 8586cb
			skip = 0;
Packit 8586cb
Packit 8586cb
			for (i = 1; argv[in][i] != '\0'; i++) {
Packit 8586cb
				switch (argv[in][i]) {
Packit 8586cb
				case 'h':
Packit 8586cb
					if (!command) {
Packit 8586cb
						command_usage ();
Packit 8586cb
						return 0;
Packit 8586cb
					}
Packit 8586cb
					break;
Packit 8586cb
Packit 8586cb
				case 'v':
Packit 8586cb
					adcli_set_message_func (message_func);
Packit 8586cb
					break;
Packit 8586cb
Packit 8586cb
				default:
Packit 8586cb
					if (!command)
Packit 8586cb
						errx (2, "unknown option: -%c", (int)argv[in][i]);
Packit 8586cb
					break;
Packit 8586cb
				}
Packit 8586cb
			}
Packit 8586cb
		}
Packit 8586cb
Packit 8586cb
		/* Skipping this argument? */
Packit 8586cb
		if (skip)
Packit 8586cb
			out--;
Packit 8586cb
		else
Packit 8586cb
			argv[out] = argv[in];
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	if (command == NULL) {
Packit 8586cb
		/* As a special favor if someone just typed 'adcli', help them out */
Packit 8586cb
		if (argc == 1)
Packit 8586cb
			command_usage ();
Packit 8586cb
		else
Packit 8586cb
			warnx ("no command specified");
Packit 8586cb
		return 2;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	argc = out;
Packit 8586cb
	conn = NULL;
Packit 8586cb
Packit 8586cb
	/* Look for the command */
Packit 8586cb
	for (i = 0; commands[i].name != NULL; i++) {
Packit 8586cb
		if (strcmp (commands[i].name, command) != 0)
Packit 8586cb
			continue;
Packit 8586cb
Packit 8586cb
		if (!(commands[i].flags & CONNECTION_LESS)) {
Packit 8586cb
			conn = adcli_conn_new (NULL);
Packit 8586cb
			if (conn == NULL)
Packit 8586cb
				errx (-1, "unexpected memory problems");
Packit 8586cb
			adcli_conn_set_password_func (conn, adcli_prompt_password_func, NULL, NULL);
Packit 8586cb
			setup_krb5_conf_directory (conn);
Packit 8586cb
		}
Packit 8586cb
Packit 8586cb
		argv[0] = command;
Packit 8586cb
		ret = (commands[i].function) (conn, argc, argv);
Packit 8586cb
Packit 8586cb
		if (conn)
Packit 8586cb
			adcli_conn_unref (conn);
Packit 8586cb
		return ret;
Packit 8586cb
	}
Packit 8586cb
Packit 8586cb
	/* At this point we have no command */
Packit 8586cb
	errx (2, "'%s' is not a valid adcli command. See 'adcli --help'", command);
Packit 8586cb
}