diff --git a/common/common-defs.h b/common/common-defs.h index b1928e611..cad5405d0 100644 --- a/common/common-defs.h +++ b/common/common-defs.h @@ -47,7 +47,8 @@ void tty_private_set_rl_hooks (void (*init_stream) (FILE *), void (*inhibit_completion) (int), void (*cleanup_after_signal) (void), 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)); diff --git a/common/gpgrlhelp.c b/common/gpgrlhelp.c index 680d9998b..fd3a48f8a 100644 --- a/common/gpgrlhelp.c +++ b/common/gpgrlhelp.c @@ -77,11 +77,77 @@ init_stream (FILE *fp) 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*/ /* 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 gnupg_rl_initialize (void) { @@ -91,7 +157,8 @@ gnupg_rl_initialize (void) inhibit_completion, cleanup_after_signal, readline, - add_history); + add_history, + read_write_history); rl_readline_name = GNUPG_NAME; #endif } diff --git a/common/ttyio.c b/common/ttyio.c index 4c095bc03..a27c095cf 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -101,7 +101,7 @@ static void (*my_rl_cleanup_after_signal) (void); static void (*my_rl_init_stream) (FILE *); static char *(*my_rl_readline) (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 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 (*cleanup_after_signal) (void), 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_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_readline = readline_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); } diff --git a/common/ttyio.h b/common/ttyio.h index 5bff82fbb..46bcc2ffc 100644 --- a/common/ttyio.h +++ b/common/ttyio.h @@ -66,6 +66,8 @@ void tty_disable_completion (void); #define tty_enable_completion(x) #define tty_disable_completion() #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_rl_after_signal (void); diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi index 0865798d2..be19704cc 100644 --- a/doc/gpg-card.texi +++ b/doc/gpg-card.texi @@ -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 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} @opindex agent-program Specify the agent program to be started if none is running. The diff --git a/doc/tools.texi b/doc/tools.texi index 985c0a75c..537c3c72a 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -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 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} @itemx --run @var{file} @opindex run @@ -1611,6 +1617,9 @@ string @code{true} or @code{yes}. The evaluation is done by passing @item /run @var{file} Run commands from @var{file}. +@item /history --clear +Clear the command history. + @item /bye Terminate the connection and the program. diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 96b2bcf44..2fcc120fb 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -48,6 +48,8 @@ #define CONTROL_D ('D' - 'A' + 1) +#define HISTORYNAME ".gpg-card_history" + /* Constants to identify the commands and options. */ enum opt_values { @@ -73,6 +75,7 @@ enum opt_values oLCmessages, oNoKeyLookup, + oNoHistory, oDummy }; @@ -99,6 +102,8 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup", "use --no-key-lookup for \"list\""), + ARGPARSE_s_n (oNoHistory,"no-history", + "do not use the command history file"), 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 oNoKeyLookup: opt.no_key_lookup = 1; break; + case oNoHistory: opt.no_history = 1; break; default: pargs->err = 2; break; } @@ -315,6 +321,9 @@ main (int argc, char **argv) } opt.interactive = !cmdidx; + if (!opt.interactive) + opt.no_history = 1; + if (opt.interactive) { 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 @@ -3509,7 +3544,7 @@ enum cmdids cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, - cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, + cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdHISTORY, cmdINVCMD }; @@ -3551,6 +3586,7 @@ static struct { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { "apdu", cmdAPDU, NULL}, + { "history", cmdHISTORY, N_("manage the command history")}, { 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 cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; + case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */ case cmdINVCMD: default: @@ -3735,10 +3772,19 @@ interactive_loop (void) card_info_t info = &info_buffer; char *p; int i; + const char *historyname = NULL; /* In the interactive mode we do not want to print the program prefix. */ 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 (;;) { if (help_arg) @@ -3818,7 +3864,7 @@ interactive_loop (void) argstr = answer + strlen (answer); 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 * 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 cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; + case cmdHISTORY: err = cmd_history (info, argstr); break; case cmdINVCMD: default: @@ -3962,7 +4009,12 @@ interactive_loop (void) } /* End of main menu loop. */ 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); + xfree (historyname); xfree (answer); } diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 56e117bf3..067720d6c 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -40,6 +40,8 @@ struct 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: */ session_env_t session_env; char *lc_ctype; diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index 746ac7daf..cde086770 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -44,6 +44,9 @@ #define CONTROL_D ('D' - 'A' + 1) #define octdigitp(p) (*(p) >= '0' && *(p) <= '7') +#define HISTORYNAME ".gpg-connect_history" + + /* Constants to identify the commands and options. */ enum cmd_and_opt_values { @@ -67,6 +70,7 @@ enum cmd_and_opt_values oDirmngr, oKeyboxd, oUIServer, + oNoHistory, oNoAutostart }; @@ -97,6 +101,8 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), 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 (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), @@ -127,6 +133,7 @@ struct unsigned int connect_flags; /* Flags used for connecting. */ int enable_varsubst; /* Set if variable substitution is enabled. */ int trim_leading_spaces; + int no_history; } opt; @@ -1178,6 +1185,7 @@ main (int argc, char **argv) } loopstack[20]; int loopidx; char **cmdline_commands = NULL; + char *historyname = NULL; early_system_init (); gnupg_rl_initialize (); @@ -1210,6 +1218,7 @@ main (int argc, char **argv) case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break; case oNoAutostart: opt.autostart = 0; break; + case oNoHistory: opt.no_history = 1; break; case oHex: opt.hex = 1; break; case oDecode: opt.decode = 1; break; case oDirmngr: opt.use_dirmngr = 1; break; @@ -1422,6 +1431,15 @@ main (int argc, char **argv) { keep_line = 0; 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 ("> "); n = strlen (line); if (n==1 && *line == CONTROL_D) @@ -1817,6 +1835,15 @@ main (int argc, char **argv) { 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")) { puts ( @@ -1843,6 +1870,7 @@ main (int argc, char **argv) "/if VAR Begin conditional block controlled by VAR.\n" "/while VAR Begin loop controlled by VAR.\n" "/end End loop or condition\n" +"/history Manage the history\n" "/bye Terminate gpg-connect-agent.\n" "/help Print this help."); } @@ -1895,6 +1923,12 @@ main (int argc, char **argv) opt.use_keyboxd? "keyboxd" : "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 nicely says good bye to the server, which results in a SIGPIPE if the server died. Unfortunately, libassuan does not ignore @@ -1904,6 +1938,7 @@ main (int argc, char **argv) assuan_release (ctx); else gpgrt_annotate_leaked_object (ctx); + xfree (historyname); xfree (line); return 0; }