Support a history file in gpg-card and gpg-connect-agent.

* common/gpgrlhelp.c (read_write_history): New.
(gnupg_rl_initialize): Register new function.
* common/ttyio.c (my_rl_rw_history): New var.
(tty_private_set_rl_hooks): Add arg read_write_history.
(tty_read_history): New.
(tty_write_history): New.
* tools/gpg-card.c (HISTORYNAME): New.
(oNoHistory): New enum value.
(opts): New option --no-history.
(cmd_history): New.
(cmds): New command "history".
(interactive_loop): Read and save the history.
* tools/gpg-connect-agent.c (HISTORYNAME): New.
(opts): New option --no-history.
(main): Read and save the history.  New command /history.
--

Yeah, finally we have stored history; I should have added this much
earlier.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2020-07-02 15:47:57 +02:00
parent 07aef873eb
commit d70b8769c8
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
9 changed files with 216 additions and 7 deletions

View File

@ -47,7 +47,8 @@ void tty_private_set_rl_hooks (void (*init_stream) (FILE *),
void (*inhibit_completion) (int), void (*inhibit_completion) (int),
void (*cleanup_after_signal) (void), void (*cleanup_after_signal) (void),
char *(*readline_fun) (const char*), char *(*readline_fun) (const char*),
void (*add_history_fun) (const char*)); void (*add_history_fun) (const char*),
int (*rw_history_fun)(const char *, int, int));

View File

@ -77,11 +77,77 @@ init_stream (FILE *fp)
rl_inhibit_completion = 1; rl_inhibit_completion = 1;
} }
/* Read or write the history to or from the file FILENAME. The
* behaviour depends on the flag WRITE_MODE:
*
* In read mode (WRITE_MODE is false) these semantics are used:
*
* If NLINES is positive only this number of lines are read from the
* history and the history is always limited to that number of
* lines. A negative value for NLINES is undefined.
*
* If FILENAME is NULL the current history is cleared. If NLINES is
* positive the number of lines stored in the history is limited to
* that number. A negative value for NLINES is undefined.
*
* If WRITE_MODE is true these semantics are used:
*
* If NLINES is negative the history and the history file are
* cleared; if it is zero the entire history is written to the file;
* if it is positive the history is written to the file and the file
* is truncated to this number of lines.
*
* If FILENAME is NULL no file operations are done but if NLINES is
* negative the entire history is cleared.
*
* On success 0 is returned; on error -1 is returned and ERRNO is set.
*/
static int
read_write_history (const char *filename, int write_mode, int nlines)
{
int rc;
if (write_mode)
{
if (nlines < 0)
clear_history ();
rc = filename? write_history (filename) : 0;
if (!rc && filename && nlines > 0)
rc = history_truncate_file (filename, nlines);
if (rc)
{
gpg_err_set_errno (rc);
return -1;
}
}
else
{
clear_history ();
if (filename)
{
if (nlines)
rc = read_history_range (filename, 0, nlines);
else
rc = read_history (filename);
if (rc)
{
gpg_err_set_errno (rc);
return -1;
}
}
if (nlines > 0)
stifle_history (nlines);
}
return 0;
}
#endif /*HAVE_LIBREADLINE*/ #endif /*HAVE_LIBREADLINE*/
/* Initialize our readline code. This should be called as early as /* Initialize our readline code. This should be called as early as
possible as it is actually a constructur. */ * possible as it is actually a constructor. */
void void
gnupg_rl_initialize (void) gnupg_rl_initialize (void)
{ {
@ -91,7 +157,8 @@ gnupg_rl_initialize (void)
inhibit_completion, inhibit_completion,
cleanup_after_signal, cleanup_after_signal,
readline, readline,
add_history); add_history,
read_write_history);
rl_readline_name = GNUPG_NAME; rl_readline_name = GNUPG_NAME;
#endif #endif
} }

View File

@ -101,7 +101,7 @@ static void (*my_rl_cleanup_after_signal) (void);
static void (*my_rl_init_stream) (FILE *); static void (*my_rl_init_stream) (FILE *);
static char *(*my_rl_readline) (const char*); static char *(*my_rl_readline) (const char*);
static void (*my_rl_add_history) (const char*); static void (*my_rl_add_history) (const char*);
static int (*my_rl_rw_history)(const char *, int, int);
/* This is a wrapper around ttyname so that we can use it even when /* This is a wrapper around ttyname so that we can use it even when
the standard streams are redirected. It figures the name out the the standard streams are redirected. It figures the name out the
@ -703,7 +703,8 @@ tty_private_set_rl_hooks (void (*init_stream) (FILE *),
void (*inhibit_completion) (int), void (*inhibit_completion) (int),
void (*cleanup_after_signal) (void), void (*cleanup_after_signal) (void),
char *(*readline_fun) (const char*), char *(*readline_fun) (const char*),
void (*add_history_fun) (const char*)) void (*add_history_fun) (const char*),
int (*rw_history_fun)(const char *, int, int))
{ {
my_rl_init_stream = init_stream; my_rl_init_stream = init_stream;
my_rl_set_completer = set_completer; my_rl_set_completer = set_completer;
@ -711,6 +712,40 @@ tty_private_set_rl_hooks (void (*init_stream) (FILE *),
my_rl_cleanup_after_signal = cleanup_after_signal; my_rl_cleanup_after_signal = cleanup_after_signal;
my_rl_readline = readline_fun; my_rl_readline = readline_fun;
my_rl_add_history = add_history_fun; my_rl_add_history = add_history_fun;
my_rl_rw_history = rw_history_fun;
}
/* Read the history from FILENAME or limit the size of the history.
* If FILENAME is NULL and NLINES is zero the current history is
* cleared. Returns 0 on success or -1 on error and sets ERRNO. No
* error is return if readline support is not available. */
int
tty_read_history (const char *filename, int nlines)
{
int rc;
if (!my_rl_rw_history)
return 0;
rc = my_rl_rw_history (filename, 0, nlines);
if (rc && gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
rc = 0;
return rc;
}
/* Write the current history to the file FILENAME. Returns 0 on
* success or -1 on error and sets ERRNO. No error is return if
* readline support is not available. */
int
tty_write_history (const char *filename)
{
if (!my_rl_rw_history)
return 0;
return my_rl_rw_history (filename, 1, 0);
} }

View File

@ -66,6 +66,8 @@ void tty_disable_completion (void);
#define tty_enable_completion(x) #define tty_enable_completion(x)
#define tty_disable_completion() #define tty_disable_completion()
#endif #endif
int tty_read_history (const char *filename, int nlines);
int tty_write_history (const char *filename);
void tty_cleanup_after_signal (void); void tty_cleanup_after_signal (void);
void tty_cleanup_rl_after_signal (void); void tty_cleanup_rl_after_signal (void);

View File

@ -105,6 +105,12 @@ Do not start the gpg-agent if it has not yet been started and its
service is required. This option is mostly useful on machines where service is required. This option is mostly useful on machines where
the connection to gpg-agent has been redirected to another machines. the connection to gpg-agent has been redirected to another machines.
@item --no-history
@opindex --no-history
In interactive mode the command line history is usually saved and
restored to and from a file below the GnuPG home directory. This
option inhibits the use of that file.
@item --agent-program @var{file} @item --agent-program @var{file}
@opindex agent-program @opindex agent-program
Specify the agent program to be started if none is running. The Specify the agent program to be started if none is running. The

View File

@ -1391,6 +1391,12 @@ passing. This option makes it use the old mode.
Do not start the gpg-agent or the dirmngr if it has not yet been Do not start the gpg-agent or the dirmngr if it has not yet been
started. started.
@item --no-history
@opindex --no-history
In interactive mode the command line history is usually saved and
restored to and from a file below the GnuPG home directory. This
option inhibits the use of that file.
@item -r @var{file} @item -r @var{file}
@itemx --run @var{file} @itemx --run @var{file}
@opindex run @opindex run
@ -1611,6 +1617,9 @@ string @code{true} or @code{yes}. The evaluation is done by passing
@item /run @var{file} @item /run @var{file}
Run commands from @var{file}. Run commands from @var{file}.
@item /history --clear
Clear the command history.
@item /bye @item /bye
Terminate the connection and the program. Terminate the connection and the program.

View File

@ -48,6 +48,8 @@
#define CONTROL_D ('D' - 'A' + 1) #define CONTROL_D ('D' - 'A' + 1)
#define HISTORYNAME ".gpg-card_history"
/* Constants to identify the commands and options. */ /* Constants to identify the commands and options. */
enum opt_values enum opt_values
{ {
@ -73,6 +75,7 @@ enum opt_values
oLCmessages, oLCmessages,
oNoKeyLookup, oNoKeyLookup,
oNoHistory,
oDummy oDummy
}; };
@ -99,6 +102,8 @@ static gpgrt_opt_t opts[] = {
ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup", ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup",
"use --no-key-lookup for \"list\""), "use --no-key-lookup for \"list\""),
ARGPARSE_s_n (oNoHistory,"no-history",
"do not use the command history file"),
ARGPARSE_end () ARGPARSE_end ()
}; };
@ -228,6 +233,7 @@ parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
case oNoKeyLookup: opt.no_key_lookup = 1; break; case oNoKeyLookup: opt.no_key_lookup = 1; break;
case oNoHistory: opt.no_history = 1; break;
default: pargs->err = 2; break; default: pargs->err = 2; break;
} }
@ -315,6 +321,9 @@ main (int argc, char **argv)
} }
opt.interactive = !cmdidx; opt.interactive = !cmdidx;
if (!opt.interactive)
opt.no_history = 1;
if (opt.interactive) if (opt.interactive)
{ {
interactive_loop (); interactive_loop ();
@ -3498,6 +3507,32 @@ cmd_apdu (card_info_t info, char *argstr)
} }
static gpg_error_t
cmd_history (card_info_t info, char *argstr)
{
int opt_list, opt_clear;
opt_list = has_option (argstr, "--list");
opt_clear = has_option (argstr, "--clear");
if (!info || !(opt_list || opt_clear))
return print_help
("HISTORY --list\n"
" List the command history\n"
"HISTORY --clear\n"
" Clear the command history",
0);
if (opt_list)
tty_printf ("Sorry, history listing not yet possible\n");
if (opt_clear)
tty_read_history (NULL, 0);
return 0;
}
/* Data used by the command parser. This needs to be outside of the /* Data used by the command parser. This needs to be outside of the
@ -3509,7 +3544,7 @@ enum cmdids
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdHISTORY,
cmdINVCMD cmdINVCMD
}; };
@ -3551,6 +3586,7 @@ static struct
{ "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
{ "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
{ "apdu", cmdAPDU, NULL}, { "apdu", cmdAPDU, NULL},
{ "history", cmdHISTORY, N_("manage the command history")},
{ NULL, cmdINVCMD, NULL } { NULL, cmdINVCMD, NULL }
}; };
@ -3679,6 +3715,7 @@ dispatch_command (card_info_t info, const char *orig_command)
case cmdUIF: err = cmd_uif (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break;
case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */
case cmdINVCMD: case cmdINVCMD:
default: default:
@ -3735,10 +3772,19 @@ interactive_loop (void)
card_info_t info = &info_buffer; card_info_t info = &info_buffer;
char *p; char *p;
int i; int i;
const char *historyname = NULL;
/* In the interactive mode we do not want to print the program prefix. */ /* In the interactive mode we do not want to print the program prefix. */
log_set_prefix (NULL, 0); log_set_prefix (NULL, 0);
if (!opt.no_history)
{
historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL);
if (tty_read_history (historyname, 500))
log_info ("error reading '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
}
for (;;) for (;;)
{ {
if (help_arg) if (help_arg)
@ -3818,7 +3864,7 @@ interactive_loop (void)
argstr = answer + strlen (answer); argstr = answer + strlen (answer);
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD)) || cmd == cmdHISTORY || cmd == cmdINVCMD))
{ {
/* If redisplay is set we know that there was an error reading /* If redisplay is set we know that there was an error reading
* the card. In this case we force a LIST command to retry. */ * the card. In this case we force a LIST command to retry. */
@ -3926,6 +3972,7 @@ interactive_loop (void)
case cmdUIF: err = cmd_uif (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break;
case cmdHISTORY: err = cmd_history (info, argstr); break;
case cmdINVCMD: case cmdINVCMD:
default: default:
@ -3962,7 +4009,12 @@ interactive_loop (void)
} /* End of main menu loop. */ } /* End of main menu loop. */
leave: leave:
if (historyname && tty_write_history (historyname))
log_info ("error writing '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
release_card_info (info); release_card_info (info);
xfree (historyname);
xfree (answer); xfree (answer);
} }

View File

@ -40,6 +40,8 @@ struct
int no_key_lookup; /* Assume --no-key-lookup for "list". */ int no_key_lookup; /* Assume --no-key-lookup for "list". */
int no_history; /* Do not use the command line history. */
/* Options passed to the gpg-agent: */ /* Options passed to the gpg-agent: */
session_env_t session_env; session_env_t session_env;
char *lc_ctype; char *lc_ctype;

View File

@ -44,6 +44,9 @@
#define CONTROL_D ('D' - 'A' + 1) #define CONTROL_D ('D' - 'A' + 1)
#define octdigitp(p) (*(p) >= '0' && *(p) <= '7') #define octdigitp(p) (*(p) >= '0' && *(p) <= '7')
#define HISTORYNAME ".gpg-connect_history"
/* Constants to identify the commands and options. */ /* Constants to identify the commands and options. */
enum cmd_and_opt_values enum cmd_and_opt_values
{ {
@ -67,6 +70,7 @@ enum cmd_and_opt_values
oDirmngr, oDirmngr,
oKeyboxd, oKeyboxd,
oUIServer, oUIServer,
oNoHistory,
oNoAutostart oNoAutostart
}; };
@ -97,6 +101,8 @@ static gpgrt_opt_t opts[] = {
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oNoHistory,"no-history",
"do not use the command history file"),
ARGPARSE_s_s (oHomedir, "homedir", "@" ), ARGPARSE_s_s (oHomedir, "homedir", "@" ),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
@ -127,6 +133,7 @@ struct
unsigned int connect_flags; /* Flags used for connecting. */ unsigned int connect_flags; /* Flags used for connecting. */
int enable_varsubst; /* Set if variable substitution is enabled. */ int enable_varsubst; /* Set if variable substitution is enabled. */
int trim_leading_spaces; int trim_leading_spaces;
int no_history;
} opt; } opt;
@ -1178,6 +1185,7 @@ main (int argc, char **argv)
} loopstack[20]; } loopstack[20];
int loopidx; int loopidx;
char **cmdline_commands = NULL; char **cmdline_commands = NULL;
char *historyname = NULL;
early_system_init (); early_system_init ();
gnupg_rl_initialize (); gnupg_rl_initialize ();
@ -1210,6 +1218,7 @@ main (int argc, char **argv)
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break; case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break;
case oNoAutostart: opt.autostart = 0; break; case oNoAutostart: opt.autostart = 0; break;
case oNoHistory: opt.no_history = 1; break;
case oHex: opt.hex = 1; break; case oHex: opt.hex = 1; break;
case oDecode: opt.decode = 1; break; case oDecode: opt.decode = 1; break;
case oDirmngr: opt.use_dirmngr = 1; break; case oDirmngr: opt.use_dirmngr = 1; break;
@ -1422,6 +1431,15 @@ main (int argc, char **argv)
{ {
keep_line = 0; keep_line = 0;
xfree (line); xfree (line);
if (!historyname && !opt.no_history)
{
historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL);
if (tty_read_history (historyname, 500))
log_info ("error reading '%s': %s\n",
historyname,
gpg_strerror (gpg_error_from_syserror ()));
}
line = tty_get ("> "); line = tty_get ("> ");
n = strlen (line); n = strlen (line);
if (n==1 && *line == CONTROL_D) if (n==1 && *line == CONTROL_D)
@ -1817,6 +1835,15 @@ main (int argc, char **argv)
{ {
gnupg_sleep (1); gnupg_sleep (1);
} }
else if (!strcmp (cmd, "history"))
{
if (!strcmp (p, "--clear"))
{
tty_read_history (NULL, 0);
}
else
log_error ("Only \"/history --clear\" is supported\n");
}
else if (!strcmp (cmd, "help")) else if (!strcmp (cmd, "help"))
{ {
puts ( puts (
@ -1843,6 +1870,7 @@ main (int argc, char **argv)
"/if VAR Begin conditional block controlled by VAR.\n" "/if VAR Begin conditional block controlled by VAR.\n"
"/while VAR Begin loop controlled by VAR.\n" "/while VAR Begin loop controlled by VAR.\n"
"/end End loop or condition\n" "/end End loop or condition\n"
"/history Manage the history\n"
"/bye Terminate gpg-connect-agent.\n" "/bye Terminate gpg-connect-agent.\n"
"/help Print this help."); "/help Print this help.");
} }
@ -1895,6 +1923,12 @@ main (int argc, char **argv)
opt.use_keyboxd? "keyboxd" : opt.use_keyboxd? "keyboxd" :
"agent"); "agent");
if (historyname && tty_write_history (historyname))
log_info ("error writing '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
/* XXX: We would like to release the context here, but libassuan /* XXX: We would like to release the context here, but libassuan
nicely says good bye to the server, which results in a SIGPIPE if nicely says good bye to the server, which results in a SIGPIPE if
the server died. Unfortunately, libassuan does not ignore the server died. Unfortunately, libassuan does not ignore
@ -1904,6 +1938,7 @@ main (int argc, char **argv)
assuan_release (ctx); assuan_release (ctx);
else else
gpgrt_annotate_leaked_object (ctx); gpgrt_annotate_leaked_object (ctx);
xfree (historyname);
xfree (line); xfree (line);
return 0; return 0;
} }