aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-01-31 17:57:16 +0000
committerWerner Koch <[email protected]>2019-01-31 17:57:16 +0000
commit1c0fa3e6f74692d5e9b5f08cda523f0fcec305eb (patch)
tree6f188a78a7c4b39089616132a798558ec9c0335c
parentcard: New command 'authenticate'. (diff)
downloadgnupg-1c0fa3e6f74692d5e9b5f08cda523f0fcec305eb.tar.gz
gnupg-1c0fa3e6f74692d5e9b5f08cda523f0fcec305eb.zip
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 <[email protected]>
-rw-r--r--tools/card-call-scd.c3
-rw-r--r--tools/card-tool.h2
-rw-r--r--tools/gpg-card-tool.c316
3 files changed, 248 insertions, 73 deletions
diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c
index 9a742a728..c51282f14 100644
--- a/tools/card-call-scd.c
+++ b/tools/card-call-scd.c
@@ -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)
diff --git a/tools/card-tool.h b/tools/card-tool.h
index bea618a8c..9aca8131d 100644
--- a/tools/card-tool.h
+++ b/tools/card-tool.h
@@ -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. */
diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c
index 3145229ad..243ee555a 100644
--- a/tools/gpg-card-tool.c
+++ b/tools/gpg-card-tool.c
@@ -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;
@@ -171,14 +168,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)
{
gpg_error_t err;
@@ -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;
- flush_keyblock_cache ();
+ 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. */