card: Implement non-interactive mode.

* tools/card-tool.h (opt): Add field 'initialized'.
* tools/card-call-scd.c (scd_learn): Set it.
* tools/gpg-card-tool.c (main): Reworked.
(dispatch_command): New.
--

This work is not yet finished because most commands need some tweaks
for non-interactive work.  What you already can do are things like:

 $ gpg-card-tool list -- 'auth <oldkey' \
   -- auth --setkey --raw 123456781234567812345678 -- help auth

Which will list the current card, authenticate using a hex encoded key
from the file "oldkey", set the new admin key to "123...78", and print
help for the auth command.  Note that the -- acts as a delimiter
between commands.  To use a double dash as argument to a command the
entire command must be quoted.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-01-31 18:57:16 +01:00
parent da38325740
commit 1c0fa3e6f7
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
3 changed files with 248 additions and 73 deletions

View File

@ -956,6 +956,8 @@ scd_learn (card_info_t info)
/* Also try to get some other key attributes. */
if (!err)
{
info->initialized = 1;
err = scd_getattr ("KEY-ATTR", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
@ -964,7 +966,6 @@ scd_learn (card_info_t info)
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
}
if (info == &dummyinfo)

View File

@ -27,6 +27,7 @@
/* We keep all global options in the structure OPT. */
struct
{
int interactive;
int verbose;
unsigned int debug;
int quiet;
@ -137,6 +138,7 @@ typedef struct key_info_s *key_info_t;
*/
struct card_info_s
{
int initialized; /* True if a learn command was successful. */
int error; /* private. */
char *reader; /* Reader information. */
char *cardtype; /* NULL or type of the card. */

View File

@ -46,7 +46,7 @@
#define CONTROL_D ('D' - 'A' + 1)
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
enum opt_values
{
aNull = 0,
@ -69,18 +69,12 @@ enum cmd_and_opt_values
oLCctype,
oLCmessages,
aTest,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aTest, "test", "test command"),
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
@ -133,7 +127,7 @@ typedef struct keyinfolabel_s *keyinfolabel_t;
/* Local prototypes. */
static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
static gpg_error_t dispatch_command (card_info_t info, const char *command);
static void interactive_loop (void);
#ifdef HAVE_LIBREADLINE
static char **command_completion (const char *text, int start, int end);
@ -157,11 +151,14 @@ my_strusage( int level )
case 1:
case 40:
p = ("Usage: gpg-card-tool [command] [options] [args] (-h for help)");
p = ("Usage: gpg-card-tool"
" [options] [{[--] command [args]}] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-card-tool [command] [options] [args]\n"
"Tool to configure cards and tokens\n");
p = ("Syntax: gpg-card-tool"
" [options] [command [args] {-- command [args]}]\n\n"
"Tool to manage cards and tokens. With a command an interactive\n"
"mode is used. Use command \"help\" to list all commands.");
break;
default: p = NULL; break;
@ -170,14 +167,6 @@ my_strusage( int level )
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
exit (2);
}
static void
set_opt_session_env (const char *name, const char *value)
{
@ -192,13 +181,10 @@ set_opt_session_env (const char *name, const char *value)
/* Command line parsing. */
static enum cmd_and_opt_values
static void
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
enum cmd_and_opt_values cmd = 0;
int no_more_options = 0;
while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts))
while (optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
@ -231,15 +217,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
case aTest:
cmd = pargs->r_opt;
break;
default: pargs->err = 2; break;
}
}
return cmd;
}
@ -250,7 +230,9 @@ main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
enum cmd_and_opt_values cmd;
char **command_list = NULL;
int cmdidx;
char *command;
gnupg_reopen_std ("gpg-card-tool");
set_strusage (my_strusage);
@ -276,44 +258,80 @@ main (int argc, char **argv)
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
cmd = parse_arguments (&pargs, opts);
parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
}
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.gpgsm_program)
opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM);
/* Run the selected command. */
switch (cmd)
/* Now build the list of commands. We guess the size of the array
* by assuming each item is a complete command. Obviously this will
* be rarely the case, but it is less code to allocate a possible
* too large array. */
command_list = xcalloc (argc+1, sizeof *command_list);
cmdidx = 0;
command = NULL;
while (argc)
{
case aTest:
if (!argc)
wrong_args ("--test KEYGRIP");
err = test_get_matching_keys (*argv);
break;
for ( ; argc && strcmp (*argv, "--"); argc--, argv++)
{
if (!command)
command = xstrdup (*argv);
else
{
char *tmp = xstrconcat (command, " ", *argv, NULL);
xfree (command);
command = tmp;
}
}
if (argc)
{ /* Skip the double dash. */
argc--;
argv++;
}
if (command)
{
command_list[cmdidx++] = command;
command = NULL;
}
}
opt.interactive = !cmdidx;
default:
if (opt.interactive)
{
interactive_loop ();
err = 0;
break;
}
else
{
struct card_info_s info_buffer;
card_info_t info = &info_buffer;
err = 0;
for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++)
{
err = dispatch_command (info, command);
if (err)
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0; /* This was a "quit". */
else if (command && !opt.quiet)
log_info ("stopped at command '%s'\n", command);
}
flush_keyblock_cache ();
if (command_list)
{
for (cmdidx=0; command_list[cmdidx]; cmdidx++)
xfree (command_list[cmdidx]);
xfree (command_list);
}
if (err)
gnupg_status_printf (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
@ -421,22 +439,24 @@ put_data_to_file (const char *fname, const void *buffer, size_t length)
static gpg_error_t
print_help (const char *text, ...)
{
estream_t fp;
va_list arg_ptr;
int value;
int any = 0;
tty_fprintf (NULL, "%s\n", text);
fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "%s\n", text);
va_start (arg_ptr, text);
while ((value = va_arg (arg_ptr, int)))
{
if (!any)
tty_fprintf (NULL, "[Supported by: ");
tty_fprintf (NULL, "%s%s", any?", ":"", app_type_string (value));
tty_fprintf (fp, "[Supported by: ");
tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value));
any = 1;
}
if (any)
tty_fprintf (NULL, "]\n");
tty_fprintf (fp, "]\n");
va_end (arg_ptr);
return 0;
@ -588,18 +608,6 @@ mem_is_zero (const char *mem, unsigned int memlen)
}
/* Return true if the buffer MEM or length MEMLEN consists only of 0xFF. */
static int
mem_is_ff (const char *mem, unsigned int memlen)
{
int i;
for (i=0; i < memlen && mem[i] == '\xff'; i++)
;
return (i == memlen);
}
/* Helper to list a single keyref. */
static void
@ -909,7 +917,7 @@ list_piv (card_info_t info, estream_t fp)
static void
list_card (card_info_t info)
{
estream_t fp = NULL;
estream_t fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "Reader ...........: %s\n",
info->reader? info->reader : "[none]");
@ -2716,6 +2724,7 @@ static struct
{ "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")},
{ "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")},
{ "auth" , cmdAUTHENTICATE, 0, NULL },
{ "reset" , cmdRESET, 0, N_("send a reset to the card daemon")},
{ "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")},
@ -2729,7 +2738,169 @@ static struct
};
/* The main loop. */
/* The command line command dispatcher. */
static gpg_error_t
dispatch_command (card_info_t info, const char *orig_command)
{
gpg_error_t err = 0;
enum cmdids cmd; /* The command. */
char *command; /* A malloced copy of ORIG_COMMAND. */
char *argstr; /* The argument as a string. */
int i;
int ignore_error;
if ((ignore_error = *orig_command == '-'))
orig_command++;
command = xstrdup (orig_command);
argstr = NULL;
if ((argstr = strchr (command, ' ')))
{
*argstr++ = 0;
trim_spaces (command);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (command, cmds[i].name ))
break;
cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */
/* Make sure we have valid strings for the args. They are allowed
* to be modified and must thus point to a buffer. */
if (!argstr)
argstr = command + strlen (command);
/* For most commands we need to make sure that we have a card. */
if (!info)
; /* Help mode */
else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD)
&& !info->initialized)
{
err = scd_learn (info);
if (err)
{
log_error ("Error reading card: %s\n", gpg_strerror (err));
goto leave;
}
}
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Stop processing.", 0);
else
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
dispatch_command (NULL, argstr);
else
{
es_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
es_printf ("Prefix a command with a dash to ignore its error.\n");
}
break;
case cmdLIST:
if (!info)
print_help ("LIST\n\n"
"Show content of the card.", 0);
else
{
err = scd_learn (info);
if (err)
log_error ("Error reading card: %s\n", gpg_strerror (err));
else
list_card (info);
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL);
}
break;
case cmdADMIN:
/* This is a NOP in non-interactive mode. */
break;
case cmdVERIFY: err = cmd_verify (info, argstr); break;
case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info); break;
case cmdPASSWD: err = cmd_passwd (info, 1); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTORYRESET: err = cmd_factoryreset (info); break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdKEYATTR: err = cmd_keyattr (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdINVCMD:
default:
log_error (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
leave:
/* Return GPG_ERR_EOF only if its origin was "quit". */
es_fflush (es_stdout);
if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT)
err = gpg_error (GPG_ERR_GENERAL);
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
if (ignore_error)
{
log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err));
err = 0;
}
else
log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err));
}
xfree (command);
return err;
}
/* The interactive main loop. */
static void
interactive_loop (void)
{
@ -2825,11 +2996,12 @@ interactive_loop (void)
}
/* Make sure we have valid strings for the args. They are
* allowed to be modifed and must thus point to a buffer. */
* allowed to be modified and must thus point to a buffer. */
if (!argstr)
argstr = answer + strlen (answer);
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP))
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD))
{
/* If redisplay is set we know that there was an error reading
* the card. In this case we force a LIST command to retry. */