/* gpg-card-tool.c - An interactive tool to work with cards. * Copyright (C) 2019 g10 Code GmbH Werner Koch * * This file is part of GnuPG. * * This file 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 file 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 Lesser 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 . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include #endif /*HAVE_LIBREADLINE*/ #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/ttyio.h" #define CONTROL_D ('D' - 'A' + 1) /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oStatusFD, oWithColons, oDummy }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_end () }; /* Debug values and macros. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* We keep all global options in the structure OPT. */ struct { int verbose; unsigned int debug; int quiet; int with_colons; const char *gpg_program; const char *gpgsm_program; } opt; static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); #endif /*HAVE_LIBREADLINE*/ /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpg-card-tool"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-card-tool [command] [options] [args] (-h for help)"); break; case 41: p = ("Syntax: gpg-card-tool [command] [options] [args]\n" "Tool to configure cards and tokens\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); exit (2); } /* Command line parsing. */ static enum cmd_and_opt_values 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)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; default: pargs->err = 2; break; } } return cmd; } /* gpg-card-tool main. */ int main (int argc, char **argv) { gpg_error_t err; ARGPARSE_ARGS pargs; enum cmd_and_opt_values cmd; gnupg_reopen_std ("gpg-card-tool"); set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card-tool", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; cmd = 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) { default: interactive_loop (); err = 0; break; } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else gnupg_status_printf (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Print all available information about the current card. */ static void print_card_status (char *serialno, size_t serialnobuflen) { /* struct agent_card_info_s info; */ /* PKT_public_key *pk = xcalloc (1, sizeof *pk); */ /* kbnode_t keyblock = NULL; */ /* int rc; */ /* unsigned int uval; */ /* const unsigned char *thefpr; */ /* unsigned int thefprlen; */ /* int i; */ /* if (serialno && serialnobuflen) */ /* *serialno = 0; */ /* rc = agent_scd_learn (&info, 0); */ /* if (rc) */ /* { */ /* if (opt.with_colons) */ /* es_fputs ("AID:::\n", fp); */ /* log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc)); */ /* xfree (pk); */ /* return; */ /* } */ /* if (opt.with_colons) */ /* es_fprintf (fp, "Reader:%s:", info.reader? info.reader : ""); */ /* else */ /* tty_fprintf (fp, "Reader ...........: %s\n", */ /* info.reader? info.reader : "[none]"); */ /* if (opt.with_colons) */ /* es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : ""); */ /* else */ /* tty_fprintf (fp, "Application ID ...: %s\n", */ /* info.serialno? info.serialno : "[none]"); */ /* if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) */ /* || strlen (info.serialno) != 32 ) */ /* { */ /* if (info.apptype && !strcmp (info.apptype, "NKS")) */ /* { */ /* if (opt.with_colons) */ /* es_fputs ("netkey-card:\n", fp); */ /* log_info ("this is a NetKey card\n"); */ /* } */ /* else if (info.apptype && !strcmp (info.apptype, "DINSIG")) */ /* { */ /* if (opt.with_colons) */ /* es_fputs ("dinsig-card:\n", fp); */ /* log_info ("this is a DINSIG compliant card\n"); */ /* } */ /* else if (info.apptype && !strcmp (info.apptype, "P15")) */ /* { */ /* if (opt.with_colons) */ /* es_fputs ("pkcs15-card:\n", fp); */ /* log_info ("this is a PKCS#15 compliant card\n"); */ /* } */ /* else if (info.apptype && !strcmp (info.apptype, "GELDKARTE")) */ /* { */ /* if (opt.with_colons) */ /* es_fputs ("geldkarte-card:\n", fp); */ /* log_info ("this is a Geldkarte compliant card\n"); */ /* } */ /* else */ /* { */ /* if (opt.with_colons) */ /* es_fputs ("unknown:\n", fp); */ /* } */ /* log_info ("not an OpenPGP card\n"); */ /* agent_release_card_info (&info); */ /* xfree (pk); */ /* return; */ /* } */ /* if (!serialno) */ /* ; */ /* else if (strlen (info.serialno)+1 > serialnobuflen) */ /* log_error ("serial number longer than expected\n"); */ /* else */ /* strcpy (serialno, info.serialno); */ /* if (opt.with_colons) */ /* es_fputs ("openpgp-card:\n", fp); */ /* tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", */ /* info.serialno[12] == '0'?"":info.serialno+12, */ /* info.serialno[13], */ /* info.serialno[14] == '0'?"":info.serialno+14, */ /* info.serialno[15]); */ /* tty_fprintf (fp, "Manufacturer .....: %s\n", */ /* get_manufacturer (xtoi_2(info.serialno+16)*256 */ /* + xtoi_2 (info.serialno+18))); */ /* tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20); */ /* print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); */ /* print_name (fp, "Language prefs ...: ", info.disp_lang); */ /* tty_fprintf (fp, "Salutation .......: %s\n", */ /* info.disp_sex == 1? _("Mr."): */ /* info.disp_sex == 2? _("Mrs.") : ""); */ /* print_name (fp, "URL of public key : ", info.pubkey_url); */ /* print_name (fp, "Login data .......: ", info.login_data); */ /* if (info.private_do[0]) */ /* print_name (fp, "Private DO 1 .....: ", info.private_do[0]); */ /* if (info.private_do[1]) */ /* print_name (fp, "Private DO 2 .....: ", info.private_do[1]); */ /* if (info.private_do[2]) */ /* print_name (fp, "Private DO 3 .....: ", info.private_do[2]); */ /* if (info.private_do[3]) */ /* print_name (fp, "Private DO 4 .....: ", info.private_do[3]); */ /* if (info.cafpr1len) */ /* { */ /* tty_fprintf (fp, "CA fingerprint %d .:", 1); */ /* print_shax_fpr (fp, info.cafpr1, info.cafpr1len); */ /* } */ /* if (info.cafpr2len) */ /* { */ /* tty_fprintf (fp, "CA fingerprint %d .:", 2); */ /* print_shax_fpr (fp, info.cafpr2, info.cafpr2len); */ /* } */ /* if (info.cafpr3len) */ /* { */ /* tty_fprintf (fp, "CA fingerprint %d .:", 3); */ /* print_shax_fpr (fp, info.cafpr3, info.cafpr3len); */ /* } */ /* tty_fprintf (fp, "Signature PIN ....: %s\n", */ /* info.chv1_cached? _("not forced"): _("forced")); */ /* if (info.key_attr[0].algo) */ /* { */ /* tty_fprintf (fp, "Key attributes ...:"); */ /* for (i=0; i < DIM (info.key_attr); i++) */ /* if (info.key_attr[i].algo == PUBKEY_ALGO_RSA) */ /* tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits); */ /* else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH */ /* || info.key_attr[i].algo == PUBKEY_ALGO_ECDSA */ /* || info.key_attr[i].algo == PUBKEY_ALGO_EDDSA) */ /* { */ /* const char *curve_for_print = "?"; */ /* if (info.key_attr[i].curve) */ /* { */ /* const char *oid; */ /* oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL); */ /* if (oid) */ /* curve_for_print = openpgp_oid_to_curve (oid, 0); */ /* } */ /* tty_fprintf (fp, " %s", curve_for_print); */ /* } */ /* tty_fprintf (fp, "\n"); */ /* } */ /* tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", */ /* info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); */ /* tty_fprintf (fp, "PIN retry counter : %d %d %d\n", */ /* info.chvretry[0], info.chvretry[1], info.chvretry[2]); */ /* tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter); */ /* if (info.extcap.kdf) */ /* { */ /* tty_fprintf (fp, "KDF setting ......: %s\n", */ /* info.kdf_do_enabled ? "on" : "off"); */ /* } */ /* if (info.extcap.bt) */ /* { */ /* tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", */ /* info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off", */ /* info.uif[2] ? "on" : "off"); */ /* } */ /* tty_fprintf (fp, "Signature key ....:"); */ /* print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); */ /* if (info.fpr1len && info.fpr1time) */ /* { */ /* tty_fprintf (fp, " created ....: %s\n", */ /* isotimestamp (info.fpr1time)); */ /* print_keygrip (fp, info.grp1); */ /* } */ /* tty_fprintf (fp, "Encryption key....:"); */ /* print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len); */ /* if (info.fpr2len && info.fpr2time) */ /* { */ /* tty_fprintf (fp, " created ....: %s\n", */ /* isotimestamp (info.fpr2time)); */ /* print_keygrip (fp, info.grp2); */ /* } */ /* tty_fprintf (fp, "Authentication key:"); */ /* print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len); */ /* if (info.fpr3len && info.fpr3time) */ /* { */ /* tty_fprintf (fp, " created ....: %s\n", */ /* isotimestamp (info.fpr3time)); */ /* print_keygrip (fp, info.grp3); */ /* } */ /* tty_fprintf (fp, "General key info..: "); */ /* thefpr = (info.fpr1len? info.fpr1 : info.fpr2len? info.fpr2 : */ /* info.fpr3len? info.fpr3 : NULL); */ /* thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len : */ /* info.fpr3len? info.fpr3len : 0); */ /* /\* If the fingerprint is all 0xff, the key has no associated */ /* OpenPGP certificate. *\/ */ /* if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */ /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ /* { */ /* print_pubkey_info (ctrl, fp, pk); */ /* if (keyblock) */ /* print_card_key_info (fp, keyblock); */ /* } */ /* else */ /* tty_fprintf (fp, "[none]\n"); */ /* release_kbnode (keyblock); */ /* free_public_key (pk); */ /* agent_release_card_info (&info); */ } static void cmd_verify (void) { /* agent_scd_checkpin (serialnobuf); */ } static void cmd_name (void) { /* change_name (); */ } static void cmd_url (void) { /* change_url (); */ } static void cmd_fetch (void) { /* fetch_url (); */ } static void cmd_login (char *arg_string) { /* change_login (arg_string); */ } static void cmd_lang (void) { /* change_lang (); */ } static void cmd_salut (void) { /* change_salut (); */ } static void cmd_cafpr (int arg_number) { if ( arg_number < 1 || arg_number > 3 ) tty_printf ("usage: cafpr N\n" " 1 <= N <= 3\n"); /* else */ /* change_cafpr (arg_number); */ } static void cmd_privatedo (int arg_number, char *arg_string) { if ( arg_number < 1 || arg_number > 4 ) tty_printf ("usage: privatedo N\n" " 1 <= N <= 4\n"); /* else */ /* change_private_do (arg_string, arg_number); */ } static void cmd_writecert (int arg_number, char *arg_rest) { if ( arg_number != 3 ) tty_printf ("usage: writecert 3 < FILE\n"); /* else */ /* change_cert (arg_rest); */ } static void cmd_readcert (int arg_number, char *arg_rest) { if ( arg_number != 3 ) tty_printf ("usage: readcert 3 > FILE\n"); /* else */ /* read_cert (arg_rest); */ } static void cmd_forcesig (void) { /* toggle_forcesig (); */ } static void cmd_generate (void) { /* generate_card_keys (); */ } static void cmd_passwd (int allow_admin) { /* change_pin (0, allow_admin); */ } static void cmd_unblock (int allow_admin) { /* change_pin (1, allow_admin); */ } static void cmd_factoryreset (void) { /* factory_reset (); */ } static void cmd_kdfsetup (char *argstring) { /* kdf_setup (arg_string); */ } static void cmd_keyattr (void) { /* key_attr (); */ } static void cmd_uif (int arg_number, char *arg_rest) { if ( arg_number < 1 || arg_number > 3 ) tty_printf ("usage: uif N [on|off|permanent]\n" " 1 <= N <= 3\n"); /* else */ /* uif (arg_number, arg_rest); */ } /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids { cmdNOP = 0, cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, cmdKEYATTR, cmdUIF, cmdINVCMD }; static struct { const char *name; enum cmdids id; int admin_only; const char *desc; } cmds[] = { { "quit" , cmdQUIT , 0, N_("quit this menu")}, { "q" , cmdQUIT , 0, NULL }, { "admin" , cmdADMIN , 0, N_("show admin commands")}, { "help" , cmdHELP , 0, N_("show this help")}, { "?" , cmdHELP , 0, NULL }, { "list" , cmdLIST , 0, N_("list all available data")}, { "l" , cmdLIST , 0, NULL }, { "debug" , cmdDEBUG , 0, NULL }, { "name" , cmdNAME , 1, N_("change card holder's name")}, { "url" , cmdURL , 1, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN , 1, N_("change the login name")}, { "lang" , cmdLANG , 1, N_("change the language preferences")}, { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, 1, N_("generate new keys")}, { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, NULL }, { "readcert", cmdREADCERT, 0, NULL }, { "writecert", cmdWRITECERT, 1, NULL }, { NULL, cmdINVCMD, 0, NULL } }; /* The main loop. */ static void interactive_loop (void) { char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ int cmd_admin_only; /* The command is an admin only command. */ int arg_number; /* The first argument as a number. */ char *arg_string = ""; /* The first argument as a string. */ char *arg_rest = ""; /* The remaining arguments. */ int redisplay = 1; /* Whether to redisplay the main info. */ int allow_admin = 0; /* Whether admin commands are allowed. */ char serialnobuf[50]; char *p; int i; for (;;) { tty_printf ("\n"); if (redisplay) { print_card_status (serialnobuf, DIM (serialnobuf)); tty_printf("\n"); redisplay = 0; } do { xfree (answer); tty_enable_completion (command_completion); answer = tty_get (_("gpg/card> ")); tty_kill_prompt(); tty_disable_completion (); trim_spaces(answer); } while ( *answer == '#' ); arg_number = 0; cmd_admin_only = 0; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) cmd = cmdQUIT; else { if ((p=strchr (answer,' '))) { *p++ = 0; trim_spaces (answer); trim_spaces (p); arg_number = atoi (p); arg_string = p; arg_rest = p; while (digitp (arg_rest)) arg_rest++; while (spacep (arg_rest)) arg_rest++; } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (answer, cmds[i].name )) break; cmd = cmds[i].id; cmd_admin_only = cmds[i].admin_only; } if (!allow_admin && cmd_admin_only) { tty_printf ("\n"); tty_printf (_("Admin-only command\n")); continue; } switch (cmd) { case cmdNOP: break; case cmdQUIT: goto leave; case cmdHELP: for (i=0; cmds[i].name; i++ ) if(cmds[i].desc && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin))) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); break; case cmdADMIN: if ( !strcmp (arg_string, "on") ) allow_admin = 1; else if ( !strcmp (arg_string, "off") ) allow_admin = 0; else if ( !strcmp (arg_string, "verify") ) { /* Force verification of the Admin Command. However, this is only done if the retry counter is at initial state. */ /* FIXME: Must depend on the type of the card. */ /* char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); */ /* strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); */ /* allow_admin = !agent_scd_checkpin (tmp); */ /* xfree (tmp); */ } else /* Toggle. */ allow_admin=!allow_admin; if(allow_admin) tty_printf(_("Admin commands are allowed\n")); else tty_printf(_("Admin commands are not allowed\n")); break; case cmdVERIFY: cmd_verify (); redisplay = 1; break; case cmdLIST: redisplay = 1; break; case cmdNAME: cmd_name (); break; case cmdURL: cmd_url (); break; case cmdFETCH: cmd_fetch (); break; case cmdLOGIN: cmd_login (arg_string); break; case cmdLANG: cmd_lang (); break; case cmdSALUT: cmd_salut (); break; case cmdCAFPR: cmd_cafpr (arg_number); break; case cmdPRIVATEDO: cmd_privatedo (arg_number, arg_string); break; case cmdWRITECERT: cmd_writecert (arg_number, arg_rest); break; case cmdREADCERT: cmd_readcert (arg_number, arg_rest); break; case cmdFORCESIG: cmd_forcesig (); break; case cmdGENERATE: cmd_generate (); break; case cmdPASSWD: cmd_passwd (allow_admin); break; case cmdUNBLOCK: cmd_unblock (allow_admin); break; case cmdFACTORYRESET: cmd_factoryreset (); break; case cmdKDFSETUP: cmd_kdfsetup (arg_string); break; case cmdKEYATTR: cmd_keyattr (); break; case cmdUIF: cmd_uif (arg_number, arg_rest); break; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ } /* End of main menu loop. */ leave: xfree (answer); } #ifdef HAVE_LIBREADLINE /* Helper function for readline's command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes * saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen(text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text. */ if (cmds[list_index++].desc && !strncmp (name, text, len)) return strdup(name); } return NULL; } /* Second helper function for readline's command completion. */ static char ** command_completion (const char *text, int start, int end) { (void)end; /* If we are at the start of a line, we try and command-complete. * If not, just do nothing for now. */ if (!start) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/