summaryrefslogtreecommitdiffstats
path: root/agent
diff options
context:
space:
mode:
Diffstat (limited to 'agent')
-rw-r--r--agent/ChangeLog-20113107
-rw-r--r--agent/Makefile.am118
-rw-r--r--agent/Makefile.in1408
-rw-r--r--agent/agent.h650
-rw-r--r--agent/all-tests.scm35
-rw-r--r--agent/cache.c530
-rw-r--r--agent/call-pinentry.c2091
-rw-r--r--agent/call-scd.c1340
-rw-r--r--agent/command-ssh.c3870
-rw-r--r--agent/command.c3648
-rw-r--r--agent/cvt-openpgp.c1413
-rw-r--r--agent/cvt-openpgp.h37
-rw-r--r--agent/divert-scd.c734
-rw-r--r--agent/findkey.c1827
-rw-r--r--agent/genkey.c650
-rw-r--r--agent/gpg-agent-w32info.rc52
-rw-r--r--agent/gpg-agent.c3266
-rw-r--r--agent/gpg-agent.w32-manifest.in18
-rw-r--r--agent/learncard.c452
-rw-r--r--agent/pkdecrypt.c147
-rw-r--r--agent/pksign.c572
-rw-r--r--agent/preset-passphrase.c272
-rw-r--r--agent/protect-tool.c837
-rw-r--r--agent/protect.c1761
-rw-r--r--agent/t-protect.c351
-rw-r--r--agent/trans.c41
-rw-r--r--agent/trustlist.c852
27 files changed, 30079 insertions, 0 deletions
diff --git a/agent/ChangeLog-2011 b/agent/ChangeLog-2011
new file mode 100644
index 0000000..d32d69c
--- /dev/null
+++ b/agent/ChangeLog-2011
@@ -0,0 +1,3107 @@
+2011-12-01 Werner Koch <wk@g10code.com>
+
+ NB: ChangeLog files are no longer manually maintained. Starting
+ on December 1st, 2011 we put change information only in the GIT
+ commit log, and generate a top-level ChangeLog file from logs at
+ "make dist". See doc/HACKING for details.
+
+2011-11-28 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (card_key_available): Change wording of no key
+ diagnostic.
+ (ssh_handler_request_identities): Do not call card_key_available
+ if the scdaemon is disabled.
+
+2011-09-12 Ben Kibbey <bjk@luxsci.net>
+
+ * genkey.c (agent_ask_new_passphrase): Allow for an empty passphrase
+ (no protection) in PINENTRY_MODE_LOOPBACK.
+
+2011-09-10 Ben Kibbey <bjk@luxsci.net>
+
+ * agent.h (pinentry_loopback): New prototype.
+ * command.c (pinentry_loopback): New function to inquire a passphrase
+ from the client. For use with pinentry-mode=loopback.
+ * call-pinentry.c (agent_askpin): Handle PINENTRY_MODE_LOOPBACK.
+ * call-pinentry.c (agent_get_passphrase): Ditto.
+ * genkey.c (agent_ask_new_passphrase): Ditto.
+
+2011-08-10 Werner Koch <wk@g10code.com>
+
+ * genkey.c (check_passphrase_pattern): Use gpg_strerror instead of
+ strerror.
+ * command-ssh.c (ssh_receive_mpint_list): Remove unused var
+ ELEMS_PUBLIC_N.
+ * gpg-agent.c (main): Remove unused var MAY_COREDUMP.
+
+2011-08-09 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (option_handler): Have option s2k-count match the
+ documentation.
+
+2011-07-27 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (struct inq_needpin_s): Add field ANY_INQ_SEEN.
+ (inq_needpin): Set it.
+ (agent_card_scd): Send the cancel only if an inquire was actually
+ used.
+
+2011-07-09 Ben Kibbey <bjk@luxsci.net>
+
+ * call-scd.c (agent_card_scd): Send the CANCEL command back to SCD
+ when the SCD command is cancelled from the client.
+
+2011-07-22 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (ssh_receive_key): Do not init comment to an empty
+ static string; in the error case it would be freed.
+
+2011-07-20 Werner Koch <wk@g10code.com>
+
+ * command.c (do_one_keyinfo, cmd_keyinfo): Support option --ssh-fpr.
+
+ * command-ssh.c (ssh_identity_register): Display the ssh
+ fingerprint in the prompt.
+ (add_control_entry): Add arg FMTFPR and use it as comment in
+ sshcontrol.
+ (confirm_flag_from_sshcontrol): New.
+ (data_sign): Ask for confirmaton if requested.
+ (search_control_file): Add new arg R_CONFIRM and enhance parser.
+ * findkey.c (agent_raw_key_from_file): New.
+ (modify_description): Add format letter %F.
+ * findkey.c (agent_key_from_file): Simplify comment extraction by
+ using gcry_sexp_nth_string.
+
+2011-06-28 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (option_handler): Add option s2k-count.
+ * agent.h (server_control_s): Add member s2k_count.
+ * genkey.c (store_key): Add parameter s2k_count.
+ * protect.c (agent_protect): Add parameter s2k_count.
+ * protect.c (do_encryption): Add parameter s2k_count.
+
+2011-06-01 Marcus Brinkmann <mb@g10code.com>
+
+ * cvt-openpgp.c (convert_to_openpgp): Change type of N to unsigned
+ int.
+
+2011-04-26 Werner Koch <wk@g10code.com>
+
+ * cvt-openpgp.c (convert_to_openpgp): Use rfc4880 encoded S2K count.
+ * protect.c (get_standard_s2k_count_rfc4880): New.
+ (S2K_DECODE_COUNT): New.
+ (s2k_hash_passphrase): Use the new macro.
+
+2011-04-21 Werner Koch <wk@g10code.com>
+
+ * agent.h (server_control_s): Add field cache_ttl_opt_preset.
+ * gpg-agent.c (agent_init_default_ctrl): Init this field.
+ * genkey.c (agent_genkey): Use this new variable.
+ * command.c (cmd_passwd): Ditto.
+ (option_handler): Add new option cache-ttl-opt-preset.
+
+2011-04-20 Marcus Brinkmann <mb@g10code.com>
+
+ * command.c (cmd_import_key): Release key from failed import
+ before converting openpgp private key in the openpgp-private-key
+ case.
+
+2011-04-17 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (cmd_passwd): Check for an error before presetting.
+
+2011-04-12 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (cmd_passwd): Fixed --preset when not previously cached.
+
+2011-04-12 Werner Koch <wk@g10code.com>
+
+ * agent.h (CACHE_TTL_NONCE, CACHE_TTL_OPT_PRESET): New.
+ * command.c (cmd_passwd, cmd_import_key): Use new macros.
+ * genkey.c (agent_genkey): Ditto.
+
+2011-04-10 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (cmd_passwd): Add option --preset.
+ * command.c (cmd_genkey): Add option --preset.
+ * genkey.c (agent_genkey): Add parameter preset.
+
+2011-04-06 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (do_one_keyinfo): Add protection type field.
+
+2011-03-10 Werner Koch <wk@g10code.com>
+
+ * protect.c (hash_passphrase): Use the new gcry_kdf_derive.
+
+2011-03-08 Werner Koch <wk@g10code.com>
+
+ * cvt-openpgp.c (GCRY_PK_ECDH) [!HAVE_GCRY_PK_ECDH]: Remove.
+
+2011-03-03 Ben Kibbey <bjk@luxsci.net>
+
+ * command.c (cmd_preset_passphrase): Add option --inquire.
+
+2011-03-03 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Add option --allow-loopback-pinentry.
+ * command.c (option_handler): Add option pinentry-mode.
+ * agent.h (pinentry_mode_t): New enum.
+ (struct server_local_s): Add PINENTRY_MODE.
+ (struct opt): Add ALLOW_LOOPBACK_PINENTRY.
+ * call-pinentry.c (agent_askpin): Implement ask, cancel and error
+ pinentry modes.
+ (agent_get_passphrase, agent_get_confirmation): Ditto.
+ (agent_show_message): Return cancel if pinentry mode is not "ask".
+ (agent_popup_message_start): Ditto.
+
+2011-03-02 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (hash_algo_option): New.
+ (agent_card_pksign): Use it with PKSIGN.
+
+2011-03-02 Ben Kibbey <bjk@luxsci.net> (wk)
+
+ * command.c (cmd_clear_passphrase): Add option --mode=normal.
+ (cmd_keyinfo): Add option --data.
+ (do_one_keyinfo): Return CACHED status. Add arg DATA.
+
+2011-02-07 Werner Koch <wk@g10code.com>
+
+ * pksign.c (do_encode_dsa): Enforce multipe of 8 bits only for DSA.
+
+2011-02-03 Werner Koch <wk@g10code.com>
+
+ * protect.c (protect_info): Support ECC algos.
+
+ * pksign.c (do_encode_dsa): Map public key algo number. Extend
+ DSA size check for ECDSA.
+
+ * gpg-agent.c: Include cipher.h.
+ (map_pk_openpgp_to_gcry): New.
+
+ * findkey.c (key_parms_from_sexp): Support ECDH.
+
+ * cvt-openpgp.c (get_keygrip): Support ECC algorithms.
+ (convert_secret_key): Ditto.
+ (do_unprotect): Ditto.
+
+2011-02-02 Werner Koch <wk@g10code.com>
+
+ * cvt-openpgp.c (convert_secret_key): Remove algo mapping.
+
+2011-01-31 Werner Koch <wk@g10code.com>
+
+ * cvt-openpgp.c (convert_to_openpgp): Adjust to reverted Libgcrypt
+ ABI.
+
+ * protect.c (protect_info): Adjust ECDSA and ECDH parameter names.
+ Add "ecc".
+ * findkey.c (key_parms_from_sexp): Ditto.
+
+2011-01-19 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (read_one_trustfile): Also chop an CR.
+
+2011-01-21 Werner Koch <wk@g10code.com>
+
+ * pksign.c (do_encode_dsa): Compare MDLEN to bytes.
+
+ * cvt-openpgp.c (GCRY_PK_ECDH) [!HAVE_GCRY_PK_ECDH]: New.
+
+2010-12-02 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (CHECK_OWN_SOCKET_INTERVAL) [W32CE]: Set to 60
+ seconds.
+
+2010-11-29 Werner Koch <wk@g10code.com>
+
+ * cache.c (initialize_module_cache): Factor code out to ...
+ (init_encryption): new.
+ (new_data, agent_get_cache): Init encryption on on the fly.
+
+2010-11-26 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (CHECK_OWN_SOCKET_INTERVAL): New.
+ (handle_tick) [W32CE]: Don't check own socket.
+
+2010-11-23 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (gpg_agent_LDFLAGS): Add extra_bin_ldflags.
+
+2010-11-11 Werner Koch <wk@g10code.com>
+
+ * agent.h (opt): Add field SIGUSR2_ENABLED.
+ * gpg-agent.c (handle_connections): Set that flag.
+ * call-scd.c (start_scd): Enable events depending on this flag.
+
+2010-10-27 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (create_socket_name): Use TMPDIR. Change callers.
+
+2010-10-26 Werner Koch <wk@g10code.com>
+
+ * cache.c (agent_put_cache): Allow deletion even if TTL is passwd
+ as 0.
+
+ * genkey.c (agent_protect_and_store): Add arg PASSPHRASE_ADDR.
+ * command.c (cmd_passwd): Add option --passwd-nonce.
+ (struct server_local_s): Add LAST_CACHE_NONCE and LAST_PASSWD_NONCE.
+ (clear_nonce_cache): New.
+ (reset_notify): Clear the nonce cache.
+ (start_command_handler): Ditto.
+
+2010-10-25 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_export_key): Free CACHE_NONCE.
+ (cmd_passwd): Add option --cache-nonce.
+
+2010-10-18 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (start_pinentry): Print name of pinentry on
+ connect error.
+
+ * call-scd.c (agent_card_pksign): Make sure to return an unsigned
+ number.
+
+2010-10-14 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_genkey): Add option --no-protection.
+ * genkey.c (agent_genkey): Add arg NO_PROTECTION.
+
+2010-10-13 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_get_passphrase): Support the close_button.
+
+ * gpg-agent.c (create_server_socket): Switch back to stderr
+ logging if we are not starting a agent.
+
+ * command.c (cmd_passwd, cmd_export_key): Move mapping of
+ GPG_ERR_FULLY_CANCELED to ..
+ (leave_cmd): .. here.
+ (option_handler): Add option agent-awareness.
+ * protect-tool.c (get_passphrase): Take care of
+ GPG_ERR_FULLY_CANCELED.
+ * findkey.c (try_unprotect_cb): Ditto.
+ (unprotect): Remove the fully_canceled hack.
+ * call-pinentry.c (start_pinentry): Ditto.
+ (agent_askpin): Ditto.
+ * pkdecrypt.c (agent_pkdecrypt): Ditto
+ * pksign.c (agent_pksign_do): Ditto.
+ * genkey.c (agent_ask_new_passphrase): Remove arg CANCEL_ALL.
+
+2010-10-06 Werner Koch <wk@g10code.com>
+
+ * cvt-openpgp.c (convert_secret_key): Add missing break.
+
+2010-10-05 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Don't set SSH_AGENT_PID so that ssh-agent -k
+ won't kill out gpg-agent.
+
+2010-09-30 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (agent_exit): Run cleanup.
+ (cleanup): Run only once.
+
+ * call-pinentry.c (close_button_status_cb): New.
+ (agent_askpin): Add arg R_CANCEL_ALL. Change all callers.
+ * genkey.c (agent_ask_new_passphrase): Ditto.
+ * findkey.c (unprotect): Return GPG_ERR_FULLY_CANCELED if needed.
+
+ * command.c (cmd_export_key): Add support for OpenPGP keys.
+ * findkey.c (unprotect): Add optional arg R_PASSPHRASE.
+ (agent_key_from_file): Ditto. Change all callers.
+
+ * findkey.c (unprotect): Do not put the passphrase into the cache
+ if it has been changed.
+
+ * cvt-openpgp.c (convert_to_openpgp, apply_protection)
+ (key_from_sexp): New.
+
+2010-09-29 Werner Koch <wk@g10code.com>
+
+ * cvt-openpgp.c (convert_openpgp): Rename to convert_from_openpgp.
+
+ * command.c (has_option): Stop at "--".
+ (has_option_name, option_value): Ditto.
+ (skip_options): Skip initial spaces.
+
+2010-09-24 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main, reread_configuration): Always test whether
+ the default configuration file has been created in the meantime.
+ Fixes bug#1285.
+
+2010-09-17 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_havekey): Allow testing of several keygrips.
+
+2010-09-15 Werner Koch <wk@g10code.com>
+
+ * protect.c (calculate_mic): Take care of shared secret format.
+
+ * agent.h (PROTECTED_SHARED_SECRET): New.
+
+2010-09-02 Werner Koch <wk@g10code.com>
+
+ * cache.c (new_data): Change arg and callers to use a string and
+ explicity return an error code. We never used raw binary data and
+ thus it is easier to use a string. Adjust callers.
+ (initialize_module_cache, deinitialize_module_cache): New.
+ (new_data): Encrypt the cached data.
+ (struct cache_item_s): Remove field LOCKCOUNT. Change all users
+ accordingly.
+ (agent_unlock_cache_entry): Remove.
+ (agent_get_cache): Return an allocated string and remove CACHE_ID.
+ * genkey.c (agent_genkey): Remove cache marker stuff.
+ * findkey.c (unprotect): Ditto.
+ * cvt-openpgp.c (convert_openpgp): Ditto.
+ * command.c (cmd_get_passphrase): Ditto.
+ * gpg-agent.c (main, cleanup): Initialize and deinitialize the
+ cache module.
+
+2010-09-01 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (start_pinentry): Disable pinentry logging.
+
+ * command.c (cmd_import_key, cmd_genkey, cmd_pksign): Add CACHE
+ handling.
+ * cvt-openpgp.c (convert_openpgp): Add arg CACHE_NONCE and try the
+ cached nonce first.
+ * genkey.c (agent_genkey): Add arg CACHE_NONCE.
+ * cache.c (agent_get_cache): Require user and nonce cache modes
+ to match the requested mode.
+ (agent_put_cache): Ditto.
+ * agent.h (CACHE_MODE_NONCE): New.
+ * pksign.c (agent_pksign_do, agent_pksign): Add arg CACHE_NONCE.
+ * findkey.c (agent_key_from_file): Ditto.
+ (unprotect): Implement it.
+
+2010-08-31 Werner Koch <wk@g10code.com>
+
+ * pksign.c (do_encode_dsa): Fix sign problem.
+ * findkey.c (agent_is_dsa_key): Adjust to actual usage.
+
+2010-08-30 Werner Koch <wk@g10code.com>
+
+ * protect.c (s2k_hash_passphrase): New public function.
+
+2010-08-27 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_import_key): Support OpenPGP keys.
+ * cvt-openpgp.h, cvt-openpgp.c: New. Some of the code is based on
+ code taken from g10/seckey-cert.c.
+
+2010-08-26 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (open_control_file): Use estream to create the file.
+
+ * findkey.c (agent_write_private_key): Explicitly create file with
+ mode 600.
+ * gpg-agent.c (main): Ditto.
+ * trustlist.c (agent_marktrusted): Explicitly create file with
+ mode 640.
+
+2010-08-16 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Replace remaining printf by es_printf.
+
+2010-08-11 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_get_passphrase, agent_askpin): Fix
+ setting of confidential flag.
+
+ * call-scd.c (agent_card_scd): Pass assuan comment lines to the
+ caller.
+ (ASSUAN_CONVEY_COMMENTS): Provide replacement if needed.
+
+2010-08-09 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (t_common_ldadd): Add NETLIBS for sake of the TCP
+ logging.
+
+2010-06-24 Werner Koch <wk@g10code.com>
+
+ * genkey.c (check_passphrase_pattern): Use HANG option for
+ gnupg_wait_progress. Fixes regression from 2010-06-09.
+
+2010-06-21 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (export_p12_file, import_p12_cert_cb)
+ (import_p12_file, sexp_to_kparms, store_private_key): Remove
+ unused code.
+
+2010-06-18 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (store_private_key, rsa_key_check): Remove.
+
+ * command.c (cmd_export_key): New.
+
+2010-06-15 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_keywrap_key, cmd_import_key): New.
+
+ * genkey.c (agent_genkey, agent_protect_and_store): Factor common
+ code out to...
+ (agent_ask_new_passphrase): .. new.
+
+ * findkey.c (agent_write_private_key): Return GPG_ERR_EEXIST
+ instead of GPG_ERR_GENERAL.
+
+2010-06-14 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c: Remove commands --p12-import and --p12-export.
+ * minip12.c, minip12.h: Move to ../sm.
+ * Makefile.am (gpg_protect_tool_SOURCES): Remove them.
+ * preset-passphrase.c: Remove unneeded minip12.h.
+
+ * command.c (cmd_keywrap_key): New.
+
+ * command.c (leave_cmd): New.
+ (cmd_istrusted, cmd_listtrusted, cmd_marktrusted, cmd_pksign)
+ (cmd_pkdecrypt, cmd_genkey, cmd_readkey, cmd_keyinfo)
+ (cmd_get_passphrase, cmd_get_confirmation, cmd_learn)
+ (cmd_passwd, cmd_preset_passphrase, cmd_getval, cmd_putval): Use it.
+
+2010-05-12 Werner Koch <wk@g10code.com>
+
+ * preset-passphrase.c (forget_passphrase): Actually implement
+ this. Fixes bug#1198.
+
+2010-05-11 Werner Koch <wk@g10code.com>
+
+ * agent.h (opt): Add field USE_STANDARD_SOCKET.
+ * gpg-agent.c (use_standard_socket): Remove. Use new option instead.
+
+ * command.c (cmd_killagent, cmd_reloadagent): Provide command also
+ for non-W32 platforms.
+ (cmd_getinfo): New subcommands std_session_env and std_startup_env.
+
+2010-05-03 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (check_own_socket_thread): Do not release SOCKNAME
+ too early.
+
+2010-04-30 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Add command --use-standard-socket-p.
+
+2010-04-26 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (create_server_socket) [W32]: Also check for EEXIST.
+
+2010-04-19 Werner Koch <wk@g10code.com>
+
+ * pksign.c (get_dsa_qbits, do_encode_dsa): New.
+ (agent_pksign_do): Detect DSA keys and use do_encode_dsa.
+ * findkey.c (agent_public_key_from_file): Factor some code out to ..
+ (key_parms_from_sexp): New.
+ (agent_is_dsa_key): New.
+
+ * command.c (cmd_sethash): Clear digeest.RAW_VALUE.
+
+2010-04-14 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (libexec_PROGRAMS) [W32CE]: Do not build
+ gpg-preset-passphrase for now.
+ (pwquery_libs) [W32CE]: Set to empty.
+
+ * trustlist.c (read_one_trustfile): Use estream.
+
+2010-04-13 Werner Koch <wk@g10code.com>
+
+ * findkey.c (read_key_file): Use estream.
+ (agent_write_private_key): Ditto.
+
+2010-04-07 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (handle_connections) [W32]: Assume that PTh support
+ the handle event. Use a dummy event for W32CE.
+ (get_agent_scd_notify_event) [W32CE]: Do not build.
+
+ * call-pinentry.c: Remove setenv.h. Include sysutils.h.
+ (atfork_cb): s/setenv/gnupg_setenv/.
+
+ * gpg-agent.c: Do not include setenv.h.
+ (main): s/unsetenv/gnupg_unsetenv/.
+
+ * protect.c (calibrate_get_time) [W32CE]: Use GetThreadTimes.
+
+2010-04-06 Werner Koch <wk@g10code.com>
+
+ * call-scd.c [!HAVE_SIGNAL_H]: Do not include signal.h.
+
+ * findkey.c (agent_write_private_key): s/remove/gnupg_remove/.
+
+ * command-ssh.c (search_control_file): Replace rewind by fseek and
+ clearerr.
+ * genkey.c (check_passphrase_pattern): Ditto.
+
+ * gpg-agent.c [!HAVE_SIGNAL_H]: Do not include signal.h.
+ (remove_socket): s/remove/gnupg_remove/.
+ (create_private_keys_directory): Use gnupg_mkdir.
+
+2010-03-11 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Include "asshelp.h".
+ (main): Remove assuan_set_assuan_log_prefix. Add
+ assuan_set_log_cb.
+ (handle_signal): Disable pth ctrl dumping.
+ (parse_rereadable_options, main): Remove assuan_set_assuan_log_stream.
+ * call-scd.c (start_scd): Remove assuan_set_log_stream.
+
+2010-03-10 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (common_libs): Remove libjnlib.a.
+
+ * trustlist.c, protect-tool.c, command-ssh.c: Remove estream.h.
+
+2010-02-17 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (start_pinentry): Always free OPTSTR. Send
+ default-xxx strings.
+
+2010-01-26 Werner Koch <wk@g10code.com>
+
+ * protect.c (do_encryption): Encode the s2kcount and no not use a
+ static value of 96.
+
+2009-12-21 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_getinfo): Add sub-command s2k_count.
+
+2009-12-14 Werner Koch <wk@g10code.com>
+
+ * protect.c (agent_unprotect): Decode the S2K count here and take
+ care of the new unencoded values. Add a lower limit sanity check.
+ (hash_passphrase): Do not decode here.
+ (get_standard_s2k_count, calibrate_s2k_count): New.
+ (calibrate_get_time, calibrate_elapsed_time): New.
+ (do_encryption): Use get_standard_s2k_count.
+
+2009-12-08 Werner Koch <wk@g10code.com>
+
+ * protect.c (agent_unprotect): Avoid compiler warning.
+
+2009-12-08 Marcus Brinkmann <marcus@g10code.de>
+
+ * call-pinentry.c (start_pinentry): Convert posix fd to assuan fd.
+ * call-scd.c (start_scd): Likewise.
+
+2009-12-03 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (set_debug): Allow for numerical debug leveles. Print
+ active debug flags.
+
+2009-12-02 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (read_trustfiles): Store the pointer returned from
+ shrinking the memory and not the orginal one. Fixes bug#1163.
+ Reported by TAKAHASHI Tamotsu. Also return correct error after
+ memory failure.
+
+2009-11-27 Marcus Brinkmann <marcus@g10code.de>
+
+ * command.c (start_command_handler): Do not call
+ assuan_set_log_stream anymore.
+ * gpg-agent.c (main): But call assuan_set_assuan_log_stream here.
+
+2009-11-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * command.c (start_command_handler): Use assuan_fd_t and
+ assuan_fdopen on fds.
+
+2009-11-05 Marcus Brinkmann <marcus@g10code.de>
+
+ * call-pinentry.c (start_pinentry): Call assuan_pipe_connect, not
+ assuan_pipe_connect_ext.
+ * command.c (start_command_handler): Change
+ assuan_init_socket_server_ext into assuan_init_socket_server.
+ * call-scd.c (start_scd): Update use of assuan_socket_connect and
+ assuan_pipe_connect.
+ * gpg-agent.c (check_own_socket_thread, check_for_running_agent):
+ Update use of assuan_socket_connect.
+
+2009-11-04 Werner Koch <wk@g10code.com>
+
+ * command.c (register_commands): Add help arg to
+ assuan_register_command. Convert all command comments to help
+ strings.
+
+2009-11-02 Marcus Brinkmann <marcus@g10code.de>
+
+ * command.c (reset_notify): Take LINE arg and return error.
+ (register_commands): Use assuan_handler_t type.
+
+2009-10-16 Marcus Brinkmann <marcus@g10code.com>
+
+ * gpg_agent_CFLAGS, gpg_agent_LDADD: Use libassuan instead of
+ libassuan-pth.
+ * gpg-agent.c: Invoke ASSUAN_SYSTEM_PTH_IMPL.
+ (main): Call assuan_set_system_hooks and assuan_sock_init.
+ Fix invocation of assuan_socket_connect.
+
+2009-09-23 Werner Koch <wk@g10code.com>
+
+ * command.c (register_commands) [HAVE_ASSUAN_SET_IO_MONITOR]:
+ Remove cpp condition.
+ (start_command_handler) [HAVE_ASSUAN_SET_IO_MONITOR]: Ditto.
+
+2009-09-23 Marcus Brinkmann <marcus@g10code.de>
+
+ * gpg-agent.c (parse_rereadable_options): Don't set global assuan
+ log file (there ain't one anymore).
+ (main): Update to new API.
+ (check_own_socket_pid_cb): Return gpg_error_t instead of int.
+ (check_own_socket_thread, check_for_running_agent): Create assuan
+ context before connecting to server.
+ * command.c: Include "scdaemon.h" before <assuan.h> because of
+ GPG_ERR_SOURCE_DEFAULT check.
+ (write_and_clear_outbuf): Use gpg_error_t instead of
+ assuan_error_t.
+ (cmd_geteventcounter, cmd_istrusted, cmd_listtrusted)
+ (cmd_marktrusted, cmd_havekey, cmd_sigkey, cmd_setkeydesc)
+ (cmd_sethash, cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_readkey)
+ (cmd_keyinfo, cmd_get_passphrase, cmd_clear_passphrase)
+ (cmd_get_confirmation, cmd_learn, cmd_passwd)
+ (cmd_preset_passphrase, cmd_scd, cmd_getval, cmd_putval)
+ (cmd_updatestartuptty, cmd_killagent, cmd_reloadagent)
+ (cmd_getinfo, option_handler): Return gpg_error_t instead of int.
+ (post_cmd_notify): Change type of ERR to gpg_error_t from int.
+ (io_monitor): Add hook argument. Use symbols for constants.
+ (register_commands): Change return type of HANDLER to gpg_error_t.
+ (start_command_handler): Allocate assuan context before starting
+ server.
+ * call-pinentry.c: Include "scdaemon.h" before <assuan.h> because
+ of GPG_ERR_SOURCE_DEFAULT check.
+ (unlock_pinentry): Call assuan_release instead of
+ assuan_disconnect.
+ (getinfo_pid_cb, getpin_cb): Return gpg_error_t instead of int.
+ (start_pinentry): Allocate assuan context before connecting to
+ server.
+ * call-scd.c (membuf_data_cb, learn_status_cb, get_serialno_cb)
+ (membuf_data_cb, inq_needpin, card_getattr_cb, pass_status_thru)
+ (pass_data_thru): Change return type to gpg_error_t.
+ (start_scd): Allocate assuan context before connecting to server.
+
+2009-09-04 Marcus Brinkmann <marcus@g10code.com>
+
+ * command.c (start_command_handler): Add comment about gap in
+ implementation (in dead code), for future reference.
+
+2009-08-11 Werner Koch <wk@g10code.com>
+
+ * divert-scd.c (ask_for_card): I18n a prompt string.
+
+2009-07-06 Werner Koch <wk@g10code.com>
+
+ * agent.h: Include session-env.h.
+ (opt): Replace most of the startup_xxx fields by a session_env_t.
+ (struct server_control_s): Likewise.
+ * gpg-agent.c (main): Rewrite setting of the startup fields.
+ (handle_connections, main): Allocate SESSION_ENV.
+ (agent_init_default_ctrl, agent_deinit_default_ctrl): Change
+ accordingly.
+ * command.c (option_handler): Ditto.
+ (cmd_updatestartuptty): Change accordingly. Protect old values
+ from out of core failures.
+ * command-ssh.c (start_command_handler_ssh): Ditto.
+ (start_command_handler_ssh): Replace strdup by xtrystrdup.
+ * call-pinentry.c (atfork_cb): Pass new envrinmnet variables.
+ (start_pinentry): Use session_env stuff.
+ * protect-tool.c (main): Adjust call to gnupg_prepare_get_passphrase.
+
+2009-06-24 Werner Koch <wk@g10code.com>
+
+ * genkey.c (agent_protect_and_store): Return RC and not 0.
+ * protect.c (do_encryption): Fix ignored error code from malloc.
+ Reported by Fabian Keil.
+
+2009-06-17 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_get_confirmation): Add arg WITH_CANCEL.
+ Change all callers.
+ * trustlist.c (agent_marktrusted): Use WITH_CANCEL
+
+2009-06-09 Werner Koch <wk@g10code.com>
+
+ * learncard.c (send_cert_back): Ignore certain error codes.
+
+2009-06-05 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (store_private_key): Fix last change by appending
+ a ".key".
+
+2009-06-03 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c: Include estream.h.
+ (store_private_key): Replace stdio streams by estream functions
+ for a portable use of the "x" mode.
+ * trustlist.c: Include estream.h.
+ (agent_marktrusted): Replace stdio stream by estream functions.
+
+ * protect-tool.c (store_private_key): Use bin2hex.
+
+2009-06-02 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Run pth_kill after fork. Fixes bug#1066.
+
+2009-05-19 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (JNLIB_NEED_AFLOCAL): Define.
+ (create_server_socket): Use SUN_LEN macro.
+
+2009-05-15 Werner Koch <wk@g10code.com>
+
+ Fix bug #1053.
+
+ * agent.h (lookup_ttl_t): New.
+ * findkey.c (unprotect): Add arg LOOKUP_TTL.
+ (agent_key_from_file): Ditto.
+ * pksign.c (agent_pksign_do): Ditto.
+ * command-ssh.c (ttl_from_sshcontrol): New.
+ (data_sign): Pass new function to agent_pksign_do.
+ (search_control_file): Add new arg R_TTL.
+
+2009-05-14 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_get_passphrase): Add option --qualitybar.
+ * call-pinentry.c (agent_askpin): Factor some code out to ...
+ (setup_qualitybar): .. new.
+ (agent_get_passphrase): Add arg WITH_QUALITYBAR and implement it.
+
+2009-04-14 Marcus Brinkmann <marcus@g10code.de>
+
+ * call-pinentry.c (agent_get_confirmation): Try SETNOTOK command
+ with pinentry.
+
+2009-04-01 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (pe_opt): New.
+ (opts): Add option --agent-program. Use ARGPARSE macros.
+ (get_new_passphrase): Remove.
+ (get_passphrase): Use gpg-agent directly. Remove arg OPT_CHECK and
+ change all callers.
+ * Makefile.am (gpg_protect_tool_LDADD): Replace pwquery_libs by
+ LIBASSUAN_LIBS.
+ (gpg_protect_tool_CFLAGS): New.
+
+ * command.c (percent_plus_unescape): Remove.
+ (cmd_putval): Use percent_plus_unescape_inplace.
+ * call-scd.c (unescape_status_string): Remove.
+ (card_getattr_cb): Use percent_plus_unescape.
+ * protect-tool.c (main): Use percent_plus_unescape from common/.
+ (percent_plus_unescape, percent_plus_unescape_string): Remove.
+
+2009-03-27 Werner Koch <wk@g10code.com>
+
+ * learncard.c (agent_handle_learn): Add new certtype 111.
+
+2009-03-26 Werner Koch <wk@g10code.com>
+
+ * agent.h (MAX_DIGEST_LEN): Change to 64.
+ * command.c (cmd_sethash): Allow digest length of 48 and 64.
+ (cmd_sethash): Allow more hash algos.
+
+ * trustlist.c (reformat_name): New.
+ (agent_marktrusted): Use a reformatted name. Reload the table
+ before the update and always reload it at the end.
+ (agent_istrusted): Check early for the disabled flag.
+
+2009-03-25 Werner Koch <wk@g10code.com>
+
+ * pkdecrypt.c (agent_pkdecrypt): Return a specific error message
+ if the key is not available.
+
+ * gpg-agent.c (main): Print a started message to show the real pid.
+
+2009-03-20 Werner Koch <wk@g10code.com>
+
+ * learncard.c (struct kpinfo_cp_parm_s): Add field CTRL.
+ (struct certinfo_cb_parm_s): Ditto.
+ (agent_handle_learn): Set CTRL field.
+ (kpinfo_cb, certinfo_cb): Send progress status.
+
+ * agent.h (agent_write_status): Flag with GNUPG_GCC_A_SENTINEL.
+
+2009-03-19 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (struct trustitem_s): Add field DISABLED.
+ (read_one_trustfile): Parse the '!' flag.
+ (agent_istrusted, agent_listtrusted): Check flag.
+ (agent_istrusted): Add arg R_DISABLED. Change all callers.
+ (agent_marktrusted): Do not ask if flagged as disabled. Reverse
+ the order of the questions. Store the disabled flag.
+
+ * gpg-agent.c (main): Save signal mask and open fds. Restore mask
+ and close all fds prior to the exec. Fixes bug#1013.
+
+2009-03-17 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_get_passphrase): Break repeat loop on error.
+ Show error message.
+ (cmd_getinfo): Add subcommand "cmd_has_option".
+ (command_has_option): New.
+
+2009-03-17 Daiki Ueno <ueno@unixuser.org>
+
+ * command.c (option_value): New function.
+ (cmd_get_passphrase): Accept new option --repeat, which makes
+ gpg-agent to ask passphrase several times.
+
+2009-03-06 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_keyinfo): New command.
+ (register_commands): Register it.
+ (agent_write_status): Make sure not to print LR or CR.
+ * divert-scd.c (ask_for_card): Factor shadow info parsing out to ...
+ * protect.c (parse_shadow_info): New.
+ * findkey.c (agent_key_from_file): Use make_canon_sexp.
+ (agent_write_private_key, unprotect, read_key_file)
+ (agent_key_available): Use bin2hex.
+ (agent_key_info_from_file): New.
+ (read_key_file): Log no error message for ENOENT.
+
+2009-03-05 Werner Koch <wk@g10code.com>
+
+ * divert-scd.c (getpin_cb): Support flag 'P'. Change max_digits
+ from 8 to 16. Append a message about keypads.
+ * findkey.c (unprotect): Change max digits to 16.
+
+2009-03-02 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_getinfo): Add subcommand "scd_running".
+
+ * call-scd.c (agent_scd_check_running): New.
+
+ * gpg-agent.c: Add missing option strings for "--batch" and
+ "--homedir". Reported by Petr Uzel.
+
+ * protect-tool.c (import_p12_file): Take care of canceled
+ passphrase entry. Fixes bug#1003.
+ (export_p12_file): Ditto.
+
+2008-12-17 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (handle_connections): Set action of all pth event
+ handled signals to SIG_IGN. Use a different pth_sigmask strategy.
+
+2008-12-10 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_get_passphrase): Implement option --no-ask.
+
+2008-12-09 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Call i18n_init before init_common_subsystems.
+ * preset-passphrase.c (main): Ditto.
+ * protect-tool.c (main): Ditto.
+
+ * command.c (cmd_preset_passphrase): Allow an arbitrary string for
+ the cache id.
+
+2008-12-08 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (handle_connections): Sync the ticker to the next
+ full second. This is bug#871.
+
+2008-12-05 Werner Koch <wk@g10code.com>
+
+ * minip12.c (decrypt_block): Fix const modified of CHARSETS.
+ * learncard.c (sinfo_cb_parm_s): Remove superflous semicolon.
+ Reported by Stoyan Angelov.
+
+2008-11-18 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (make_libversion): New.
+ (my_strusage): Print libgcrypt version
+
+2008-11-11 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (membuf_data_cb): Change return type to
+ assuan_error_t to avoid warnings with newer libassuan versions.
+
+2008-11-04 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_killagent): Stop the agent immediately.
+ (start_command_handler): Take care of GPG_ERR_EOF.
+
+2008-10-29 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Move USE_STANDARD_SOCKET to the outer scope.
+ (create_socket_name): Remove arg USE_STANDARD_SOCKET. Change all
+ callers.
+ (create_server_socket): Remove IS_STANDARD_NAME and replace it by
+ USE_STANDARD_SOCKET. Change all callers.
+ (check_own_socket_running): New.
+ (check_own_socket, check_own_socket_thread): New.
+ (handle_tick): Check server socket once a minute.
+ (handle_connections): Remove the extra pth_wait in the shutdown
+ case.
+
+2008-10-20 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_geteventcounter): Mark unused arg.
+ (cmd_listtrusted, cmd_pksign, cmd_pkdecrypt, cmd_genkey): Ditto.
+ (cmd_updatestartuptty, post_cmd_notify): Ditto.
+ * command-ssh.c (add_control_entry)
+ (ssh_handler_request_identities, ssh_handler_remove_identity)
+ (ssh_handler_remove_all_identities, ssh_handler_lock)
+ (ssh_handler_unlock): Ditto.
+ * call-pinentry.c (pinentry_active_p, popup_message_thread)
+ (agent_popup_message_stop): Ditto.
+ * findkey.c (agent_public_key_from_file): Ditto.
+ * genkey.c (check_passphrase_pattern): Ditto.
+ * call-scd.c (atfork_cb): Ditto.
+ * protect-tool.c (import_p12_cert_cb): Ditto.
+ * t-protect.c (main): Ditto.
+
+2008-10-17 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (start_scd) [W32]: Use snprintf again because we now
+ always use the estream variant.
+
+2008-10-15 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (start_scd): Enable assuan loggging if requested.
+ (agent_scd_check_aliveness) [W32]: Fix use of GetExitCodeProcess.
+
+2008-10-14 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (get_agent_scd_notify_event): Need to use a manual
+ reset event.
+
+2008-09-29 Werner Koch <wk@g10code.com>
+
+ * agent.h (GCRY_MD_USER): Rename to GCRY_MODULE_ID_USER.
+ (GCRY_MD_USER_TLS_MD5SHA1): Rename to MD_USER_TLS_MD5SHA1 and
+ change all users.
+
+2008-09-25 Werner Koch <wk@g10code.com>
+
+ * divert-scd.c (getpin_cb): Support a Reset Code style PINs..
+
+2008-09-03 Werner Koch <wk@g10code.com>
+
+ * command.c (parse_keygrip): Use hex2bin.
+ (cmd_preset_passphrase): Decode the passphrase. Reported by Kiss
+ Gabor. Fixes #679 again.
+ * preset-passphrase.c (make_hexstring): Remove.
+ (preset_passphrase): Use bin2hex.
+
+2008-05-27 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (insert_colons): Fix stupidly wrong allocation size
+ computation.
+
+2008-05-26 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Re-initialize default assuan log stream if a
+ log file is used.
+
+ * trustlist.c (agent_marktrusted): Use xtryasprintf and xfree.
+
+ * gpg-agent.c (main, agent_deinit_default_ctrl): Always use xfree
+ because our asprintf is mapped to an xmalloc style function in
+ util.h. Replace xstrdup by xtrystrdup.
+ * w32main.c (build_argv): Ditto.
+ * preset-passphrase.c (preset_passphrase): Ditto.
+ * divert-scd.c (ask_for_card): Ditto.
+ * command.c (option_handler): Ditto.
+ * command-ssh.c (ssh_handler_request_identities): Ditto.
+ * call-pinentry.c (start_pinentry): Ditto.
+
+ * gpg-agent.c (start_connection_thread)
+ (start_connection_thread_ssh): Use pth_thread_id for useful output
+ under W32.
+ (pth_thread_id) [!PTH_HAVE_PTH_THREAD_ID]: New.
+
+2008-03-17 Werner Koch <wk@g10code.com>
+
+ * agent.h (agent_inq_pinentry_launched): New prototype.
+
+ * call-pinentry.c: Include sys/types.h and signal.h.
+
+2008-02-14 Werner Koch <wk@g10code.com>
+
+ * command.c (agent_inq_pinentry_launched): New.
+ (option_handler): Add option allow-pinentry-notify.
+ * call-pinentry.c (getinfo_pid_cb): New.
+ (start_pinentry): Ask for the PID and notify the client.
+
+2008-01-15 Marcus Brinkmann <marcus@g10code.de>
+
+ * call-pinentry.c (start_pinentry): Start pinentry in detached
+ mode.
+
+2007-12-04 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_askpin): Use gnupg_get_help_string.
+
+2007-12-03 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): s/standard_socket/use_standard_socket/ for
+ clarity.
+ (create_server_socket): New arg IS_SSH to avoid testing with
+ assuan commands.
+
+2007-11-20 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (get_agent_scd_notify_event): New.
+ (handle_signal): Factor SIGUSR2 code out to:
+ (agent_sigusr2_action): .. New.
+ (agent_sighup_action): Print info message here and not in
+ handle_signal.
+ (handle_connections) [PTH_EVENT_HANDLE]: Call agent_sigusr2_action.
+
+ * call-scd.c (agent_scd_check_aliveness) [W32]: Implemented.
+ (start_scd) [W32]: Send event-signal option.
+
+2007-11-19 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_askpin): Set the tooltip for the quality
+ bar.
+
+2007-11-15 Werner Koch <wk@g10code.com>
+
+ * agent.h (struct server_control_s): Add XAUTHORITY and
+ PINENTRY_USER_DATA.
+ * gpg-agent.c: New option --xauthority.
+ (main, agent_init_default_ctrl)
+ (agent_deinit_default_ctrl): Implemented
+ * command.c (cmd_updatestartuptty): Ditto.
+ * command-ssh.c (start_command_handler_ssh): Ditto.
+ * call-pinentry.c (atfork_cb): Set the environment.
+ (start_pinentry): Pass CTRL as arg to atfork_cb.
+
+2007-11-14 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (start_scd) [W32]: Take care of fflush peculiarities.
+
+2007-11-07 Werner Koch <wk@g10code.com>
+
+ * agent.h: Remove errors.h.
+
+2007-10-24 Werner Koch <wk@g10code.com>
+
+ * genkey.c (check_passphrase_constraints): Changed the wording of
+ the warning messages.
+
+2007-10-19 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (get_passphrase): Use new utf8 switch fucntions.
+
+2007-10-15 Daiki Ueno <ueno@unixuser.org> (wk)
+
+ * command-ssh.c (reenter_compare_cb): New function; imported from
+ genkey.c.
+ (ssh_identity_register): Ask initial passphrase twice.
+
+2007-10-02 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_getinfo): Add "pid" subcommand.
+
+2007-10-01 Werner Koch <wk@g10code.com>
+
+ * agent.h (struct server_control_s): Remove unused CONNECTION_FD.
+
+ * gpg-agent.c: Remove w32-afunix.h. Include mkdtemp.h.
+ (socket_nonce, socket_nonce_ssh): New.
+ (create_server_socket): Use assuan socket wrappers. Remove W32
+ specific stuff. Save the server nonce.
+ (check_nonce): New.
+ (start_connection_thread, start_connection_thread_ssh): Call it.
+ (handle_connections): Change args to gnupg_fd_t.
+ * command.c (start_command_handler): Change LISTEN_FD to gnupg_fd_t.
+ * command-ssh.c (start_command_handler_ssh): Ditto.
+
+2007-09-18 Werner Koch <wk@g10code.com>
+
+ * agent.h (struct pin_entry_info_s): Add element WITH_QUALITYBAR.
+ * genkey.c (check_passphrase_constraints): New arg SILENT.
+ Changed all callers.
+ (agent_protect_and_store, agent_genkey): Enable qualitybar.
+ * call-pinentry.c (agent_askpin): Send that option.
+ (unescape_passphrase_string): New.
+ (inq_quality): New.
+ (estimate_passphrase_quality): New.
+
+2007-09-14 Marcus Brinkmann <marcus@g10code.de>
+
+ * call-pinentry.c (agent_popup_message_stop): Implement kill for
+ Windows.
+
+2007-08-28 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Add option --faked-system-time.
+
+ * protect-tool.c (read_and_unprotect): Print the protected-at date.
+
+ * agent.h (struct server_control_s): Add member IN_PASSWD.
+ * command.c (cmd_passwd): Set it.
+ * findkey.c (try_unprotect_cb): Use it.
+
+ * protect.c (do_encryption): Replace asprintf by xtryasprint.
+ (agent_protect): Create the protected-at item.
+ (agent_unprotect): Add optional arg PROTECTED_AT.
+ (merge_lists): Add args CUTOFF and CUTLEN.
+ (agent_unprotect): Use them.
+ * findkey.c (try_unprotect_cb): Add code to test for expired keys.
+ (unprotect): Allow changing the passphrase.
+
+2007-08-27 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Add options --min-passphrase-nonalpha,
+ --check-passphrase-pattern and --enforce-passphrase-constraints.
+ (MIN_PASSPHRASE_NONALPHA): Init nonalpha option to 1.
+ (main): Declare options for gpgconf.
+ * agent.h (struct): Add members MIN_PASSPHRASE_NONALPHA,
+ ENFORCE_PASSPHRASE_CONSTRAINTS and CHECK_PASSPHRASE_PATTERN.
+ * genkey.c (nonalpha_charcount): New.
+ (check_passphrase_pattern): New.
+ (check_passphrase_constraints): Implement. Factor some code out...
+ (take_this_one_anyway, take_this_one_anyway2): .. New.
+
+ * call-pinentry.c (agent_show_message): New.
+ (agent_askpin): We better reset the pin buffer before asking.
+
+ * trustlist.c (insert_colons): New.
+ (agent_marktrusted): Pretty print the fpr.
+
+2007-08-22 Werner Koch <wk@g10code.com>
+
+ * findkey.c (O_BINARY): Make sure it is defined.
+ (agent_write_private_key): Use O_BINARY
+
+ * protect-tool.c (import_p12_file): Add hack to allow importing of
+ gnupg 2.0.4 generated files.
+
+2007-08-06 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (read_one_trustfile): Add flag "cm".
+ (agent_istrusted): Ditto.
+
+2007-08-02 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Include gc-opt-flags.h and remove their definition
+ here.
+
+2007-07-13 Werner Koch <wk@g10code.com>
+
+ * genkey.c (check_passphrase_constraints): Require a confirmation
+ for an empty passphrase.
+ (agent_genkey, agent_protect_and_store): No need to repeat an
+ empty passphrase.
+
+2007-07-05 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (struct inq_needpin_s): New.
+ (inq_needpin): Pass unknown inquiries up.
+
+2007-07-04 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (TIMERTICK_INTERVAL): New.
+ (fixed_gcry_pth_init, main): Kludge to fix Pth initialization.
+
+2007-07-03 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (handle_connections): Do not use FD_SETSIZE for
+ select but compute the correct number.
+
+2007-07-02 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_reloadagent) [W32]: New.
+ (register_commands) [W32]: New command RELOADAGENT.
+
+ * Makefile.am (gpg_agent_SOURCES): Remove w32main.c and w32main.h.
+ (gpg_agent_res_ldflags): Remove icon file as we don't have a
+ proper icon yet.
+ * gpg-agent.c (main): do not include w32main.h. Remove all calls
+ to w32main.c.
+ (agent_sighup_action): New.
+ (handle_signal): Use it.
+
+2007-06-26 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (create_directories) [W32]: Made it work.
+
+2007-06-21 Werner Koch <wk@g10code.com>
+
+ * agent.h (ctrl_t): Remove. It is now declared in ../common/util.h.
+
+ * gpg-agent.c (check_for_running_agent): New arg SILENT. Changed
+ all callers.
+ (create_server_socket): If the standard socket is in use check
+ whether a agent is running and avoid starting another one.
+
+2007-06-18 Marcus Brinkmann <marcus@g10code.de>
+
+ * gpg-agent.c (main): Percent escape pathname in --gpgconf-list
+ output.
+
+2007-06-18 Werner Koch <wk@g10code.com>
+
+ * w32main.c (build_argv): New.
+ (WinMain): Use it.
+
+ * command.c (cmd_killagent) [W32]: New.
+ (cmd_getinfo): New.
+ * gpg-agent.c (get_agent_ssh_socket_name): New.
+ (no_force_standard_socket) New.
+ (create_server_socket): Use it.
+ * Makefile.am (gpg_agent_res_ldflags): Pass windows option to ld.
+
+2007-06-14 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (main): Setup default socket name for
+ simple-pwquery.
+ (MAP_SPWQ_ERROR_IMPL): New. Use map_spwq_error for spqw related
+ error codes.
+ * preset-passphrase.c (main): Setup default socket name for
+ simple-pwquery.
+ (map_spwq_error): Remove.
+ (MAP_SPWQ_ERROR_IMPL): New.
+
+ * call-pinentry.c (start_pinentry): Use gnupg_module_name.
+ * call-scd.c (start_scd): Ditto.
+
+2007-06-12 Werner Koch <wk@g10code.com>
+
+ * taskbar.c: New.
+
+ * trustlist.c (read_one_trustfile): Replace GNUPG_SYSCONFDIR by a
+ function call.
+ (read_trustfiles): Ditto.
+
+ * gpg-agent.c (main): Replace some calls by init_common_subsystems.
+ * preset-passphrase.c (main): Ditto.
+ * protect-tool.c (main): Ditto.
+
+2007-06-11 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (common_libs): Use libcommonstd macro.
+ (commonpth_libs): Use libcommonpth macro.
+
+ * protect-tool.c (main) [W32]: Call pth_init.
+
+ * preset-passphrase.c (main) [W32]: Replace the explicit Winsocket
+ init by a call to pth_init.
+
+ * trustlist.c (initialize_module_trustlist): New.
+ * gpg-agent.c (main): Call it.
+
+ * call-pinentry.c (initialize_module_query): Rename to
+ initialize_module_call_pinentry.
+
+ * minip12.c: Remove iconv.h. Add utf8conf.h. Changed all iconv
+ calss to use these jnlib wrappers.
+
+2007-06-06 Werner Koch <wk@g10code.com>
+
+ * minip12.c (enum): Rename CONTEXT to ASNCONTEXT as winnt.h
+ defines such a symbol to access the process context.
+
+ * call-pinentry.c (dump_mutex_state) [W32]: Handle the W32Pth case.
+ * call-scd.c (dump_mutex_state): Ditto.
+
+ * protect-tool.c (i18n_init): Remove.
+ * preset-passphrase.c (i18n_init): Remove.
+ * gpg-agent.c (i18n_init): Remove.
+
+2007-05-19 Marcus Brinkmann <marcus@g10code.de>
+
+ * protect-tool.c (get_passphrase): Free ORIG_CODESET on error.
+
+2007-05-14 Werner Koch <wk@g10code.com>
+
+ * protect.c (make_shadow_info): Replace sprintf by smklen.
+
+2007-04-20 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (my_gcry_logger, my_gcry_outofcore_handler): Removed.
+ (main): Call the setup_libgcrypt_logging helper.
+ * protect-tool.c (my_gcry_logger): Removed.
+ (main): Call the setup_libgcrypt_logging helper.
+
+2007-04-03 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (read_trustfiles): Take a missing trustlist as an
+ empty one.
+
+2007-03-20 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c: New option --p12-charset.
+ * minip12.c (p12_build): Implement it.
+
+2007-03-19 Werner Koch <wk@g10code.com>
+
+ * minip12.c: Include iconv.h.
+ (decrypt_block): New.
+ (parse_bag_encrypted_data, parse_bag_data): Use it here.
+ (bag_data_p, bag_decrypted_data_p): New helpers.
+
+2007-03-06 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main) <gpgconf>: Add entries for all ttl options.
+
+2007-02-20 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (start_pinentry): Fix for OS X to allow loading
+ of the bundle. Tested by Benjamin Donnachie.
+
+2007-02-14 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: New option --pinentry-touch-file.
+ (get_agent_socket_name): New.
+ * agent.h (opt): Add pinentry_touch_file.
+ * call-pinentry.c (start_pinentry): Send new option to the
+ pinentry.
+
+2007-01-31 Moritz Schulte <moritz@g10code.com> (wk)
+
+ * command-ssh.c (stream_read_string): Initialize LENGTH to zero.
+ (start_command_handler_ssh): Use es_fgetc/es_ungetc to check if
+ EOF has been reached before trying to process another request.
+
+2007-01-31 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (start_command_handler_ssh):
+
+ * Makefile.am (t_common_ldadd): Add LIBICONV.
+
+2007-01-25 Werner Koch <wk@g10code.com>
+
+ * genkey.c (check_passphrase_constraints): Get ngettext call right
+ and use UTF-8 aware strlen.
+
+ * protect-tool.c (get_passphrase): New arg OPT_CHECK.
+ (get_new_passphrase): Enable OPT_CHECK on the first call.
+ * command.c (cmd_get_passphrase): Implement option --check.
+
+2007-01-24 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (MIN_PASSPHRASE_LEN): New
+ (parse_rereadable_options): New option --min-passphrase-len.
+ * genkey.c (check_passphrase_constraints): New.
+ (agent_genkey, agent_protect_and_store): Call new function. Fix
+ memory leak.
+
+ * call-pinentry.c (agent_askpin): Allow translation of the displayed
+ error message.
+ (agent_popup_message_start): Remove arg CANCEL_BTN.
+ (popup_message_thread): Use --one-button option.
+
+ * command.c (cmd_passwd): Now that we don't distinguish between
+ assuan and regular error codes we can jump to the end on error.
+
+2006-12-07 David Shaw <dshaw@jabberwocky.com>
+
+ * Makefile.am: Link to iconv for jnlib dependency.
+
+2006-11-20 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_popup_message_stop): Use SIGKILL.
+ * call-scd.c (inq_needpin): Implement POPUPKEYPADPROMPT and
+ DISMISSKEYPADPROMPT.
+
+2006-11-15 Werner Koch <wk@g10code.com>
+
+ * protect.c (make_shadow_info): Cast printf arg to unsigned int.
+ * minip12.c (parse_bag_encrypted_data): Ditto.
+ (parse_bag_data, p12_parse): Ditto.
+ * command-ssh.c (ssh_identity_register): Changed buffer_n to
+ size_t.
+
+ * agent.h (struct server_control_s): New field thread_startup.
+ * command.c (start_command_handler): Moved CTRL init code to ..
+ * gpg-agent.c (start_connection_thread): .. here.
+ (agent_deinit_default_ctrl): New.
+ (agent_init_default_ctrl): Made static.
+ (handle_connections): Allocate CTRL and pass it pth_spawn.
+ * command-ssh.c (start_command_handler_ssh): Moved CTRL init code
+ to ..
+ * gpg-agent.c (start_connection_thread_ssh): .. here.
+
+2006-11-14 Werner Koch <wk@g10code.com>
+
+ * command.c (bump_key_eventcounter): New.
+ (bump_card_eventcounter): New.
+ (cmd_geteventcounter): New command.
+ * gpg-agent.c (handle_signal): Call bump_card_eventcounter.
+ * findkey.c (agent_write_private_key): Call bump_key_eventcounter.
+ * trustlist.c (agent_reload_trustlist): Ditto.
+
+ * command.c (post_cmd_notify, io_monitor): New.
+ (register_commands, start_command_handler): Register them.
+
+2006-11-09 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): In detached mode connect standard
+ descriptors to /dev/null.
+
+ * trustlist.c (read_trustfiles): Make sure not to pass a zero size
+ to realloc as the C standards says that this behaves like free.
+
+2006-11-06 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (my_strusage): Fixed typo.
+
+2006-10-23 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): New command --gpgconf-test.
+
+ * minip12.c (parse_bag_encrypted_data, parse_bag_data): Allow for
+ a salt of 20 bytes.
+
+2006-10-20 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (t_common_ldadd): Use GPG_ERROR_LIBS instead -o just -l
+
+2006-10-19 Werner Koch <wk@g10code.com>
+
+ * findkey.c (unprotect): Use it to avoid unnecessary calls to
+ agent_askpin.
+ * call-pinentry.c (pinentry_active_p): New.
+
+2006-10-17 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (gpg_agent_LDADD): Link to libcommonpth.
+ (gpg_agent_CFLAGS): New. This allows to only link this with Pth.
+
+2006-10-16 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_get_confirmation): Map Cancel code here too.
+ * trustlist.c (agent_marktrusted): Return Cancel instead of
+ Not_Confirmed for the first question.
+
+2006-10-12 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (get_passphrase): Fix if !HAVE_LANGINFO_CODESET.
+
+2006-10-06 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (AM_CFLAGS): Use PTH version of libassuan.
+ (gpg_agent_LDADD): Ditto.
+
+ * divert-scd.c (divert_pksign): Use PKAUTH for the TLS algo.
+
+2006-10-05 Werner Koch <wk@g10code.com>
+
+ * command.c (has_option_name): New.
+ (cmd_sethash): New --hash option.
+ * pksign.c (do_encode_raw_pkcs1): New.
+ (agent_pksign_do): Use it here for the TLS algo.
+ * agent.h (GCRY_MD_USER_TLS_MD5SHA1): New.
+ * divert-scd.c (pksign): Add case for tls-md5sha1.
+
+ * divert-scd.c (encode_md_for_card): Check that the algo is valid.
+
+2006-10-04 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_get_passphrase): Changed to return the
+ unencoded passphrase.
+ (agent_askpin, agent_get_passphrase, agent_get_confirmation): Need
+ to map the cancel error.
+ * command.c (send_back_passphrase): New.
+ (cmd_get_passphrase): Use it here. Also implement --data option.
+ (skip_options): New.
+
+2006-09-26 Werner Koch <wk@g10code.com>
+
+ * learncard.c (agent_handle_learn): Send back the keypair
+ information.
+
+2006-09-25 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (read_one_trustfile): Allow extra flags.
+ (struct trustitem_s): Replaced KEYFLAGS by a FLAGS struct.
+ Changed all code to use this.
+ (agent_istrusted): New arg CTRL. Changed all callers. Send back
+ flags.
+ * command.c (agent_write_status): New.
+
+2006-09-20 Werner Koch <wk@g10code.com>
+
+ * Makefile.am: Changes to allow parallel make runs.
+
+2006-09-15 Werner Koch <wk@g10code.com>
+
+ * trustlist.c: Entirely rewritten.
+ (agent_trustlist_housekeeping): Removed and removed all calls.
+
+2006-09-14 Werner Koch <wk@g10code.com>
+
+ Replaced all call gpg_error_from_errno(errno) by
+ gpg_error_from_syserror().
+
+ * call-pinentry.c (start_pinentry): Replaced pipe_connect2 by
+ pipe_connect_ext.
+ * call-scd.c (start_scd): Ditto.
+ * command.c (start_command_handler): Replaced
+ init_connected_socket_server by init_socket_server_ext.
+
+2006-09-13 Werner Koch <wk@g10code.com>
+
+ * preset-passphrase.c (main) [W32]: Check for WSAStartup error.
+
+2006-09-08 Werner Koch <wk@g10code.com>
+
+ * call-scd.c: Add signal.h as we are referencing SIGUSR2.
+
+2006-09-06 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (AM_CFLAGS): Add $(GPG_ERR_CFLAGS).
+ (gpg_agent_LDADD): Replace -lgpg-error with $(GPG_ERROR_LIBS).
+
+2006-09-06 Werner Koch <wk@g10code.com>
+
+ * query.c: Renamed to ..
+ * call-pinentry.c: .. this.
+
+ * agent.h (out_of_core): Removed.
+ (CTRL): Removed and changed everywhere to ctrl_t.
+
+ Replaced all Assuan error codes by libgpg-error codes. Removed
+ all map_to_assuan_status and map_assuan_err.
+
+ * gpg-agent.c (main): Call assuan_set_assuan_err_source to have Assuan
+ switch to gpg-error codes.
+ * command.c (set_error): Adjusted.
+
+2006-09-04 Werner Koch <wk@g10code.com>
+
+ * command.c (percent_plus_unescape): New.
+ (cmd_get_val, cmd_putval): New.
+
+2006-08-29 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (stream_read_mpi): Sanity check for early
+ detecting of too large keys.
+ * gpg-agent.c (my_gcry_outofcore_handler): New.
+ (main): Register it.
+ (main): No allocate 32k secure memory (was 16k).
+
+2006-07-31 Werner Koch <wk@g10code.com>
+
+ * preset-passphrase.c (make_hexstring): For consistency use
+ xtrymalloc and changed caller to use xfree. Fixed function
+ comment.
+
+2006-07-29 Marcus Brinkmann <marcus@g10code.de>
+
+ * preset-passphrase.c (preset_passphrase): Do not strip off last
+ character of passphrase.
+ (make_hexstring): New function.
+ * command.c (cmd_preset_passphrase): Use parse_hexstring to syntax
+ check passphrase argument. Truncate passphrase at delimiter.
+
+2006-07-24 Werner Koch <wk@g10code.com>
+
+ * minip12.c (build_key_bag): New args SHA1HASH and
+ KEYIDSTR. Append bag Attributes if these args are given.
+ (build_cert_sequence): ditto.
+ (p12_build): Calculate certificate hash and pass to build
+ functions.
+
+2006-07-21 Werner Koch <wk@g10code.com>
+
+ * minip12.c (oid_pkcs_12_keyBag): New.
+ (parse_bag_encrypted_data): New arg R_RESULT. Support keybags and
+ return the key object.
+ (p12_parse): Take new arg into account. Free RESULT on error.
+
+2006-06-26 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (handle_signal): Print info for SIGUSR2 only in
+ verbose mode.
+
+2006-06-22 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (make_cstring): Use memcpy instead of strncpy.
+ (ssh_receive_mpint_list, sexp_key_extract, data_sign): Use
+ xtrycalloc instead of xtrymalloc followed by memset.
+
+2006-06-20 Werner Koch <wk@g10code.com>
+
+ * minip12.c (create_final): New arg PW. Add code to calculate the
+ MAC.
+
+2006-06-09 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (gpg_agent_LDADD): Add $(NETLIBS).
+ (gpg_protect_tool_LDADD): Likewise.
+ (gpg_preset_passphrase_LDADD): Likewise.
+
+2006-04-09 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_request_process): Removed FIXME mentioning a
+ possible DoS attack.
+
+2006-04-01 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_identity_register): Make KEY_GRIP_RAW be 20
+ instead of 21 bytes long; do not fill KEY_GRIP_RAW[20] with NUL
+ byte - KEY_GRIP_RAW is a raw binary string anyway.
+
+2006-02-09 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (struct scd_local_s): New field next_local.
+ (scd_local_list): New.
+ (start_scd): Put new local into list.
+ (agent_reset_scd): Remove it from the list.
+ (agent_scd_check_aliveness): Here is the actual reason why we need
+ all this stuff.
+ (agent_reset_scd): Send the new command RESTART instead of RESET.
+
+2005-12-16 Werner Koch <wk@g10code.com>
+
+ * minip12.c (cram_octet_string): New
+ (p12_parse): Use it for NDEFed bags.
+ (parse_bag_data): Ditto.
+ (string_to_key, set_key_iv, crypt_block): New arg SALTLEN.
+ (p12_build): Use old value 8 for new arg.
+ (parse_bag_encrypted_data, parse_bag_data): Allow for salts of 8
+ to 16 bytes. Add new arg R_CONSUMED.
+
+2005-11-24 Werner Koch <wk@g10code.com>
+
+ * minip12.c (p12_parse): Fixed for case that the key object comes
+ prior to the certificate.
+
+2005-10-19 Werner Koch <wk@g10code.com>
+
+ * divert-scd.c (getpin_cb): Hack to use it for a keypad message.
+
+ * call-scd.c (inq_needpin): Reworked to support the new KEYPADINFO.
+
+ * query.c (start_pinentry): Keep track of the owner.
+ (popup_message_thread, agent_popup_message_start)
+ (agent_popup_message_stop, agent_reset_query): New.
+ * command.c (start_command_handler): Make sure a popup window gets
+ closed.
+
+2005-10-08 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (gpg_protect_tool_LDADD): Add ../gl/libgnu.a.
+ (gpg_preset_passphrase_LDADD, t_common_ldadd): Likewise.
+ (gpg_agent_LDADD): Add ../gl/libgnu.a after ../common/libcommon.a.
+
+2005-09-16 Werner Koch <wk@g10code.com>
+
+ * minip12.c (build_key_sequence, build_cert_sequence): Fixed
+ padding.
+
+2005-09-15 Moritz Schulte <moritz@g10code.com>
+
+ * t-protect.c (test_agent_protect): Implemented.
+ (main): Disable use of secure memory.
+
+2005-09-09 Werner Koch <wk@g10code.com>
+
+ * minip12.c (p12_build): Oops, array needs to be larger for the
+ certificate.
+ (build_cert_bag): Fixed yesterdays change.
+
+ * command-ssh.c (card_key_available): Let the card handler decide
+ whether the card is supported here. Also get a short serial
+ number to return from the card handler.
+
+2005-09-08 Werner Koch <wk@g10code.com>
+
+ * minip12.c (build_cert_bag): Use a non constructed object.
+ i.e. 0x80 and not 0xa0.
+
+2005-08-16 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Use a default file name for --write-env-file.
+
+2005-07-25 Werner Koch <wk@g10code.com>
+
+ * findkey.c (agent_public_key_from_file): Fixed array assignment.
+ This was the cause for random segvs.
+
+2005-06-29 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (data_sign): Removed empty statement.
+
+2005-06-21 Werner Koch <wk@g10code.com>
+
+ * minip12.c (create_final): Cast size_t to ulong for printf.
+ (build_key_bag, build_cert_bag, build_cert_sequence): Ditto.
+
+2005-06-16 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c (make_advanced): Makde RESULT a plain char.
+ * call-scd.c (unescape_status_string): Need to cast unsigned char*
+ for strcpy.
+ (agent_card_pksign): Made arg R_BUF an unsigned char**.
+ * divert-scd.c (divert_pksign): Made SIGVAL unsigned char*.
+ (encode_md_for_card): Initialize R_VAL and R_LEN.
+ * genkey.c (store_key): Made BUF unsigned.
+ * protect.c (do_encryption): Ditto.
+ (do_encryption): Made arg PROTBEGIN unsigned. Initialize RESULT
+ and RESULTLEN even on error.
+ (merge_lists): Need to cast unsigned char * for strcpy. Initialize
+ RESULTand RESULTLEN even on error.
+ (agent_unprotect): Likewise for strtoul.
+ (make_shadow_info): Made P and INFO plain char.
+ (agent_shadow_key): Made P plain char.
+
+2005-06-15 Werner Koch <wk@g10code.com>
+
+ * query.c (agent_get_passphrase): Made HEXSTRING a char*.
+ * command-ssh.c (ssh_key_grip): Made arg BUFFER unsigned.
+ (ssh_key_grip): Simplified.
+ (data_sign): Initialize variables with the definition.
+ (ssh_convert_key_to_blob): Make sure that BLOB and BLOB_SIZE
+ are set to NULL on error. Cool, gcc-4 detects uninitialized stuff
+ beyond function boundaries; well it can't know that we do error
+ proper error handling so that this was not a real error.
+ (file_to_buffer): Likewise for BUFFER and BUFFER_N.
+ (data_sign): Likewise for SIG and SIG_N.
+ (stream_read_byte): Set B to a value even on error.
+ * command.c (cmd_genkey): Changed VALUE to char.
+ (cmd_readkey): Cast arg for gcry_sexp_sprint.
+ * agent.h (struct server_control_s): Made KEYGRIP unsigned.
+
+2005-06-13 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (start_command_handler_ssh): Reset the SCD.
+
+2005-06-09 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (create_socket_name): New option --max-cache-ttl-ssh.
+ * cache.c (housekeeping): Use it.
+ (agent_put_cache): Use a switch to get the default ttl so that it
+ is easier to add more cases.
+
+2005-06-06 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: New option --default-cache-ttl-ssh.
+ * agent.h (cache_mode_t): New.
+ * pksign.c (agent_pksign_do): New arg CACHE_MODE to replace the
+ ARG IGNORE_CACHE. Changed all callers.
+ (agent_pksign): Ditto.
+ * findkey.c (agent_key_from_file): Ditto. Canged all callers.
+ (unprotect): Ditto.
+ * command-ssh.c (data_sign): Use CACHE_MODE_SSH.
+ * cache.c (agent_get_cache): New arg CACHE_MODE.
+ (agent_put_cache): Ditto. Store it in the cache.
+
+ * query.c (agent_query_dump_state, dump_mutex_state): New.
+ (unlock_pinentry): Reset the global context before releasing the
+ mutex.
+ * gpg-agent.c (handle_signal): Dump query.c info on SIGUSR1.
+
+ * call-scd.c (agent_scd_check_aliveness): Always do a waitpid and
+ add a timeout to the locking.
+
+2005-06-03 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_updatestartuptty): New.
+
+ * gpg-agent.c: New option --write-env-file.
+
+ * gpg-agent.c (handle_connections): Make sure that the signals we
+ are handling are not blocked.Block signals while creating new
+ threads.
+
+2005-06-02 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (agent_scd_dump_state, dump_mutex_state): New.
+ * gpg-agent.c (handle_signal): Print it on SIGUSR1.
+ (handle_connections): Include the file descriptor into the
+ threadnames.
+
+2005-06-01 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Include setenv.h.
+
+2005-05-31 Werner Koch <wk@g10code.com>
+
+ * agent.h (out_of_core): s/__inline__/inine. Noted by Ray Link.
+
+2005-05-25 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Do not unset the DISPLAY when we are
+ continuing as child.
+
+2005-05-24 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (inq_needpin): Skip leading spaces in of PIN
+ description.
+ * divert-scd.c (getpin_cb): Enhanced to cope with description
+ flags.
+ * query.c (agent_askpin): Add arg PROMPT_TEXT. Changed all
+ callers.
+
+2005-05-21 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (start_scd): Don't test for an alive scdaemon here.
+ (agent_scd_check_aliveness): New.
+ * gpg-agent.c (handle_tick): Test for an alive scdaemon.
+ (handle_signal): Print thread info on SIGUSR1.
+
+2005-05-20 Werner Koch <wk@g10code.com>
+
+ * protect-tool.c: New option --canonical.
+ (show_file): Implement it.
+
+ * keyformat.txt: Define the created-at attribute for keys.
+
+2005-05-18 Werner Koch <wk@g10code.com>
+
+ * divert-scd.c (ask_for_card): Removed the card reset kludge.
+
+2005-05-17 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (unlock_scd): Add new arg CTRL. Changed all callers.
+ (start_scd): Reoworked to allow for additional connections.
+ * agent.h (ctrl_t): Add local data for the SCdaemon.
+ * command.c (start_command_handler): Release SERVER_LOCAL.
+
+ * gpg-agent.c (create_server_socket): Use xmalloc.
+ (main): Removed option --disable-pth a dummy. Removed non-pth
+ code path.
+ (cleanup_sh): Removed. Not needed anymore.
+
+2005-05-05 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_key_to_buffer): Rename to ...
+ (ssh_key_to_protected_buffer): ... this; change callers.
+ Improved documentation.
+ Use ssh_key_grip(), where gcry_pk_get_keygrip() has been used
+ before.
+ (ssh_handler_sign_request): Removed unusued variable P.
+
+2005-04-20 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_handler_request_identities): Removed
+ debugging code (sleep call), which was commited unintenionally.
+
+2005-04-20 Werner Koch <wk@g10code.com>
+
+ * minip12.c (parse_bag_encrypted_data): Fix the unpadding hack.
+
+ * gpg-agent.c: New option --disable-scdaemon.
+ (handle_connections): Add time event to drive ...
+ (handle_tick): New function.
+ (main): Record the parent PID. Fixed segv when using ssh and a
+ command.
+
+ * call-scd.c (start_scd): Take care of this option.
+
+2005-04-03 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_request_spec): New member: secret_input.
+ (REQUEST_SPEC_DEFINE): New argument: secret_input.
+ (request_specs): Add secret_input flag.
+ (request_spec_lookup): New function ...
+ (ssh_request_process): ... use it here; depending on secret_input
+ flag allocate secure or non-secure memory.
+
+2005-03-02 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (sexp_key_extract): Removed FIXME, since
+ xtrymallos does set errno correctly by now.
+ (sexp_extract_identifier): Remove const attribute from identifier.
+ (ssh_handler_request_identities): Remove const attribute from
+ key_type; removes ugly casts and FIXME.
+ (sexp_key_extract): Remove const attribute from comment.
+ (ssh_send_key_public): Remove const attribute from
+ key_type/comment; removes ugly cast.
+ (data_sign): Remove const attribute from identifier; removes ugly
+ cast.
+ (key_secret_to_public): Remove const attribute from comment;
+ removes ugly cast.
+ (ssh_handler_sign_request): Remove const attribute from p.
+ (sexp_key_extract): Use make_cstring().
+ (ssh_key_extract_comment): Likewise.
+ (ssh_key_to_buffer): Use secure memory for memory area to hold the
+ key S-Expression.
+ Added more comments.
+
+2005-02-25 Werner Koch <wk@g10code.com>
+
+ * findkey.c (modify_description): Keep invalid % escapes, so that
+ %0A may pass through.
+
+ * agent.h (server_control_s): New field USE_AUTH_CALL.
+ * call-scd.c (agent_card_pksign): Make use of it.
+ * command-ssh.c (data_sign): Set the flag.
+ (ssh_send_key_public): New arg OVERRIDE_COMMENT.
+ (card_key_available): Add new arg CARDSN.
+ (ssh_handler_request_identities): Use the card s/n as comment.
+ (sexp_key_extract): Use GCRYMPI_FMT_STD.
+ (data_sign): Ditto.
+
+ * learncard.c (make_shadow_info): Moved to ..
+ * protect.c (make_shadow_info): .. here. Return NULL on malloc
+ failure. Made global.
+ * agent.h: Add prototype.
+
+2005-02-24 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (unescape_status_string): New. Actual a copy of
+ ../g10/call-agent.c
+ (card_getattr_cb, agent_card_getattr): New.
+
+ * command-ssh.c (card_key_available): New.
+ (ssh_handler_request_identities): First see whether a card key is
+ available.
+
+ * gpg-agent.c (handle_connections): Need to check for events if
+ select returns with -1.
+
+2005-02-23 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (get_passphrase): Removed.
+ (ssh_identity_register): Partly rewritten.
+ (open_control_file, search_control_file, add_control_entry): New.
+ (ssh_handler_request_identities): Return only files listed in our
+ control file.
+
+ * findkey.c (unprotect): Check for allocation error.
+
+ * agent.h (opt): Add fields to record the startup terminal
+ settings.
+ * gpg-agent.c (main): Record them and do not force keep display
+ with --enable-ssh-support.
+ * command-ssh.c (start_command_handler_ssh): Use them here.
+
+ * gpg-agent.c: Renamed option --ssh-support to
+ --enable-ssh-support.
+
+ * command.c (cmd_readkey): New.
+ (register_commands): Register new command "READKEY".
+
+ * command-ssh.c (ssh_request_process): Improved logging.
+
+ * findkey.c (agent_write_private_key): Always use plain open.
+ Don't depend on an umask for permissions.
+ (agent_key_from_file): Factored file reading code out to ..
+ (read_key_file): .. new function.
+ (agent_public_key_from_file): New.
+
+2005-02-22 Werner Koch <wk@g10code.com>
+
+ * command-ssh.c (stream_read_string): Removed call to abort on
+ memory error because the CVS version of libgcrypt makes sure
+ that ERRNO gets always set on error even with a faulty user
+ supplied function.
+
+2005-02-19 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_receive_mpint_list): Slightly rewritten, do
+ not use elems_secret member of key_spec.
+ (ssh_key_type_spec): Removed member: elems_secret.
+ (ssh_key_types): Removed elems_secret data.
+ (ssh_sexp_construct): Renamed to ...
+ (sexp_key_construct): ... this; changed callers.
+ (ssh_sexp_extract): Renamed to ...
+ (sexp_key_extract): ... this; changed callers.
+ (ssh_sexp_extract_key_type): Renamed to ...
+ (sexp_extract_identifier): ... this; changed callers; use
+ make_cstring().
+ Added more comments.
+
+2005-02-18 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_sexp_construct): Rewritten generation of sexp
+ template, clarified.
+ (ssh_sexp_extract): Support shadowed-private-key-sexp; treat
+ protected-private key and shadowed-private-key as public keys.
+ (key_secret_to_public): Rewritten: simply use ssh_sexp_extract()
+ and ssh_sexp_construct().
+
+2005-02-15 Werner Koch <wk@g10code.com>
+
+ * findkey.c (modify_description): Don't increment OUT_LEN during
+ the second pass.
+
+2005-02-14 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (es_read_byte): Renamed to ...
+ (stream_es_read_byte): ... this; changed callers.
+ (es_write_byte): Renamed to ...
+ (stream_write_byte): ... this; changed callers.
+ (es_read_uint32): Renamed to ...
+ (stream_read_uint32): ... this; changed callers.
+ (es_write_uint32): Renamed to ...
+ (stream_write_uint32): ... this; changed callers.
+ (es_read_data): Renamed to ...
+ (stream_read_data): ... this; changed callers.
+ (es_write_data): Renamed to ...
+ (stream_write_data): ... this; changed callers.
+ (es_read_string): Renamed to ...
+ (stream_read_string): ... this; changed callers.
+ (es_read_cstring): Renamed to ...
+ (stream_read_cstring): ... this; changed callers.
+ (es_write_string): Renamed to ...
+ (stream_write_string): ... this; changed callers.
+ (es_write_cstring): Renamed to ...
+ (stream_write_cstring): ... this; changed callers.
+ (es_read_mpi): Renamed to ...
+ (stream_read_mpi): ... this; changed callers.
+ (es_write_mpi): Renamed to ...
+ (stream_write_mpi): ... this; changed callers.
+ (es_copy): Renamed to ...
+ (stream_copy): ... this; changed callers.
+ (es_read_file): Renamed to ...
+ (file_to_buffer): ... this; changed callers.
+ (ssh_identity_register): Removed variable description_length;
+ changed code to use asprintf for description.
+ (stream_write_uint32): Do not filter out the last byte of shift
+ expression.
+ (uint32_construct): New macro ...
+ (stream_read_uint32): ... use it; removed unnecessary cast.
+
+2005-02-03 Werner Koch <wk@g10code.com>
+
+ * agent.h (agent_exit): Add JNLIB_GCC_A_NR to indicate that this
+ function won't return.
+
+ * gpg-agent.c (check_for_running_agent): Initialize pid to a
+ default value if not needed.
+
+ * command-ssh.c: Removed stdint.h. s/byte_t/unsigned char/,
+ s/uint32/u32/ becuase that is what we have always used in GnuPG.
+ (ssh_request_specs): Moved to top of file.
+ (ssh_key_types): Ditto.
+ (make_cstring): Ditto.
+ (data_sign): Don't use a variable for the passphrase prompt, make
+ it translatable.
+ (ssh_request_process):
+
+
+ * findkey.c (modify_description): Renamed arguments for clarity,
+ polished documentation. Make comment a C-string. Fixed case of
+ DESCRIPTION being just "%".
+ (agent_key_from_file): Make sure comment string to a C-string.
+
+ * gpg-agent.c (create_socket_name): Cleanup the implemntation, use
+ DIMof, agent_exit, removed superflous args and return the
+ allocated string as value. Documented. Changed callers.
+ (create_server_socket): Cleanups similar to above. Changed callers.
+ (cleanup_do): Renamed to ..
+ (remove_socket): .. this. Changed caller.
+ (handle_connections): The signals are to be handled in the select
+ and not in the accept. Test all FDs after returning from a
+ select. Remove the event tests from the accept calls. The select
+ already assured that the accept won't block.
+
+2005-01-29 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_handler_request_identities)
+ (ssh_handler_sign_request, ssh_handler_add_identity)
+ (ssh_handler_remove_identity, ssh_handler_remove_all_identities)
+ (ssh_handler_lock, ssh_handler_unlock): Changed to return an error
+ code instead of a boolean.
+ (ssh_request_process): Changed to return a boolean instead of an
+ error; adjust caller.
+ (ssh_request_handle_t): Adjusted type.
+ (ssh_request_spec): New member: identifier.
+ (REQUEST_SPEC_DEFINE): New macro; use it for initialization of
+ request_specs[].
+ (ssh_request_process): In debugging mode, log identifier of
+ handler to execute.
+ (start_command_handler_ssh): Moved most of the stream handling
+ code ...
+ (ssh_request_process): ... here.
+
+2005-01-28 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_handler_add_identity): Pass ctrl to
+ ssh_identity_register().
+ (ssh_identity_register): New argument: ctrl; pass ctrl to
+ get_passphrase().
+ (get_passphrase): Pass ctrl instead of NULL to agent_askpin().
+ (start_command_handler_ssh): Use agent_init_default_ctrl();
+ deallocate structure members, which might be dynamically
+ allocated.
+ (lifetime_default): Removed variable.
+ (ssh_handler_add_identity): Fix ttl handling; renamed variable
+ `death' to `ttl'.
+ (ssh_identity_register): Fix key grip handling.
+
+2005-01-26 Moritz Schulte <moritz@g10code.com>
+
+ * command-ssh.c (ssh_handler_sign_request): Confirm to agent
+ protocol in case of failure.
+
+ * command-ssh.c: New file.
+
+ * Makefile.am (gpg_agent_SOURCES): New source file: command-ssh.c.
+
+ * findkey.c (modify_description): New function.
+ (agent_key_from_file): Support comment field in key s-expressions.
+
+ * gpg-agent.c (enum cmd_and_opt_values): New item: oSSHSupport.
+ (opts) New entry for oSSHSupport.
+ New variable: socket_name_ssh.
+ (cleanup_do): New function based on cleanup().
+ (cleanup): Use cleanup_do() for socket_name and socket_name_ssh.
+ (main): New switch case for oSSHSupport.
+ (main): Move socket name creation code to ...
+ (create_socket_name): ... this new function.
+ (main): Use create_socket_name() for creating socket names for
+ socket_name and for socket_name_ssh in case ssh support is
+ enabled.
+ Move socket creation code to ...
+ (create_server_socket): ... this new function.
+ (main): Use create_server_socket() for creating sockets.
+ In case standard_socket is set, do not only store a socket name in
+ socket_name, but also in socket_name_ssh.
+ Generate additional environment info strings for ssh support.
+ Pass additional ssh socket argument to handle_connections.
+ (start_connection_thread_ssh): New function.
+ (handle_connections): Use select to multiplex between gpg-agent
+ and ssh-agent protocol.
+
+ * agent.h (struct opt): New member: ssh_support.
+ (start_command_handler_ssh): Add prototype.
+
+2005-01-04 Werner Koch <wk@g10code.com>
+
+ * trustlist.c (agent_marktrusted): Use "Cancel" for the first
+ confirmation and made the strings translatable.
+
+ * cache.c (agent_put_cache): Fix the test for using the default
+ TTL.
+
+2004-12-21 Werner Koch <wk@g10code.com>
+
+ * preset-passphrase.c (preset_passphrase): Handle --passphrase.
+
+ * Makefile.am (gpg_preset_passphrase_LDADD): Reorder libs so that
+ pwquery may use stuff from jnlib. Conditionally add -lwsock2
+ (gpg_protect_tool_LDADD): Ditto.
+
+ * preset-passphrase.c (main): Use default_homedir().
+ (main) [W32]: Initialize sockets.
+
+2004-12-21 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (libexec_PROGRAMS): Add gpg-preset-passphrase.
+ (gpg_preset_passphrase_SOURCES, gpg_preset_passphrase_LDADD): New
+ targets.
+ * agent.h (opt): New member allow_cache_passphrase.
+ * cache.c (housekeeping): Check if R->ttl is not negative.
+ (agent_put_cache): Allow ttl to be negative.
+ * command.c (parse_hexstring): Allow something to follow the
+ hexstring.
+ (cmd_cache_passphrase): New function.
+ (register_commands): Add it.
+ * gpg-agent.c: Handle --allow-preset-passphrase.
+ * preset-passphrase.c: New file.
+
+2004-12-21 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Use default_homedir().
+ * protect-tool.c (main): Ditto.
+
+2004-12-20 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main) [W32]: Now that Mutexes work we can remove
+ the pth_init kludge.
+ (main): Add new options --[no-]use-standard-socket.
+ (check_for_running_agent): Check whether it is running on the
+ standard socket.
+
+ * call-scd.c (init_membuf, put_membuf, get_membuf): Removed. We
+ now use the identical implementation from ../common/membuf.c.
+
+ * pksign.c (agent_pksign): Changed arg OUTFP to OUTBUF and use
+ membuf functions to return the value.
+ * pkdecrypt.c (agent_pkdecrypt): Ditto.
+ * genkey.c (agent_genkey): Ditto.
+ * command.c (cmd_pksign, cmd_pkdecrypt, cmd_genkey): Replaced
+ assuan_get_data_fp() by a the membuf scheme.
+ (clear_outbuf, write_and_clear_outbuf): New.
+
+2004-12-19 Werner Koch <wk@g10code.com>
+
+ * query.c (initialize_module_query): New.
+ * call-scd.c (initialize_module_call_scd): New.
+ * gpg-agent.c (main): Call them.
+
+2004-12-18 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Remove special Pth initialize.
+
+ * agent.h (map_assuan_err): Define in terms of
+ map_assuan_err_with_source.
+
+2004-12-17 Moritz Schulte <moritz@g10code.com>
+
+ * query.c: Undo change from 2004-12-05.
+
+2004-12-15 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c [W32]: Various hacks to make it work.
+
+ * findkey.c (agent_write_private_key) [W32]: Adjust open call.
+
+ * call-scd.c (start_scd) [W32]: Don't check whether the daemon
+ didn't died. To hard to do under Windows.
+ (start_scd) [W32]: Disable sending of the event signal option.
+
+ * protect-tool.c (read_file, export_p12_file) [W32]: Use setmode
+ to get stdout and stin into binary mode.
+
+2004-12-05 Moritz Schulte <moritz@g10code.com>
+
+ * query.c (start_pinentry): Allow CTRL be NULL.
+
+2004-10-22 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (parse_rereadable_options): Return "not handled"
+ when the log file has not beend hadled. This is will let the main
+ option processing continue. Fixed a bug introduced on 2004-09-4
+ resulting in logging to stderr until a HUP has been given.
+ (main): Don't close the listen FD.
+
+2004-09-30 Werner Koch <wk@g10code.com>
+
+ * Makefile.am: Adjusted from gettext 1.14.
+
+2004-09-29 Werner Koch <wk@g10code.com>
+
+ * minip12.c (parse_bag_encrypted_data): Print error if a bad
+ passphrase has been given.
+
+2004-09-28 Werner Koch <wk@g10code.com>
+
+ * protect.c (agent_unprotect): Fixed wiping of CLEARTEXT. Thanks
+ to Moritz for pointing this out.
+
+2004-09-25 Moritz Schulte <moritz@g10code.com>
+
+ * agent.h: Declare: agent_pksign_do.
+ (struct server_control_s): New member: raw_value.
+
+ * pksign.c (do_encode_md): New argument: raw_value; support
+ generation of raw (non-pkcs1) data objects; adjust callers.
+ (agent_pksign_do): New function, based on code ripped
+ out from agent_pksign.
+ (agent_pksign): Use agent_pksign_do.
+
+ * command.c (start_command_handler): Set ctrl.digest.raw_value.
+
+2004-09-09 Werner Koch <wk@g10code.de>
+
+ * gpg-agent.c (check_for_running_agent): New.
+ (main): The default action is now to check for an already running
+ agent.
+ (parse_rereadable_options): Set logfile only on reread.
+ (main): Do not print the "is development version" note.
+
+2004-08-20 Werner Koch <wk@g10code.de>
+
+ * gpg-agent.c: New option --max-cache-ttl. Suggested by Alexander
+ Belopolsky.
+ * cache.c (housekeeping): Use it here instead of the hardwired
+ default of 1 hour.
+
+ * query.c (start_pinentry): Use a timeout for the pinentry lock.
+
+2004-08-18 Werner Koch <wk@g10code.de>
+
+ * protect-tool.c (get_passphrase): Make sure that the default
+ prompts passed to gpg-agent are utf-8 encoded. Add new prompt values.
+ (import_p12_file, import_p12_file, export_p12_file): Changed calls
+ to get_passphrase so that better prompts are displayed.
+ (get_new_passphrase): New.
+
+2004-07-22 Werner Koch <wk@g10code.de>
+
+ * trustlist.c (read_list): Allow colons in the fingerprint.
+ (headerblurb): Rephrased.
+
+ * gpg-agent.c (handle_connections): Increase the stack size ot 256k.
+
+2004-06-20 Moritz Schulte <moritz@g10code.com>
+
+ * gpg-agent.c: Include <sys/stat.h> (build fix for BSD).
+
+2004-05-11 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_signal): Reload the trustlist on SIGHUP.
+ (start_connection_thread): Hack to simulate a ticker.
+ * trustlist.c (agent_trustlist_housekeeping)
+ (agent_reload_trustlist): New. Protected all global functions
+ here with a simple counter which is sufficient for Pth.
+
+2004-05-03 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: Remove help texts for options lile --lc-ctype.
+ (main): New option --allow-mark-trusted.
+ * trustlist.c (agent_marktrusted): Use it here.
+
+2004-04-30 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c: New option --enable-status-msg.
+ (store_private_key): Print status messages for imported keys.
+ (read_and_unprotect): Ditto for bad passphrase.
+
+ * gpg-agent.c (parse_rereadable_options): New arg REREAD. Allow
+ changing oLogFile.
+ (current_logfile): New.
+
+2004-04-26 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (start_scd): Do not register an event signal if we
+ are running as a pipe server.
+
+2004-04-21 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (start_scd): Send event-signal option. Always check
+ that the scdaemon is still running.
+
+ * gpg-agent.c (handle_signal): Do not use SIGUSR{1,2} anymore for
+ changing the verbosity.
+
+2004-04-16 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Tell the logging code that we are running
+ detached.
+
+2004-04-06 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Use new libgcrypt thread library register
+ scheme.
+
+2004-03-23 Marcus Brinkmann <marcus@g10code.de>
+
+ * gpg-agent.c (main): For now, always print the default config
+ file name for --gpgconf-list.
+
+2004-03-17 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main) <gpgconf>: Fixed default value quoting.
+
+2004-03-16 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (parse_rereadable_options): Use the new
+ DEFAULT_CACHE_TTL macro.
+ (main): Updated --gpgconf-list output.
+
+2004-02-21 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_passwd): Take acount of a key description.
+
+ * genkey.c (reenter_compare_cb): Do not set the error text.
+ (agent_protect_and_store, agent_genkey): Force a re-enter after a
+ non-matching passphrase.
+ * query.c (agent_askpin): Add new arg INITIAL_ERRTEXT; changed
+ all callers.
+
+2004-02-19 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c: New options --have-cert and --prompt.
+ (export_p12_file): Read a certificate from STDIN and pass it to
+ p12_build. Detect a keygrip and construct the filename in that
+ case. Unprotcet a key if needed. Print error messages for key
+ formats we can't handle.
+ (release_passphrase): New.
+ (get_passphrase): New arg PROMPTNO. Return the allocated
+ string. Changed all callers.
+
+ * minip12.c: Revamped the build part.
+ (p12_build): New args CERT and CERTLEN.
+
+2004-02-18 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (main): Setup the used character set.
+ * gpg-agent.c (main): Ditto.
+
+ * gpg-agent.c (set_debug): New. New option --debug-level.
+ (main): New option --gpgconf-list.
+
+2004-02-17 Werner Koch <wk@gnupg.org>
+
+ * pksign.c (do_encode_md): Cleaned up by using gcry_sexp_build.
+
+ * Makefile.am (gpg_protect_tool_SOURCES): Removed
+ simple-pwquery.[ch], as we once moved it to ../common.
+
+2004-02-13 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_setkeydesc): New.
+ (register_commands): Add command SETKEYDESC.
+ (cmd_pksign, cmd_pkdecrypt): Use the key description.
+ (reset_notify): Reset the description.
+ * findkey.c (unprotect): Add arg DESC_TEXT.
+ (agent_key_from_file): Ditto.
+ * pksign.c (agent_pksign): Ditto.
+ * pkdecrypt.c (agent_pkdecrypt): Ditto. Made CIPHERTEXT an
+ unsigned char*.
+
+ * protect-tool.c (main): New options --no-fail-on-exist, --homedir.
+ (store_private_key): Use them here.
+
+2004-02-12 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (read_file, main): Allow reading from stdin.
+
+ * Makefile.am: Include cmacros.am for common flags.
+ (libexec_PROGRAMS): Put gpg-protect-tool there.
+
+2004-02-10 Werner Koch <wk@gnupg.org>
+
+ * minip12.c (parse_bag_encrypted_data): Finished implementation.
+ (p12_parse): Add callback args.
+ * protect-tool.c (import_p12_cert_cb): New.
+ (import_p12_file): Use it.
+
+2004-02-06 Werner Koch <wk@gnupg.org>
+
+ * minip12.c (crypt_block): Add arg CIPHER_ALGO; changed all callers.
+ (set_key_iv): Add arg KEYBYTES; changed caller.
+
+2004-02-03 Werner Koch <wk@gnupg.org>
+
+ * findkey.c (agent_key_from_file): Extra paranoid wipe.
+ * protect.c (agent_unprotect): Ditto.
+ (merge_lists): Ditto. Add arg RESULTLEN.
+ * pkdecrypt.c (agent_pkdecrypt): Don't show the secret key even in
+ debug mode.
+
+ * protect.c: Add DSA and Elgamal description.
+
+2004-01-29 Werner Koch <wk@gnupg.org>
+
+ * agent.h (server_control_s): Add connection_fd field.
+ * command.c (start_command_handler): Init it here.
+ * gpg-agent.c (agent_init_default_ctrl): and here.
+ * call-scd.c: Add the CTRL arg to all functions calling start_scd
+ and pass it to start_scd. Changed all callers
+ (start_scd): Keep track of the current active connection.
+ (agent_reset_scd): New.
+ * command.c (start_command_handler): Call it here.
+ * learncard.c (agent_handle_learn): Add arg CTRL; changed caller.
+ (send_cert_back): Ditto.
+
+2004-01-28 Werner Koch <wk@gnupg.org>
+
+ * trustlist.c (agent_marktrusted): Check whether the trustlist is
+ writable.
+
+2004-01-27 Werner Koch <wk@gnupg.org>
+
+ * sexp-parse.h: Moved to ../common.
+
+2004-01-24 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (atfork_cb): New.
+ (start_scd): Make sure secmem gets cleared.
+ * query.c (atfork_cb): New.
+ (start_pinentry): Make sure secmem gets cleared.
+
+2004-01-16 Werner Koch <wk@gnupg.org>
+
+ * findkey.c (agent_key_from_file): Now return an error code so
+ that we have more detailed error messages in the upper layers.
+ This fixes the handling of pinentry's cancel button.
+ * pksign.c (agent_pksign): Changed accordingly.
+ * pkdecrypt.c (agent_pkdecrypt): Ditto.
+ * command.c (cmd_passwd): Ditto.
+
+2003-12-16 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Set the prefixes for assuan logging.
+
+2003-12-15 Werner Koch <wk@gnupg.org>
+
+ * protect.c (do_encryption): Use gcry_create_nonce instad of the
+ obsolete WEAK_RANDOM.
+
+2003-11-20 Werner Koch <wk@gnupg.org>
+
+ * sexp-parse.h (snext): Don't use atoi_1 and digitp macros, so
+ that this file is useful by other applications too.
+
+2003-10-27 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_get_confirmation): New command.
+
+2003-08-20 Timo Schulz <twoaday@freakmail.de>
+
+ * pksign.c (do_encode_md): Allocate enough space. Cast md
+ byte to unsigned char to prevent sign extension.
+
+2003-08-14 Timo Schulz <twoaday@freakmail.de>
+
+ * pksign.c (do_encode_md): Due to the fact pkcs#1 padding
+ is now in Libgcrypt, use the new interface.
+
+2003-07-31 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am (gpg_agent_LDADD): Added INTLLIBS.
+ (gpg_protect_tool_SOURCES): Added simple-pwquery.[ch]
+
+2003-07-27 Werner Koch <wk@gnupg.org>
+
+ Adjusted for gcry_mpi_print and gcry_mpi_scan API change.
+
+2003-07-15 Werner Koch <wk@gnupg.org>
+
+ * simple-pwquery.c, simple-pwquery.h: Moved to ../common.
+ * Makefile.am (gpg_protect_tool_LDADD): Add simple-pwquery.o.
+ Removed it from xx_SOURCES.
+
+2003-07-04 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_connections): Kludge to allow use of Pth 1
+ and 2.
+
+2003-06-30 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (learn_status_cb): Store the serialno in PARM.
+
+2003-06-26 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (agent_card_serialno): Don't do a RESET anymore.
+
+2003-06-25 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_scd): New.
+ * call-scd.c (agent_card_scd): New.
+ * divert-scd.c (divert_generic_cmd): New
+
+ * call-scd.c (agent_card_learn): New callback args SINFO.
+ (learn_status_cb): Pass all other status lines to the sinfo
+ callback.
+ * learncard.c (release_sinfo, sinfo_cb): New.
+ (agent_handle_learn): Pass the new cb to the learn function and
+ pass the collected information back to the client's assuan
+ connection.
+
+ * gpg-agent.c (main): Moved pth_init before gcry_check_version.
+
+2003-06-24 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_connections): Adjusted for Pth 2.0
+
+ Adjusted for changes in the libgcrypt API. Some more fixes for the
+ libgpg-error stuff.
+
+2003-06-04 Werner Koch <wk@gnupg.org>
+
+ Renamed error codes from INVALID to INV and removed _ERROR suffixes.
+
+2003-06-03 Werner Koch <wk@gnupg.org>
+
+ Changed all error codes in all files to the new libgpg-error scheme.
+
+ * agent.h: Include gpg-error.h and errno.h
+ * Makefile.am: Link with libgpg-error
+
+ * query.c: assuan.h is now a system header.
+ * genkey.c (agent_genkey): Fixed silly use of xmalloc by
+ xtrymalloc.
+
+2003-04-29 Werner Koch <wk@gnupg.org>
+
+ * command.c (register_commands): Adjusted for new Assuan semantics.
+
+ * Makefile.am: Don't override LDFLAGS.
+
+2002-12-04 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: New variable config_filename.
+ (parse_rereadable_options): New.
+ (main): Use it here. Add setting of default values, set
+ config_filename.
+ (reread_configuration): Filled with actual code.
+
+2002-12-03 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (read_key): Don't run make_canonical on a NULL
+ buffer.
+
+ * command.c (parse_hexstring): New.
+ (cmd_sethash): Use it.
+ (parse_keygrip): New.
+ (cmd_havekey, cmd_sigkey): Use it.
+ (cmd_passwd): New.
+ * genkey.c (agent_protect_and_store): New.
+ (store_key): Add arg FORCE.
+ (agent_genkey): Pass false to this force of store_key.
+
+2002-11-13 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Switch all messages to utf-8.
+
+ * simple-pwquery.c (agent_send_all_options): Use $GPG_TTY and
+ stdin with ttyname.
+
+ * cache.c (new_data): Uiih - /sizeof d/sizeof *d/.
+
+2002-11-10 Werner Koch <wk@gnupg.org>
+
+ * command.c (option_handler): Fix keep_tty check.
+
+2002-11-06 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Make sure we have a default ttyname.
+ * command.c (option_handler): Check opt.keep_tty here
+ * query.c (start_pinentry): but not anymore here.
+
+2002-11-05 Werner Koch <wk@gnupg.org>
+
+ * agent.h (opt,server_control_s): Move display and lc_ variables
+ to the control struct so that they are per connection.
+ * gpg-agent.c (agent_init_default_ctrl): New.
+ (main): Assign those command line options to new default_* variables.
+ Reset DISPLAY in server mode so that there is no implicit default.
+ * command.c (start_command_handler): Initialize and deinitialize
+ the control values.
+ (option_handler): Work on the ctrl values and not on the opt.
+ * query.c (start_pinentry): New argument CTRL to set the display
+ connection specific. Changed all callers to pass this value.
+ (agent_askpin,agent_get_passphrase,agent_get_confirmation): Add
+ CTRL arg and pass it ot start_pinentry.
+ * command.c (cmd_get_passphrase): Pass CTRL argument.
+ * trustlist.c (agent_marktrusted): Add CTRL argument
+ * command.c (cmd_marktrusted): Pass CTRL argument
+ * divert-scd.c (ask_for_card): Add CTRL arg.
+ (divert_pksign,divert_pkdecrypt): Ditto. Changed caller.
+ (getpin_cb): Use OPAQUE to pass the CTRL variable. Changed both
+ users.
+ * findkey.c (unprotect): Add CTRL arg.
+ (agent_key_from_file): Ditto.
+
+ * query.c (unlock_pinentry): Disconnect the pinentry so that we
+ start a new one for each request. This is required to support
+ clients with different environments (e.g. X magic cookies).
+
+2002-09-05 Neal H. Walfield <neal@cs.uml.edu>
+
+ * gpg-agent.c (main) [USE_GNU_PTH]: No need to call
+ assuan_set_io_func as assuan is smart.
+
+2002-09-25 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_signal): Flush cache on SIGHUP.
+ * cache.c (agent_flush_cache): New.
+
+ * gpg-agent.c, agent.h: Add --keep-display and --keep-tty.
+ * query.c (start_pinentry): Implement them. The option passing
+ needs more thoughts.
+
+2002-09-09 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (create_private_keys_directory)
+ (create_directories): New.
+ (main): Try to create a home directory.
+
+2002-09-04 Neal H. Walfield <neal@g10code.de>
+
+ * gpg-agent.c (main): Use sigaction, not signal.
+
+2002-09-03 Neal H. Walfield <neal@g10code.de>
+
+ * findkey.c: Include <fcntl.h>.
+ (agent_write_private_key): Prefer POSIX compatibity, open and
+ fdopen, over the simplicity of GNU extensions, fopen(file, "x").
+
+2002-08-22 Werner Koch <wk@gnupg.org>
+
+ * query.c (agent_askpin): Provide the default desc text depending
+ on the pininfo. Do the basic PIN verification only when
+ min_digits is set.
+
+2002-08-21 Werner Koch <wk@gnupg.org>
+
+ * query.c (agent_askpin): Hack to show the right default prompt.
+ (agent_get_passphrase): Ditto.
+
+ * trans.c: Removed and replaced all usages with standard _()
+
+ * divert-scd.c (getpin_cb): Pass a more descritive text to the
+ pinentry.
+
+ * Makefile.am: Renamed the binary protect-tool to gpg-protect-tool.
+ * protect-tool.c: Removed the note about internal use only.
+
+ * gpg-agent.c (main): New option --daemon so that the program is
+ not accidently started in the background.
+
+2002-08-16 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (learn_status_cb): Handle CERTINFO status.
+ (agent_card_learn): Add args for certinfo cb.
+ * learncard.c (release_certinfo,certinfo_cb): New.
+ (send_cert_back): New. With factored out code from ..
+ (agent_handle_learn): here. Return certinfo stuff.
+
+2002-07-26 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): New option --ignore-cache-for-signing.
+ * command.c (option_handler): New server option
+ use-cache-for-signing defaulting to true.
+ (cmd_pksign): handle global and per session option.
+ * findkey.c (agent_key_from_file, unprotect): New arg
+ ignore_cache. Changed all callers.
+ * pksign.c (agent_pksign): Likewise.
+
+2002-06-29 Werner Koch <wk@gnupg.org>
+
+ * query.c (start_pinentry): Use GNUPG_DERAULT_PINENTRY.
+ * call-scd.c (start_scd): Use GNUPG_DEFAULT_SCDAEMON.
+
+2002-06-28 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (export_p12_file): New.
+ (main): New command --p12-export.
+ * minip12.c (create_final,p12_build,compute_tag_length): New.
+ (store_tag_length): New.
+
+2002-06-27 Werner Koch <wk@gnupg.org>
+
+ * minip12.c (crypt_block): Renamed from decrypt_block, add arg to
+ allow encryption.
+
+ * Makefile.am (pkglib_PROGRAMS): Put protect-tool there.
+
+ * findkey.c (agent_write_private_key,agent_key_from_file)
+ (agent_key_available): Use GNUPG_PRIVATE_KEYS_DIR constant.
+ * gpg-agent.c (main): Use GNUPG_DEFAULT_HOMEDIR constant.
+
+ * protect-tool.c (store_private_key): New.
+ (import_p12_file): Store the new file if requested.
+ (main): New options --force and --store.
+
+ * gpg-agent.c (main): Set a global flag when running detached.
+ * query.c (start_pinentry): Pass the list of FD to keep in the
+ child when not running detached.
+ * call-scd.c (start_scd): Ditto.
+
+2002-06-26 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted)
+ (cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_get_passphrase)
+ (cmd_learn): Print an error message for a failed operation.
+
+ * simple-pwquery.c, simple-pwquery.h: New.
+ * protect-tool. (get_passphrase): New, used to get a passphrase
+ from the agent if none was given on the command line.
+
+2002-06-25 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (rsa_key_check): New.
+ (import_p12_file): New.
+ (main): New command --p12-import.
+ * minip12.c, minip12.h: New.
+
+2002-06-24 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (read_file): New.
+ (read_key): Factored most code out to read_file.
+
+2002-06-17 Werner Koch <wk@gnupg.org>
+
+ * agent.h: Add a callback function to the pin_entry_info structure.
+ * query.c (agent_askpin): Use the callback to check for a correct
+ PIN. Removed the start_err_text argument because it is not
+ anymore needed; changed callers.
+ * findkey.c (unprotect): Replace our own check loop by a callback.
+ (try_unprotect_cb): New.
+ * genkey.c (reenter_compare_cb): New.
+ (agent_genkey): Use this callback here. Fixed setting of the pi2
+ variable and a segv in case of an empty PIN.
+
+ * divert-scd.c (getpin_cb): Removed some unused stuff and
+ explained what we still have to change.
+
+2002-06-12 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): New option --disable-pth.
+
+2002-06-11 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c: Add command --show-keygrip
+ (show_keygrip): New.
+
+2002-05-23 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c: Seirialized all scdaeom access when using Pth.
+
+ * cache.c: Made the cache Pth-thread-safe.
+ (agent_unlock_cache_entry): New.
+ * findkey.c (unprotect): Unlock the returned cache value.
+ * command.c (cmd_get_passphrase): Ditto.
+
+ * gpg-agent.c (main): Register pth_read/write with Assuan.
+
+2002-05-22 Werner Koch <wk@gnupg.org>
+
+ * query.c: Serialized all pinentry access when using Pth.
+
+ * gpg-agent.c (handle_signal,start_connection_thread)
+ (handle_connections): New
+ (main): Use the new Pth stuff to allow concurrent connections.
+ * command.c (start_command_handler): Add new arg FD so that the
+ fucntion can also be used for an already connected socket.
+ * Makefile.am: Link with Pth.
+
+2002-05-14 Werner Koch <wk@gnupg.org>
+
+ * cache.c (housekeeping, agent_put_cache): Use our time() wrapper.
+
+2002-04-26 Werner Koch <wk@gnupg.org>
+
+ * cache.c (agent_put_cache): Reinitialize the creation time and
+ the ttl when reusing a slot.
+
+ * call-scd.c (start_scd): Print debug messages only with debug
+ flags set.
+ * query.c (start_pinentry): Ditto.
+
+2002-04-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * agent.h (agent_get_confirmation): Replace paramter prompt with
+ two parameters ok and cancel.
+ * query.c (agent_get_confirmation): Likewise. Implement this.
+ * trustlist.c (agent_marktrusted): Fix invocation of
+ agent_get_confirmation.
+ * divert-scd.c (ask_for_card): Likewise.
+
+2002-04-24 Marcus Brinkmann <marcus@g10code.de>
+
+ * agent.h (struct opt): Add members display, ttyname, ttytype,
+ lc_ctype, and lc_messages.
+ * gpg-agent.c (enum cmd_and_opt_values): Add oDisplay, oTTYname,
+ oTTYtype, oLCctype, and LCmessages.
+ (main): Handle these options.
+ * command.c (option_handler): New function.
+ (register_commands): Register option handler.
+ * query.c (start_pinentry): Pass the various display and tty
+ options to the pinentry.
+
+2002-04-05 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (show_file): New. Used as default action.
+
+2002-03-28 Werner Koch <wk@gnupg.org>
+
+ * divert-scd.c (encode_md_for_card): Don't do the pkcs-1 padding,
+ the scdaemon should take care of it.
+ (ask_for_card): Hack to not display the trailing zero.
+
+2002-03-11 Werner Koch <wk@gnupg.org>
+
+ * learncard.c (kpinfo_cb): Remove the content restrictions from
+ the keyID.
+
+2002-03-06 Werner Koch <wk@gnupg.org>
+
+ * learncard.c: New.
+ * divert-scd.c (ask_for_card): The serial number is binary so
+ convert it to hex here.
+ * findkey.c (agent_write_private_key): New.
+ * genkey.c (store_key): And use it here.
+
+ * pkdecrypt.c (agent_pkdecrypt): Changed the way the diversion is done.
+ * divert-scd.c (divert_pkdecrypt): Changed interface and
+ implemented it.
+
+2002-03-05 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (inq_needpin): New.
+ (agent_card_pksign): Add getpin_cb args.
+ (agent_card_pkdecrypt): New.
+
+2002-03-04 Werner Koch <wk@gnupg.org>
+
+ * pksign.c (agent_pksign): Changed how the diversion is done.
+ * divert-scd.c (divert_pksign): Changed interface and implemented it.
+ (encode_md_for_card): New.
+ * call-scd.c (agent_card_pksign): New.
+
+2002-02-28 Werner Koch <wk@gnupg.org>
+
+ * pksign.c (agent_pksign): Detect whether a Smartcard is to be
+ used and divert the operation in this case.
+ * pkdecrypt.c (agent_pkdecrypt): Likewise
+ * findkey.c (agent_key_from_file): Add optional arg shadow_info
+ and have it return information about a shadowed key.
+ * protect.c (agent_get_shadow_info): New.
+
+ * protect.c (snext,sskip,smatch): Moved to
+ * sexp-parse.h: New file.
+ * divert-scd.c: New.
+
+2002-02-27 Werner Koch <wk@gnupg.org>
+
+ * protect.c (agent_shadow_key): New.
+
+ * command.c (cmd_learn): New command LEARN.
+ * gpg-agent.c: New option --scdaemon-program.
+ * call-scd.c (start_scd): New. Based on query.c
+ * query.c: Add 2 more arguments to all uses of assuan_transact.
+
+2002-02-18 Werner Koch <wk@gnupg.org>
+
+ * findkey.c (unprotect): Show an error message for a bad passphrase.
+
+ * command.c (cmd_marktrusted): Implemented.
+ * trustlist.c (agent_marktrusted): New.
+ (open_list): Add APPEND arg.
+
+ * query.c (agent_get_confirmation): New.
+
+2002-02-06 Werner Koch <wk@gnupg.org>
+
+ * cache.c (housekeeping): Fixed linking in the remove case.
+
+2002-02-01 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: New option --default-cache-ttl.
+ * cache.c (agent_put_cache): Use it.
+
+ * cache.c: Add a few debug outputs.
+
+ * protect.c (agent_private_key_type): New.
+ * agent.h: Add PRIVATE_KEY_ enums.
+ * findkey.c (agent_key_from_file): Use it to decide whether we
+ have to unprotect a key.
+ (unprotect): Cache the passphrase.
+
+ * findkey.c (agent_key_from_file,agent_key_available): The key
+ files do now require a ".key" suffix to make a script's life
+ easier.
+ * genkey.c (store_key): Ditto.
+
+2002-01-31 Werner Koch <wk@gnupg.org>
+
+ * genkey.c (store_key): Protect the key.
+ (agent_genkey): Ask for the passphrase.
+ * findkey.c (unprotect): Actually unprotect the key.
+ * query.c (agent_askpin): Add an optional start_err_text.
+
+2002-01-30 Werner Koch <wk@gnupg.org>
+
+ * protect.c: New.
+ (hash_passphrase): Based on the GnuPG 1.0.6 version.
+ * protect-tool.c: New
+
+2002-01-29 Werner Koch <wk@gnupg.org>
+
+ * findkey.c (agent_key_available): New.
+ * command.c (cmd_havekey): New.
+ (register_commands): And register new command.
+
+2002-01-20 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_get_passphrase): Remove the plus signs.
+
+ * query.c (start_pinentry): Send no-grab option to pinentry
+ * gpg-agent.c (main): Move variable grab as no_grab to agent.h.
+
+2002-01-19 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Disable core dumps.
+
+ * cache.c: New.
+ * command.c (cmd_get_passphrase): Use the cache.
+ (cmd_clear_passphrase): Ditto.
+
+ * gpg-agent.c: Removed unused cruft and implement the socket
+ based server.
+ (my_strusage): Take bug report address from configure.ac.
+ * command.c (start_command_handler): Add an argument to start as
+ regular server.
+ (start_command_handler): Enable Assuan logging.
+
+2002-01-15 Werner Koch <wk@gnupg.org>
+
+ * trustlist.c: New.
+ * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted): New.
+
+2002-01-07 Werner Koch <wk@gnupg.org>
+
+ * genkey.c: Store the secret part and return the public part.
+
+2002-01-03 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_get_passphrase): New.
+ (cmd_clear_passphrase): New.
+ * query.c (agent_get_passphrase): New.
+
+2002-01-02 Werner Koch <wk@gnupg.org>
+
+ * genkey.c: New.
+ * command.c (cmd_genkey): New.
+
+ * command.c (rc_to_assuan_status): Removed and changed all callers
+ to use map_to_assuan_status.
+
+2001-12-19 Werner Koch <wk@gnupg.org>
+
+ * keyformat.txt: New.
+
+2001-12-19 Marcus Brinkmann <marcus@g10code.de>
+
+ * query.c (start_pinentry): Add new argument to assuan_pipe_connect.
+
+2001-12-18 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am: Use LIBGCRYPT macros
+
+2001-12-14 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): New option --batch. New option --debug-wait
+ n, so that it is possible to attach gdb when used in server mode.
+ * query.c (agent_askpin): Don't ask in batch mode.
+
+ * command.c: Removed the conversion macros as they are now in
+ ../common/util.h.
+
+2001-12-14 Marcus Brinkmann <marcus@g10code.de>
+
+ * query.c (LINELENGTH): Removed.
+ (agent_askpin): Use ASSUAN_LINELENGTH, not LINELENGTH.
+
+2001-11-19 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: Removed all GUI code, removed code for old
+ protocol. New code to use the Assuan protocol as a server and
+ also to communicate with a new ask-passphrase utility.
+
+2000-11-22 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): csh support by Dan Winship, new options --sh
+ and --csh and set default by consulting $SHELL.
+
+Mon Aug 21 17:59:17 CEST 2000 Werner Koch <wk@openit.de>
+
+ * gpg-agent.c (passphrase_dialog): Cleanup the window and added the
+ user supplied text to the window.
+ (main): Fixed segv in gtk_init when used without a command to start.
+
+ * gpg-agent.c: --flush option.
+ (req_flush): New.
+ (req_clear_passphrase): Implemented.
+
+Fri Aug 18 14:27:14 CEST 2000 Werner Koch <wk@openit.de>
+
+ * gpg-agent.c: New.
+ * Makefile.am: New.
+
+
+ Copyright 2001, 2002, 2003, 2004, 2005,
+ 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+Local Variables:
+buffer-read-only: t
+End:
diff --git a/agent/Makefile.am b/agent/Makefile.am
new file mode 100644
index 0000000..f0ba964
--- /dev/null
+++ b/agent/Makefile.am
@@ -0,0 +1,118 @@
+# Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
+#
+# This file is part of GnuPG.
+#
+# GnuPG 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.
+#
+# GnuPG 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 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 <https://www.gnu.org/licenses/>.
+
+## Process this file with automake to produce Makefile.in
+
+bin_PROGRAMS = gpg-agent
+libexec_PROGRAMS = gpg-protect-tool
+if !HAVE_W32CE_SYSTEM
+# fixme: Do no use simple-pwquery for preset-passphrase.
+libexec_PROGRAMS += gpg-preset-passphrase
+endif
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = ChangeLog-2011 \
+ gpg-agent-w32info.rc gpg-agent.w32-manifest.in \
+ all-tests.scm
+
+
+AM_CPPFLAGS =
+
+include $(top_srcdir)/am/cmacros.am
+
+if HAVE_W32_SYSTEM
+gpg_agent_robjs = $(resource_objs) gpg-agent-w32info.o
+gpg-agent-w32info.o : gpg-agent.w32-manifest
+else
+gpg_agent_robjs =
+endif
+
+AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
+
+gpg_agent_SOURCES = \
+ gpg-agent.c agent.h \
+ command.c command-ssh.c \
+ call-pinentry.c \
+ cache.c \
+ trans.c \
+ findkey.c \
+ pksign.c \
+ pkdecrypt.c \
+ genkey.c \
+ protect.c \
+ trustlist.c \
+ divert-scd.c \
+ cvt-openpgp.c cvt-openpgp.h \
+ call-scd.c \
+ learncard.c
+
+common_libs = $(libcommon)
+commonpth_libs = $(libcommonpth)
+if HAVE_W32CE_SYSTEM
+pwquery_libs =
+else
+pwquery_libs = ../common/libsimple-pwquery.a
+endif
+
+
+gpg_agent_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \
+ $(INCICONV)
+gpg_agent_LDADD = $(commonpth_libs) \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
+ $(gpg_agent_robjs)
+gpg_agent_LDFLAGS = $(extra_bin_ldflags)
+gpg_agent_DEPENDENCIES = $(gpg_agent_robjs)
+
+gpg_protect_tool_SOURCES = \
+ protect-tool.c \
+ protect.c cvt-openpgp.c
+
+gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) \
+ $(INCICONV)
+gpg_protect_tool_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
+gpg_preset_passphrase_SOURCES = \
+ preset-passphrase.c
+
+# Needs $(NETLIBS) for libsimple-pwquery.la.
+gpg_preset_passphrase_LDADD = \
+ $(pwquery_libs) $(common_libs) $(LIBASSUAN_LIBS) \
+ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
+
+# Make sure that all libs are build before we use them. This is
+# important for things like make -j2.
+$(PROGRAMS): $(common_libs) $(commonpth_libs) $(pwquery_libs)
+
+
+
+#
+# Module tests
+#
+if DISABLE_TESTS
+TESTS =
+else
+TESTS = t-protect
+endif
+
+t_common_ldadd = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
+ $(LIBINTL) $(LIBICONV) $(NETLIBS)
+
+t_protect_SOURCES = t-protect.c protect.c
+t_protect_LDADD = $(t_common_ldadd)
diff --git a/agent/Makefile.in b/agent/Makefile.in
new file mode 100644
index 0000000..ab8735f
--- /dev/null
+++ b/agent/Makefile.in
@@ -0,0 +1,1408 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
+#
+# This file is part of GnuPG.
+#
+# GnuPG 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.
+#
+# GnuPG 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 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 <https://www.gnu.org/licenses/>.
+
+# cmacros.am - C macro definitions
+# Copyright (C) 2004 Free Software Foundation, Inc.
+#
+# This file is part of GnuPG.
+#
+# GnuPG 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.
+#
+# GnuPG 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 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 <https://www.gnu.org/licenses/>.
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = gpg-agent$(EXEEXT)
+libexec_PROGRAMS = gpg-protect-tool$(EXEEXT) $(am__EXEEXT_1)
+# fixme: Do no use simple-pwquery for preset-passphrase.
+@HAVE_W32CE_SYSTEM_FALSE@am__append_1 = gpg-preset-passphrase
+noinst_PROGRAMS = $(am__EXEEXT_2)
+@HAVE_DOSISH_SYSTEM_FALSE@am__append_2 = -DGNUPG_BINDIR="\"$(bindir)\"" \
+@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \
+@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \
+@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \
+@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \
+@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\""
+
+
+# If a specific protect tool program has been defined, pass its name
+# to cc. Note that these macros should not be used directly but via
+# the gnupg_module_name function.
+@GNUPG_AGENT_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\""
+@GNUPG_PINENTRY_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\""
+@GNUPG_SCDAEMON_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\""
+@GNUPG_DIRMNGR_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\""
+@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\""
+@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_8 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\""
+@DISABLE_TESTS_FALSE@TESTS = t-protect$(EXEEXT)
+subdir = agent
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \
+ $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \
+ $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \
+ $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \
+ $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \
+ $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = gpg-agent.w32-manifest
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)"
+@HAVE_W32CE_SYSTEM_FALSE@am__EXEEXT_1 = \
+@HAVE_W32CE_SYSTEM_FALSE@ gpg-preset-passphrase$(EXEEXT)
+@DISABLE_TESTS_FALSE@am__EXEEXT_2 = t-protect$(EXEEXT)
+PROGRAMS = $(bin_PROGRAMS) $(libexec_PROGRAMS) $(noinst_PROGRAMS)
+am_gpg_agent_OBJECTS = gpg_agent-gpg-agent.$(OBJEXT) \
+ gpg_agent-command.$(OBJEXT) gpg_agent-command-ssh.$(OBJEXT) \
+ gpg_agent-call-pinentry.$(OBJEXT) gpg_agent-cache.$(OBJEXT) \
+ gpg_agent-trans.$(OBJEXT) gpg_agent-findkey.$(OBJEXT) \
+ gpg_agent-pksign.$(OBJEXT) gpg_agent-pkdecrypt.$(OBJEXT) \
+ gpg_agent-genkey.$(OBJEXT) gpg_agent-protect.$(OBJEXT) \
+ gpg_agent-trustlist.$(OBJEXT) gpg_agent-divert-scd.$(OBJEXT) \
+ gpg_agent-cvt-openpgp.$(OBJEXT) gpg_agent-call-scd.$(OBJEXT) \
+ gpg_agent-learncard.$(OBJEXT)
+gpg_agent_OBJECTS = $(am_gpg_agent_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \
+@HAVE_W32_SYSTEM_TRUE@ gpg-agent-w32info.o
+gpg_agent_LINK = $(CCLD) $(gpg_agent_CFLAGS) $(CFLAGS) \
+ $(gpg_agent_LDFLAGS) $(LDFLAGS) -o $@
+am_gpg_preset_passphrase_OBJECTS = preset-passphrase.$(OBJEXT)
+gpg_preset_passphrase_OBJECTS = $(am_gpg_preset_passphrase_OBJECTS)
+@HAVE_W32CE_SYSTEM_FALSE@am__DEPENDENCIES_3 = \
+@HAVE_W32CE_SYSTEM_FALSE@ ../common/libsimple-pwquery.a
+gpg_preset_passphrase_DEPENDENCIES = $(am__DEPENDENCIES_3) \
+ $(common_libs) $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_gpg_protect_tool_OBJECTS = gpg_protect_tool-protect-tool.$(OBJEXT) \
+ gpg_protect_tool-protect.$(OBJEXT) \
+ gpg_protect_tool-cvt-openpgp.$(OBJEXT)
+gpg_protect_tool_OBJECTS = $(am_gpg_protect_tool_OBJECTS)
+gpg_protect_tool_DEPENDENCIES = $(common_libs) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+gpg_protect_tool_LINK = $(CCLD) $(gpg_protect_tool_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am_t_protect_OBJECTS = t-protect.$(OBJEXT) protect.$(OBJEXT)
+t_protect_OBJECTS = $(am_t_protect_OBJECTS)
+am__DEPENDENCIES_4 = $(common_libs) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+t_protect_DEPENDENCIES = $(am__DEPENDENCIES_4)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/gpg_agent-cache.Po \
+ ./$(DEPDIR)/gpg_agent-call-pinentry.Po \
+ ./$(DEPDIR)/gpg_agent-call-scd.Po \
+ ./$(DEPDIR)/gpg_agent-command-ssh.Po \
+ ./$(DEPDIR)/gpg_agent-command.Po \
+ ./$(DEPDIR)/gpg_agent-cvt-openpgp.Po \
+ ./$(DEPDIR)/gpg_agent-divert-scd.Po \
+ ./$(DEPDIR)/gpg_agent-findkey.Po \
+ ./$(DEPDIR)/gpg_agent-genkey.Po \
+ ./$(DEPDIR)/gpg_agent-gpg-agent.Po \
+ ./$(DEPDIR)/gpg_agent-learncard.Po \
+ ./$(DEPDIR)/gpg_agent-pkdecrypt.Po \
+ ./$(DEPDIR)/gpg_agent-pksign.Po \
+ ./$(DEPDIR)/gpg_agent-protect.Po \
+ ./$(DEPDIR)/gpg_agent-trans.Po \
+ ./$(DEPDIR)/gpg_agent-trustlist.Po \
+ ./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po \
+ ./$(DEPDIR)/gpg_protect_tool-protect-tool.Po \
+ ./$(DEPDIR)/gpg_protect_tool-protect.Po \
+ ./$(DEPDIR)/preset-passphrase.Po ./$(DEPDIR)/protect.Po \
+ ./$(DEPDIR)/t-protect.Po
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(gpg_agent_SOURCES) $(gpg_preset_passphrase_SOURCES) \
+ $(gpg_protect_tool_SOURCES) $(t_protect_SOURCES)
+DIST_SOURCES = $(gpg_agent_SOURCES) $(gpg_preset_passphrase_SOURCES) \
+ $(gpg_protect_tool_SOURCES) $(t_protect_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/gpg-agent.w32-manifest.in \
+ $(top_srcdir)/am/cmacros.am $(top_srcdir)/build-aux/depcomp \
+ $(top_srcdir)/build-aux/mkinstalldirs
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@
+BUILD_FILEVERSION = @BUILD_FILEVERSION@
+BUILD_HOSTNAME = @BUILD_HOSTNAME@
+BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@
+BUILD_REVISION = @BUILD_REVISION@
+BUILD_TIMESTAMP = @BUILD_TIMESTAMP@
+BUILD_VERSION = @BUILD_VERSION@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DL_LIBS = @DL_LIBS@
+DNSLIBS = @DNSLIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENCFS = @ENCFS@
+EXEEXT = @EXEEXT@
+FUSERMOUNT = @FUSERMOUNT@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@
+GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@
+GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@
+GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@
+GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@
+GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@
+GPGKEYS_LDAP = @GPGKEYS_LDAP@
+GPGRT_CONFIG = @GPGRT_CONFIG@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@
+GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+KSBA_CFLAGS = @KSBA_CFLAGS@
+KSBA_CONFIG = @KSBA_CONFIG@
+KSBA_LIBS = @KSBA_LIBS@
+LBER_LIBS = @LBER_LIBS@
+LDAPLIBS = @LDAPLIBS@
+LDAP_CPPFLAGS = @LDAP_CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@
+LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBREADLINE = @LIBREADLINE@
+LIBS = @LIBS@
+LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@
+LIBUSB_LIBS = @LIBUSB_LIBS@
+LIBUTIL_LIBS = @LIBUTIL_LIBS@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NETLIBS = @NETLIBS@
+NPTH_CFLAGS = @NPTH_CFLAGS@
+NPTH_CONFIG = @NPTH_CONFIG@
+NPTH_LIBS = @NPTH_LIBS@
+NTBTLS_CFLAGS = @NTBTLS_CFLAGS@
+NTBTLS_CONFIG = @NTBTLS_CONFIG@
+NTBTLS_LIBS = @NTBTLS_LIBS@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_GT = @PACKAGE_GT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+RANLIB = @RANLIB@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SHRED = @SHRED@
+SQLITE3_CFLAGS = @SQLITE3_CFLAGS@
+SQLITE3_LIBS = @SQLITE3_LIBS@
+STRIP = @STRIP@
+SYSROOT = @SYSROOT@
+SYS_SOCKET_H = @SYS_SOCKET_H@
+TAR = @TAR@
+USE_C99_CFLAGS = @USE_C99_CFLAGS@
+USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+W32SOCKLIBS = @W32SOCKLIBS@
+WINDRES = @WINDRES@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+YAT2M = @YAT2M@
+ZLIBS = @ZLIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = $(datadir)/locale
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = ChangeLog-2011 \
+ gpg-agent-w32info.rc gpg-agent.w32-manifest.in \
+ all-tests.scm
+
+
+# NB: AM_CFLAGS may also be used by tools running on the build
+# platform to create source files.
+AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(am__append_2) \
+ $(am__append_3) $(am__append_4) $(am__append_5) \
+ $(am__append_6) $(am__append_7) $(am__append_8)
+@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs =
+
+# Under Windows we use LockFileEx. WindowsCE provides this only on
+# the WindowsMobile 6 platform and thus we need to use the coredll6
+# import library. We also want to use a stacksize of 256k instead of
+# the 2MB which is the default with cegcc. 256k is the largest stack
+# we use with pth.
+@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6
+@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags =
+@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000
+resource_objs =
+
+# Convenience macros
+libcommon = ../common/libcommon.a
+libcommonpth = ../common/libcommonpth.a
+libcommontls = ../common/libcommontls.a
+libcommontlsnpth = ../common/libcommontlsnpth.a
+@HAVE_W32_SYSTEM_FALSE@gpg_agent_robjs =
+@HAVE_W32_SYSTEM_TRUE@gpg_agent_robjs = $(resource_objs) gpg-agent-w32info.o
+AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
+gpg_agent_SOURCES = \
+ gpg-agent.c agent.h \
+ command.c command-ssh.c \
+ call-pinentry.c \
+ cache.c \
+ trans.c \
+ findkey.c \
+ pksign.c \
+ pkdecrypt.c \
+ genkey.c \
+ protect.c \
+ trustlist.c \
+ divert-scd.c \
+ cvt-openpgp.c cvt-openpgp.h \
+ call-scd.c \
+ learncard.c
+
+common_libs = $(libcommon)
+commonpth_libs = $(libcommonpth)
+@HAVE_W32CE_SYSTEM_FALSE@pwquery_libs = ../common/libsimple-pwquery.a
+@HAVE_W32CE_SYSTEM_TRUE@pwquery_libs =
+gpg_agent_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \
+ $(INCICONV)
+
+gpg_agent_LDADD = $(commonpth_libs) \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
+ $(gpg_agent_robjs)
+
+gpg_agent_LDFLAGS = $(extra_bin_ldflags)
+gpg_agent_DEPENDENCIES = $(gpg_agent_robjs)
+gpg_protect_tool_SOURCES = \
+ protect-tool.c \
+ protect.c cvt-openpgp.c
+
+gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) \
+ $(INCICONV)
+
+gpg_protect_tool_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
+gpg_preset_passphrase_SOURCES = \
+ preset-passphrase.c
+
+
+# Needs $(NETLIBS) for libsimple-pwquery.la.
+gpg_preset_passphrase_LDADD = \
+ $(pwquery_libs) $(common_libs) $(LIBASSUAN_LIBS) \
+ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
+t_common_ldadd = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
+ $(LIBINTL) $(LIBICONV) $(NETLIBS)
+
+t_protect_SOURCES = t-protect.c protect.c
+t_protect_LDADD = $(t_common_ldadd)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj .rc
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu agent/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu agent/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/am/cmacros.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+gpg-agent.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpg-agent.w32-manifest.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+install-libexecPROGRAMS: $(libexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-libexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(libexecdir)" && rm -f $$files
+
+clean-libexecPROGRAMS:
+ -test -z "$(libexec_PROGRAMS)" || rm -f $(libexec_PROGRAMS)
+
+clean-noinstPROGRAMS:
+ -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+
+gpg-agent$(EXEEXT): $(gpg_agent_OBJECTS) $(gpg_agent_DEPENDENCIES) $(EXTRA_gpg_agent_DEPENDENCIES)
+ @rm -f gpg-agent$(EXEEXT)
+ $(AM_V_CCLD)$(gpg_agent_LINK) $(gpg_agent_OBJECTS) $(gpg_agent_LDADD) $(LIBS)
+
+gpg-preset-passphrase$(EXEEXT): $(gpg_preset_passphrase_OBJECTS) $(gpg_preset_passphrase_DEPENDENCIES) $(EXTRA_gpg_preset_passphrase_DEPENDENCIES)
+ @rm -f gpg-preset-passphrase$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(gpg_preset_passphrase_OBJECTS) $(gpg_preset_passphrase_LDADD) $(LIBS)
+
+gpg-protect-tool$(EXEEXT): $(gpg_protect_tool_OBJECTS) $(gpg_protect_tool_DEPENDENCIES) $(EXTRA_gpg_protect_tool_DEPENDENCIES)
+ @rm -f gpg-protect-tool$(EXEEXT)
+ $(AM_V_CCLD)$(gpg_protect_tool_LINK) $(gpg_protect_tool_OBJECTS) $(gpg_protect_tool_LDADD) $(LIBS)
+
+t-protect$(EXEEXT): $(t_protect_OBJECTS) $(t_protect_DEPENDENCIES) $(EXTRA_t_protect_DEPENDENCIES)
+ @rm -f t-protect$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(t_protect_OBJECTS) $(t_protect_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-cache.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-call-pinentry.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-call-scd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-command-ssh.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-command.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-cvt-openpgp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-divert-scd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-findkey.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-genkey.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-gpg-agent.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-learncard.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-pkdecrypt.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-pksign.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-protect.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-trans.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-trustlist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-protect-tool.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-protect.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/preset-passphrase.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protect.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-protect.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+gpg_agent-gpg-agent.o: gpg-agent.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-gpg-agent.o -MD -MP -MF $(DEPDIR)/gpg_agent-gpg-agent.Tpo -c -o gpg_agent-gpg-agent.o `test -f 'gpg-agent.c' || echo '$(srcdir)/'`gpg-agent.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-gpg-agent.Tpo $(DEPDIR)/gpg_agent-gpg-agent.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-agent.c' object='gpg_agent-gpg-agent.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-gpg-agent.o `test -f 'gpg-agent.c' || echo '$(srcdir)/'`gpg-agent.c
+
+gpg_agent-gpg-agent.obj: gpg-agent.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-gpg-agent.obj -MD -MP -MF $(DEPDIR)/gpg_agent-gpg-agent.Tpo -c -o gpg_agent-gpg-agent.obj `if test -f 'gpg-agent.c'; then $(CYGPATH_W) 'gpg-agent.c'; else $(CYGPATH_W) '$(srcdir)/gpg-agent.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-gpg-agent.Tpo $(DEPDIR)/gpg_agent-gpg-agent.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-agent.c' object='gpg_agent-gpg-agent.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-gpg-agent.obj `if test -f 'gpg-agent.c'; then $(CYGPATH_W) 'gpg-agent.c'; else $(CYGPATH_W) '$(srcdir)/gpg-agent.c'; fi`
+
+gpg_agent-command.o: command.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command.o -MD -MP -MF $(DEPDIR)/gpg_agent-command.Tpo -c -o gpg_agent-command.o `test -f 'command.c' || echo '$(srcdir)/'`command.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command.Tpo $(DEPDIR)/gpg_agent-command.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command.c' object='gpg_agent-command.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command.o `test -f 'command.c' || echo '$(srcdir)/'`command.c
+
+gpg_agent-command.obj: command.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command.obj -MD -MP -MF $(DEPDIR)/gpg_agent-command.Tpo -c -o gpg_agent-command.obj `if test -f 'command.c'; then $(CYGPATH_W) 'command.c'; else $(CYGPATH_W) '$(srcdir)/command.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command.Tpo $(DEPDIR)/gpg_agent-command.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command.c' object='gpg_agent-command.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command.obj `if test -f 'command.c'; then $(CYGPATH_W) 'command.c'; else $(CYGPATH_W) '$(srcdir)/command.c'; fi`
+
+gpg_agent-command-ssh.o: command-ssh.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command-ssh.o -MD -MP -MF $(DEPDIR)/gpg_agent-command-ssh.Tpo -c -o gpg_agent-command-ssh.o `test -f 'command-ssh.c' || echo '$(srcdir)/'`command-ssh.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command-ssh.Tpo $(DEPDIR)/gpg_agent-command-ssh.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command-ssh.c' object='gpg_agent-command-ssh.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command-ssh.o `test -f 'command-ssh.c' || echo '$(srcdir)/'`command-ssh.c
+
+gpg_agent-command-ssh.obj: command-ssh.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command-ssh.obj -MD -MP -MF $(DEPDIR)/gpg_agent-command-ssh.Tpo -c -o gpg_agent-command-ssh.obj `if test -f 'command-ssh.c'; then $(CYGPATH_W) 'command-ssh.c'; else $(CYGPATH_W) '$(srcdir)/command-ssh.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command-ssh.Tpo $(DEPDIR)/gpg_agent-command-ssh.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command-ssh.c' object='gpg_agent-command-ssh.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command-ssh.obj `if test -f 'command-ssh.c'; then $(CYGPATH_W) 'command-ssh.c'; else $(CYGPATH_W) '$(srcdir)/command-ssh.c'; fi`
+
+gpg_agent-call-pinentry.o: call-pinentry.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-pinentry.o -MD -MP -MF $(DEPDIR)/gpg_agent-call-pinentry.Tpo -c -o gpg_agent-call-pinentry.o `test -f 'call-pinentry.c' || echo '$(srcdir)/'`call-pinentry.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-pinentry.Tpo $(DEPDIR)/gpg_agent-call-pinentry.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-pinentry.c' object='gpg_agent-call-pinentry.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-pinentry.o `test -f 'call-pinentry.c' || echo '$(srcdir)/'`call-pinentry.c
+
+gpg_agent-call-pinentry.obj: call-pinentry.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-pinentry.obj -MD -MP -MF $(DEPDIR)/gpg_agent-call-pinentry.Tpo -c -o gpg_agent-call-pinentry.obj `if test -f 'call-pinentry.c'; then $(CYGPATH_W) 'call-pinentry.c'; else $(CYGPATH_W) '$(srcdir)/call-pinentry.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-pinentry.Tpo $(DEPDIR)/gpg_agent-call-pinentry.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-pinentry.c' object='gpg_agent-call-pinentry.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-pinentry.obj `if test -f 'call-pinentry.c'; then $(CYGPATH_W) 'call-pinentry.c'; else $(CYGPATH_W) '$(srcdir)/call-pinentry.c'; fi`
+
+gpg_agent-cache.o: cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cache.o -MD -MP -MF $(DEPDIR)/gpg_agent-cache.Tpo -c -o gpg_agent-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cache.Tpo $(DEPDIR)/gpg_agent-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='gpg_agent-cache.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c
+
+gpg_agent-cache.obj: cache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cache.obj -MD -MP -MF $(DEPDIR)/gpg_agent-cache.Tpo -c -o gpg_agent-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cache.Tpo $(DEPDIR)/gpg_agent-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='gpg_agent-cache.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi`
+
+gpg_agent-trans.o: trans.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trans.o -MD -MP -MF $(DEPDIR)/gpg_agent-trans.Tpo -c -o gpg_agent-trans.o `test -f 'trans.c' || echo '$(srcdir)/'`trans.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trans.Tpo $(DEPDIR)/gpg_agent-trans.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trans.c' object='gpg_agent-trans.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trans.o `test -f 'trans.c' || echo '$(srcdir)/'`trans.c
+
+gpg_agent-trans.obj: trans.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trans.obj -MD -MP -MF $(DEPDIR)/gpg_agent-trans.Tpo -c -o gpg_agent-trans.obj `if test -f 'trans.c'; then $(CYGPATH_W) 'trans.c'; else $(CYGPATH_W) '$(srcdir)/trans.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trans.Tpo $(DEPDIR)/gpg_agent-trans.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trans.c' object='gpg_agent-trans.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trans.obj `if test -f 'trans.c'; then $(CYGPATH_W) 'trans.c'; else $(CYGPATH_W) '$(srcdir)/trans.c'; fi`
+
+gpg_agent-findkey.o: findkey.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-findkey.o -MD -MP -MF $(DEPDIR)/gpg_agent-findkey.Tpo -c -o gpg_agent-findkey.o `test -f 'findkey.c' || echo '$(srcdir)/'`findkey.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-findkey.Tpo $(DEPDIR)/gpg_agent-findkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='findkey.c' object='gpg_agent-findkey.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-findkey.o `test -f 'findkey.c' || echo '$(srcdir)/'`findkey.c
+
+gpg_agent-findkey.obj: findkey.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-findkey.obj -MD -MP -MF $(DEPDIR)/gpg_agent-findkey.Tpo -c -o gpg_agent-findkey.obj `if test -f 'findkey.c'; then $(CYGPATH_W) 'findkey.c'; else $(CYGPATH_W) '$(srcdir)/findkey.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-findkey.Tpo $(DEPDIR)/gpg_agent-findkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='findkey.c' object='gpg_agent-findkey.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-findkey.obj `if test -f 'findkey.c'; then $(CYGPATH_W) 'findkey.c'; else $(CYGPATH_W) '$(srcdir)/findkey.c'; fi`
+
+gpg_agent-pksign.o: pksign.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pksign.o -MD -MP -MF $(DEPDIR)/gpg_agent-pksign.Tpo -c -o gpg_agent-pksign.o `test -f 'pksign.c' || echo '$(srcdir)/'`pksign.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pksign.Tpo $(DEPDIR)/gpg_agent-pksign.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pksign.c' object='gpg_agent-pksign.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pksign.o `test -f 'pksign.c' || echo '$(srcdir)/'`pksign.c
+
+gpg_agent-pksign.obj: pksign.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pksign.obj -MD -MP -MF $(DEPDIR)/gpg_agent-pksign.Tpo -c -o gpg_agent-pksign.obj `if test -f 'pksign.c'; then $(CYGPATH_W) 'pksign.c'; else $(CYGPATH_W) '$(srcdir)/pksign.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pksign.Tpo $(DEPDIR)/gpg_agent-pksign.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pksign.c' object='gpg_agent-pksign.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pksign.obj `if test -f 'pksign.c'; then $(CYGPATH_W) 'pksign.c'; else $(CYGPATH_W) '$(srcdir)/pksign.c'; fi`
+
+gpg_agent-pkdecrypt.o: pkdecrypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pkdecrypt.o -MD -MP -MF $(DEPDIR)/gpg_agent-pkdecrypt.Tpo -c -o gpg_agent-pkdecrypt.o `test -f 'pkdecrypt.c' || echo '$(srcdir)/'`pkdecrypt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pkdecrypt.Tpo $(DEPDIR)/gpg_agent-pkdecrypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pkdecrypt.c' object='gpg_agent-pkdecrypt.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pkdecrypt.o `test -f 'pkdecrypt.c' || echo '$(srcdir)/'`pkdecrypt.c
+
+gpg_agent-pkdecrypt.obj: pkdecrypt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pkdecrypt.obj -MD -MP -MF $(DEPDIR)/gpg_agent-pkdecrypt.Tpo -c -o gpg_agent-pkdecrypt.obj `if test -f 'pkdecrypt.c'; then $(CYGPATH_W) 'pkdecrypt.c'; else $(CYGPATH_W) '$(srcdir)/pkdecrypt.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pkdecrypt.Tpo $(DEPDIR)/gpg_agent-pkdecrypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pkdecrypt.c' object='gpg_agent-pkdecrypt.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pkdecrypt.obj `if test -f 'pkdecrypt.c'; then $(CYGPATH_W) 'pkdecrypt.c'; else $(CYGPATH_W) '$(srcdir)/pkdecrypt.c'; fi`
+
+gpg_agent-genkey.o: genkey.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-genkey.o -MD -MP -MF $(DEPDIR)/gpg_agent-genkey.Tpo -c -o gpg_agent-genkey.o `test -f 'genkey.c' || echo '$(srcdir)/'`genkey.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-genkey.Tpo $(DEPDIR)/gpg_agent-genkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='genkey.c' object='gpg_agent-genkey.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-genkey.o `test -f 'genkey.c' || echo '$(srcdir)/'`genkey.c
+
+gpg_agent-genkey.obj: genkey.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-genkey.obj -MD -MP -MF $(DEPDIR)/gpg_agent-genkey.Tpo -c -o gpg_agent-genkey.obj `if test -f 'genkey.c'; then $(CYGPATH_W) 'genkey.c'; else $(CYGPATH_W) '$(srcdir)/genkey.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-genkey.Tpo $(DEPDIR)/gpg_agent-genkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='genkey.c' object='gpg_agent-genkey.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-genkey.obj `if test -f 'genkey.c'; then $(CYGPATH_W) 'genkey.c'; else $(CYGPATH_W) '$(srcdir)/genkey.c'; fi`
+
+gpg_agent-protect.o: protect.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-protect.o -MD -MP -MF $(DEPDIR)/gpg_agent-protect.Tpo -c -o gpg_agent-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-protect.Tpo $(DEPDIR)/gpg_agent-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_agent-protect.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c
+
+gpg_agent-protect.obj: protect.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-protect.obj -MD -MP -MF $(DEPDIR)/gpg_agent-protect.Tpo -c -o gpg_agent-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-protect.Tpo $(DEPDIR)/gpg_agent-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_agent-protect.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi`
+
+gpg_agent-trustlist.o: trustlist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trustlist.o -MD -MP -MF $(DEPDIR)/gpg_agent-trustlist.Tpo -c -o gpg_agent-trustlist.o `test -f 'trustlist.c' || echo '$(srcdir)/'`trustlist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trustlist.Tpo $(DEPDIR)/gpg_agent-trustlist.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trustlist.c' object='gpg_agent-trustlist.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trustlist.o `test -f 'trustlist.c' || echo '$(srcdir)/'`trustlist.c
+
+gpg_agent-trustlist.obj: trustlist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trustlist.obj -MD -MP -MF $(DEPDIR)/gpg_agent-trustlist.Tpo -c -o gpg_agent-trustlist.obj `if test -f 'trustlist.c'; then $(CYGPATH_W) 'trustlist.c'; else $(CYGPATH_W) '$(srcdir)/trustlist.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trustlist.Tpo $(DEPDIR)/gpg_agent-trustlist.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trustlist.c' object='gpg_agent-trustlist.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trustlist.obj `if test -f 'trustlist.c'; then $(CYGPATH_W) 'trustlist.c'; else $(CYGPATH_W) '$(srcdir)/trustlist.c'; fi`
+
+gpg_agent-divert-scd.o: divert-scd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-divert-scd.o -MD -MP -MF $(DEPDIR)/gpg_agent-divert-scd.Tpo -c -o gpg_agent-divert-scd.o `test -f 'divert-scd.c' || echo '$(srcdir)/'`divert-scd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-divert-scd.Tpo $(DEPDIR)/gpg_agent-divert-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='divert-scd.c' object='gpg_agent-divert-scd.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-divert-scd.o `test -f 'divert-scd.c' || echo '$(srcdir)/'`divert-scd.c
+
+gpg_agent-divert-scd.obj: divert-scd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-divert-scd.obj -MD -MP -MF $(DEPDIR)/gpg_agent-divert-scd.Tpo -c -o gpg_agent-divert-scd.obj `if test -f 'divert-scd.c'; then $(CYGPATH_W) 'divert-scd.c'; else $(CYGPATH_W) '$(srcdir)/divert-scd.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-divert-scd.Tpo $(DEPDIR)/gpg_agent-divert-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='divert-scd.c' object='gpg_agent-divert-scd.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-divert-scd.obj `if test -f 'divert-scd.c'; then $(CYGPATH_W) 'divert-scd.c'; else $(CYGPATH_W) '$(srcdir)/divert-scd.c'; fi`
+
+gpg_agent-cvt-openpgp.o: cvt-openpgp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cvt-openpgp.o -MD -MP -MF $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo -c -o gpg_agent-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo $(DEPDIR)/gpg_agent-cvt-openpgp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_agent-cvt-openpgp.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c
+
+gpg_agent-cvt-openpgp.obj: cvt-openpgp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cvt-openpgp.obj -MD -MP -MF $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo -c -o gpg_agent-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo $(DEPDIR)/gpg_agent-cvt-openpgp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_agent-cvt-openpgp.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi`
+
+gpg_agent-call-scd.o: call-scd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-scd.o -MD -MP -MF $(DEPDIR)/gpg_agent-call-scd.Tpo -c -o gpg_agent-call-scd.o `test -f 'call-scd.c' || echo '$(srcdir)/'`call-scd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-scd.Tpo $(DEPDIR)/gpg_agent-call-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-scd.c' object='gpg_agent-call-scd.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-scd.o `test -f 'call-scd.c' || echo '$(srcdir)/'`call-scd.c
+
+gpg_agent-call-scd.obj: call-scd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-scd.obj -MD -MP -MF $(DEPDIR)/gpg_agent-call-scd.Tpo -c -o gpg_agent-call-scd.obj `if test -f 'call-scd.c'; then $(CYGPATH_W) 'call-scd.c'; else $(CYGPATH_W) '$(srcdir)/call-scd.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-scd.Tpo $(DEPDIR)/gpg_agent-call-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-scd.c' object='gpg_agent-call-scd.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-scd.obj `if test -f 'call-scd.c'; then $(CYGPATH_W) 'call-scd.c'; else $(CYGPATH_W) '$(srcdir)/call-scd.c'; fi`
+
+gpg_agent-learncard.o: learncard.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-learncard.o -MD -MP -MF $(DEPDIR)/gpg_agent-learncard.Tpo -c -o gpg_agent-learncard.o `test -f 'learncard.c' || echo '$(srcdir)/'`learncard.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-learncard.Tpo $(DEPDIR)/gpg_agent-learncard.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='learncard.c' object='gpg_agent-learncard.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-learncard.o `test -f 'learncard.c' || echo '$(srcdir)/'`learncard.c
+
+gpg_agent-learncard.obj: learncard.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-learncard.obj -MD -MP -MF $(DEPDIR)/gpg_agent-learncard.Tpo -c -o gpg_agent-learncard.obj `if test -f 'learncard.c'; then $(CYGPATH_W) 'learncard.c'; else $(CYGPATH_W) '$(srcdir)/learncard.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-learncard.Tpo $(DEPDIR)/gpg_agent-learncard.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='learncard.c' object='gpg_agent-learncard.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-learncard.obj `if test -f 'learncard.c'; then $(CYGPATH_W) 'learncard.c'; else $(CYGPATH_W) '$(srcdir)/learncard.c'; fi`
+
+gpg_protect_tool-protect-tool.o: protect-tool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect-tool.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo -c -o gpg_protect_tool-protect-tool.o `test -f 'protect-tool.c' || echo '$(srcdir)/'`protect-tool.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo $(DEPDIR)/gpg_protect_tool-protect-tool.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect-tool.c' object='gpg_protect_tool-protect-tool.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect-tool.o `test -f 'protect-tool.c' || echo '$(srcdir)/'`protect-tool.c
+
+gpg_protect_tool-protect-tool.obj: protect-tool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect-tool.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo -c -o gpg_protect_tool-protect-tool.obj `if test -f 'protect-tool.c'; then $(CYGPATH_W) 'protect-tool.c'; else $(CYGPATH_W) '$(srcdir)/protect-tool.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo $(DEPDIR)/gpg_protect_tool-protect-tool.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect-tool.c' object='gpg_protect_tool-protect-tool.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect-tool.obj `if test -f 'protect-tool.c'; then $(CYGPATH_W) 'protect-tool.c'; else $(CYGPATH_W) '$(srcdir)/protect-tool.c'; fi`
+
+gpg_protect_tool-protect.o: protect.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect.Tpo -c -o gpg_protect_tool-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect.Tpo $(DEPDIR)/gpg_protect_tool-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_protect_tool-protect.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c
+
+gpg_protect_tool-protect.obj: protect.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect.Tpo -c -o gpg_protect_tool-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect.Tpo $(DEPDIR)/gpg_protect_tool-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_protect_tool-protect.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi`
+
+gpg_protect_tool-cvt-openpgp.o: cvt-openpgp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-cvt-openpgp.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo -c -o gpg_protect_tool-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_protect_tool-cvt-openpgp.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c
+
+gpg_protect_tool-cvt-openpgp.obj: cvt-openpgp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-cvt-openpgp.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo -c -o gpg_protect_tool-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_protect_tool-cvt-openpgp.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi`
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libexecPROGRAMS \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/gpg_agent-cache.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-call-pinentry.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-call-scd.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-command-ssh.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-command.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-cvt-openpgp.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-divert-scd.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-findkey.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-genkey.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-gpg-agent.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-learncard.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-pkdecrypt.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-pksign.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-protect.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-trans.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-trustlist.Po
+ -rm -f ./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po
+ -rm -f ./$(DEPDIR)/gpg_protect_tool-protect-tool.Po
+ -rm -f ./$(DEPDIR)/gpg_protect_tool-protect.Po
+ -rm -f ./$(DEPDIR)/preset-passphrase.Po
+ -rm -f ./$(DEPDIR)/protect.Po
+ -rm -f ./$(DEPDIR)/t-protect.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS install-libexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/gpg_agent-cache.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-call-pinentry.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-call-scd.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-command-ssh.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-command.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-cvt-openpgp.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-divert-scd.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-findkey.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-genkey.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-gpg-agent.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-learncard.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-pkdecrypt.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-pksign.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-protect.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-trans.Po
+ -rm -f ./$(DEPDIR)/gpg_agent-trustlist.Po
+ -rm -f ./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po
+ -rm -f ./$(DEPDIR)/gpg_protect_tool-protect-tool.Po
+ -rm -f ./$(DEPDIR)/gpg_protect_tool-protect.Po
+ -rm -f ./$(DEPDIR)/preset-passphrase.Po
+ -rm -f ./$(DEPDIR)/protect.Po
+ -rm -f ./$(DEPDIR)/t-protect.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-libexecPROGRAMS
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \
+ check-am clean clean-binPROGRAMS clean-generic \
+ clean-libexecPROGRAMS clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libexecPROGRAMS install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-libexecPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+@HAVE_W32_SYSTEM_TRUE@.rc.o:
+@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@"
+@HAVE_W32_SYSTEM_TRUE@gpg-agent-w32info.o : gpg-agent.w32-manifest
+
+# Make sure that all libs are build before we use them. This is
+# important for things like make -j2.
+$(PROGRAMS): $(common_libs) $(commonpth_libs) $(pwquery_libs)
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/agent/agent.h b/agent/agent.h
new file mode 100644
index 0000000..56e13ec
--- /dev/null
+++ b/agent/agent.h
@@ -0,0 +1,650 @@
+/* agent.h - Global definitions for the agent
+ * Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2015 g10 Code GmbH.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef AGENT_H
+#define AGENT_H
+
+#ifdef GPG_ERR_SOURCE_DEFAULT
+#error GPG_ERR_SOURCE_DEFAULT already defined
+#endif
+#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT
+#include <gpg-error.h>
+#define map_assuan_err(a) \
+ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
+#include <errno.h>
+
+#include <gcrypt.h>
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/sysutils.h" /* (gnupg_fd_t) */
+#include "../common/session-env.h"
+#include "../common/shareddefs.h"
+#include "../common/name-value.h"
+
+/* To convey some special hash algorithms we use algorithm numbers
+ reserved for application use. */
+#ifndef GCRY_MODULE_ID_USER
+#define GCRY_MODULE_ID_USER 1024
+#endif
+#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1)
+
+/* Maximum length of a digest. */
+#define MAX_DIGEST_LEN 64
+
+/* The maximum length of a passphrase (in bytes). Note: this is
+ further contrained by the Assuan line length (and any other text on
+ the same line). However, the Assuan line length is 1k bytes so
+ this shouldn't be a problem in practice. */
+#define MAX_PASSPHRASE_LEN 255
+
+
+/* A large struct name "opt" to keep global flags */
+EXTERN_UNLESS_MAIN_MODULE
+struct
+{
+ unsigned int debug; /* Debug flags (DBG_foo_VALUE) */
+ int verbose; /* Verbosity level */
+ int quiet; /* Be as quiet as possible */
+ int dry_run; /* Don't change any persistent data */
+ int batch; /* Batch mode */
+
+ /* True if we handle sigusr2. */
+ int sigusr2_enabled;
+
+ /* Environment settings gathered at program start or changed using the
+ Assuan command UPDATESTARTUPTTY. */
+ session_env_t startup_env;
+ char *startup_lc_ctype;
+ char *startup_lc_messages;
+
+ /* Enable pinentry debugging (--debug 1024 should also be used). */
+ int debug_pinentry;
+
+ /* Filename of the program to start as pinentry. */
+ const char *pinentry_program;
+
+ /* Filename of the program to handle smartcard tasks. */
+ const char *scdaemon_program;
+
+ int disable_scdaemon; /* Never use the SCdaemon. */
+
+ int no_grab; /* Don't let the pinentry grab the keyboard */
+
+ /* The name of the file pinentry shall touch before exiting. If
+ this is not set the file name of the standard socket is used. */
+ const char *pinentry_touch_file;
+
+ /* A string where the first character is used by the pinentry as a
+ custom invisible character. */
+ char *pinentry_invisible_char;
+
+ /* The timeout value for the Pinentry in seconds. This is passed to
+ the pinentry if it is not 0. It is up to the pinentry to act
+ upon this timeout value. */
+ unsigned long pinentry_timeout;
+
+ /* If set, then passphrase formatting is enabled in pinentry. */
+ int pinentry_formatted_passphrase;
+
+ /* The default and maximum TTL of cache entries. */
+ unsigned long def_cache_ttl; /* Default. */
+ unsigned long def_cache_ttl_ssh; /* for SSH. */
+ unsigned long max_cache_ttl; /* Default. */
+ unsigned long max_cache_ttl_ssh; /* for SSH. */
+
+ /* Flag disallowing bypassing of the warning. */
+ int enforce_passphrase_constraints;
+
+ /* The require minmum length of a passphrase. */
+ unsigned int min_passphrase_len;
+
+ /* The minimum number of non-alpha characters in a passphrase. */
+ unsigned int min_passphrase_nonalpha;
+
+ /* File name with a patternfile or NULL if not enabled. If the
+ * second one is set, it is used for symmetric only encryption
+ * instead of the former. */
+ const char *check_passphrase_pattern;
+ const char *check_sym_passphrase_pattern;
+
+ /* If not 0 the user is asked to change his passphrase after these
+ number of days. */
+ unsigned int max_passphrase_days;
+
+ /* If set, a passphrase history will be written and checked at each
+ passphrase change. */
+ int enable_passphrase_history;
+
+ /* If set the extended key format is used for new keys. Note that
+ * this may have the value 2 in which case
+ * --disable-extended-key-format won't have any effect and thus
+ * effectivley locking it. This is required to support existing
+ * profiles which lock the use of --enable-extended-key-format. */
+ int enable_extended_key_format;
+
+ int running_detached; /* We are running detached from the tty. */
+
+ /* If this global option is true, the passphrase cache is ignored
+ for signing operations. */
+ int ignore_cache_for_signing;
+
+ /* If this global option is true, the user is allowed to
+ interactively mark certificate in trustlist.txt as trusted. */
+ int allow_mark_trusted;
+
+ /* Only use the system trustlist. */
+ int no_user_trustlist;
+
+ /* The standard system trustlist is SYSCONFDIR/trustlist.txt. This
+ * option can be used to change the name. */
+ const char *sys_trustlist_name;
+
+ /* If this global option is true, the Assuan command
+ PRESET_PASSPHRASE is allowed. */
+ int allow_preset_passphrase;
+
+ /* If this global option is true, the Assuan option
+ pinentry-mode=loopback is allowed. */
+ int allow_loopback_pinentry;
+
+ /* Allow the use of an external password cache. If this option is
+ enabled (which is the default) we send an option to Pinentry
+ to allow it to enable such a cache. */
+ int allow_external_cache;
+
+ /* If this global option is true, the Assuan option of Pinentry
+ allow-emacs-prompt is allowed. */
+ int allow_emacs_pinentry;
+
+ int keep_tty; /* Don't switch the TTY (for pinentry) on request */
+ int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */
+
+ /* This global option indicates the use of an extra socket. Note
+ that we use a hack for cleanup handling in gpg-agent.c: If the
+ value is less than 2 the name has not yet been malloced. */
+ int extra_socket;
+
+ /* This global option indicates the use of an extra socket for web
+ browsers. Note that we use a hack for cleanup handling in
+ gpg-agent.c: If the value is less than 2 the name has not yet
+ been malloced. */
+ int browser_socket;
+
+ /* The digest algorithm to use for ssh fingerprints when
+ * communicating with the user. */
+ int ssh_fingerprint_digest;
+
+ /* The value of the option --s2k-count. If this option is not given
+ * or 0 an auto-calibrated value is used. */
+ unsigned long s2k_count;
+} opt;
+
+
+/* Bit values for the --debug option. */
+#define DBG_MPI_VALUE 2 /* debug mpi details */
+#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
+#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
+#define DBG_CACHE_VALUE 64 /* debug the caching */
+#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
+#define DBG_HASHING_VALUE 512 /* debug hashing operations */
+#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
+
+/* Test macros for the debug option. */
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
+#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
+#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
+#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
+
+/* Forward reference for local definitions in command.c. */
+struct server_local_s;
+
+/* Declaration of objects from command-ssh.c. */
+struct ssh_control_file_s;
+typedef struct ssh_control_file_s *ssh_control_file_t;
+
+/* Forward reference for local definitions in call-scd.c. */
+struct scd_local_s;
+
+/* Collection of data per session (aka connection). */
+struct server_control_s
+{
+ /* Private data used to fire up the connection thread. We use this
+ structure do avoid an extra allocation for only a few bytes while
+ spawning a new connection thread. */
+ struct {
+ gnupg_fd_t fd;
+ } thread_startup;
+
+ /* Flag indicating the connection is run in restricted mode.
+ A value of 1 if used for --extra-socket,
+ a value of 2 is used for --browser-socket. */
+ int restricted;
+
+ /* Private data of the server (command.c). */
+ struct server_local_s *server_local;
+
+ /* Private data of the SCdaemon (call-scd.c). */
+ struct scd_local_s *scd_local;
+
+ /* Environment settings for the connection. */
+ session_env_t session_env;
+ char *lc_ctype;
+ char *lc_messages;
+ unsigned long client_pid;
+
+ /* The current pinentry mode. */
+ pinentry_mode_t pinentry_mode;
+
+ /* The TTL used for the --preset option of certain commands. */
+ int cache_ttl_opt_preset;
+
+ /* Information on the currently used digest (for signing commands). */
+ struct {
+ int algo;
+ unsigned char value[MAX_DIGEST_LEN];
+ int valuelen;
+ int raw_value: 1;
+ } digest;
+ unsigned char keygrip[20];
+ int have_keygrip;
+
+ /* A flag to enable a hack to send the PKAUTH command instead of the
+ PKSIGN command to the scdaemon. */
+ int use_auth_call;
+
+ /* A flag to inhibit enforced passphrase change during an explicit
+ passwd command. */
+ int in_passwd;
+
+ /* The current S2K which might be different from the calibrated
+ count. */
+ unsigned long s2k_count;
+
+ /* If pinentry is active for this thread. It can be more than 1,
+ when pinentry is called recursively. */
+ int pinentry_active;
+};
+
+
+/* Status of pinentry. */
+enum
+ {
+ PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0,
+ PINENTRY_STATUS_PIN_REPEATED = 1 << 8,
+ PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9,
+ PINENTRY_STATUS_PASSWORD_GENERATED = 1 << 10
+ };
+
+/* Information pertaining to pinentry requests. */
+struct pin_entry_info_s
+{
+ int min_digits; /* min. number of digits required or 0 for freeform entry */
+ int max_digits; /* max. number of allowed digits allowed*/
+ int max_tries; /* max. number of allowed tries. */
+ unsigned int constraints_flags; /* CHECK_CONSTRAINTS_... */
+ int failed_tries; /* Number of tries so far failed. */
+ int with_qualitybar; /* Set if the quality bar should be displayed. */
+ int with_repeat; /* Request repetition of the passphrase. */
+ int repeat_okay; /* Repetition worked. */
+ unsigned int status; /* Status. */
+ gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check
+ the PIN */
+ void *check_cb_arg; /* optional argument which might be of use in the CB */
+ const char *cb_errtext; /* used by the cb to display a specific error */
+ size_t max_length; /* Allocated length of the buffer PIN. */
+ char pin[1]; /* The buffer to hold the PIN or passphrase.
+ It's actual allocated length is given by
+ MAX_LENGTH (above). */
+};
+
+
+/* Types of the private keys. */
+enum
+ {
+ PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */
+ PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */
+ PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */
+ PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard
+ based key. */
+ PROTECTED_SHARED_SECRET = 4, /* RFU. */
+ PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */
+ };
+
+
+/* Values for the cache_mode arguments. */
+typedef enum
+ {
+ CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */
+ CACHE_MODE_ANY, /* Any mode except ignore matches. */
+ CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */
+ CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */
+ CACHE_MODE_SSH, /* SSH related cache. */
+ CACHE_MODE_NONCE /* This is a non-predictable nonce. */
+ }
+cache_mode_t;
+
+/* The TTL is seconds used for adding a new nonce mode cache item. */
+#define CACHE_TTL_NONCE 120
+
+/* The TTL in seconds used by the --preset option of some commands.
+ This is the default value changeable by an OPTION command. */
+#define CACHE_TTL_OPT_PRESET 900
+
+
+/* The type of a function to lookup a TTL by a keygrip. */
+typedef int (*lookup_ttl_t)(const char *hexgrip);
+
+
+/* This is a special version of the usual _() gettext macro. It
+ assumes a server connection control variable with the name "ctrl"
+ and uses that to translate a string according to the locale set for
+ the connection. The macro LunderscoreIMPL is used by i18n to
+ actually define the inline function when needed. */
+#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
+#define L_(a) agent_Lunderscore (ctrl, (a))
+#define LunderscorePROTO \
+ static inline const char *agent_Lunderscore (ctrl_t ctrl, \
+ const char *string) \
+ GNUPG_GCC_ATTR_FORMAT_ARG(2);
+#define LunderscoreIMPL \
+ static inline const char * \
+ agent_Lunderscore (ctrl_t ctrl, const char *string) \
+ { \
+ return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
+ /* */: gettext (string); \
+ }
+#else
+#define L_(a) (a)
+#endif
+
+
+/*-- gpg-agent.c --*/
+void agent_exit (int rc)
+ GPGRT_ATTR_NORETURN; /* Also implemented in other tools */
+void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
+ int printchar, int current, int total),
+ ctrl_t ctrl);
+gpg_error_t agent_copy_startup_env (ctrl_t ctrl);
+const char *get_agent_socket_name (void);
+const char *get_agent_ssh_socket_name (void);
+int get_agent_active_connection_count (void);
+#ifdef HAVE_W32_SYSTEM
+void *get_agent_scd_notify_event (void);
+#endif
+void agent_sighup_action (void);
+int map_pk_openpgp_to_gcry (int openpgp_algo);
+
+/*-- command.c --*/
+gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid,
+ const char *extra);
+gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...)
+ GPGRT_ATTR_SENTINEL(0);
+gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword,
+ const char *format, ...)
+ GPGRT_ATTR_PRINTF(3,4);
+void bump_key_eventcounter (void);
+void bump_card_eventcounter (void);
+void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
+gpg_error_t pinentry_loopback (ctrl_t, const char *keyword,
+ unsigned char **buffer, size_t *size,
+ size_t max_length);
+
+#ifdef HAVE_W32_SYSTEM
+int serve_mmapped_ssh_request (ctrl_t ctrl,
+ unsigned char *request, size_t maxreqlen);
+#endif /*HAVE_W32_SYSTEM*/
+
+/*-- command-ssh.c --*/
+ssh_control_file_t ssh_open_control_file (void);
+void ssh_close_control_file (ssh_control_file_t cf);
+gpg_error_t ssh_read_control_file (ssh_control_file_t cf,
+ char *r_hexgrip, int *r_disabled,
+ int *r_ttl, int *r_confirm);
+gpg_error_t ssh_search_control_file (ssh_control_file_t cf,
+ const char *hexgrip,
+ int *r_disabled,
+ int *r_ttl, int *r_confirm);
+
+void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
+
+/*-- findkey.c --*/
+gpg_error_t agent_modify_description (const char *in, const char *comment,
+ const gcry_sexp_t key, char **result);
+int agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force,
+ time_t timestamp,
+ const char *serialno, const char *keyref,
+ const char *dispserialno);
+gpg_error_t agent_key_from_file (ctrl_t ctrl,
+ const char *cache_nonce,
+ const char *desc_text,
+ const unsigned char *grip,
+ unsigned char **shadow_info,
+ cache_mode_t cache_mode,
+ lookup_ttl_t lookup_ttl,
+ gcry_sexp_t *result,
+ char **r_passphrase);
+gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+ gcry_sexp_t *result);
+gpg_error_t agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip,
+ nvc_t *r_keymeta);
+gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result);
+int agent_is_dsa_key (gcry_sexp_t s_key);
+int agent_is_eddsa_key (gcry_sexp_t s_key);
+int agent_key_available (const unsigned char *grip);
+gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
+ int *r_keytype,
+ unsigned char **r_shadow_info);
+gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *grip,
+ int force, int only_stubs);
+
+/*-- call-pinentry.c --*/
+void initialize_module_call_pinentry (void);
+void agent_query_dump_state (void);
+void agent_reset_query (ctrl_t ctrl);
+int pinentry_active_p (ctrl_t ctrl, int waitseconds);
+gpg_error_t agent_askpin (ctrl_t ctrl,
+ const char *desc_text, const char *prompt_text,
+ const char *inital_errtext,
+ struct pin_entry_info_s *pininfo,
+ const char *keyinfo, cache_mode_t cache_mode);
+int agent_get_passphrase (ctrl_t ctrl, char **retpass,
+ const char *desc, const char *prompt,
+ const char *errtext, int with_qualitybar,
+ const char *keyinfo, cache_mode_t cache_mode,
+ struct pin_entry_info_s *pininfo);
+int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
+ const char *notokay, int with_cancel);
+int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
+int agent_popup_message_start (ctrl_t ctrl,
+ const char *desc, const char *ok_btn);
+void agent_popup_message_stop (ctrl_t ctrl);
+int agent_clear_passphrase (ctrl_t ctrl,
+ const char *keyinfo, cache_mode_t cache_mode);
+
+/*-- cache.c --*/
+void initialize_module_cache (void);
+void deinitialize_module_cache (void);
+void agent_cache_housekeeping (void);
+void agent_flush_cache (void);
+int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
+ const char *data, int ttl);
+char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode);
+void agent_store_cache_hit (const char *key);
+
+
+/*-- pksign.c --*/
+gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
+ const char *desc_text,
+ gcry_sexp_t *signature_sexp,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
+ const void *overridedata, size_t overridedatalen);
+gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
+ const char *desc_text,
+ membuf_t *outbuf, cache_mode_t cache_mode);
+
+/*-- pkdecrypt.c --*/
+int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *ciphertext, size_t ciphertextlen,
+ membuf_t *outbuf, int *r_padding);
+
+/*-- genkey.c --*/
+#define CHECK_CONSTRAINTS_NOT_EMPTY 1
+#define CHECK_CONSTRAINTS_NEW_SYMKEY 2
+
+int check_passphrase_constraints (ctrl_t ctrl, const char *pw,
+ unsigned int flags,
+ char **failed_constraint);
+gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
+ char **r_passphrase);
+int agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp,
+ const char *keyparam, size_t keyparmlen,
+ int no_protection, const char *override_passphrase,
+ int preset, membuf_t *outbuf);
+gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
+ char **passphrase_addr);
+
+/*-- protect.c --*/
+void set_s2k_calibration_time (unsigned int milliseconds);
+unsigned long get_calibrated_s2k_count (void);
+unsigned long get_standard_s2k_count (void);
+unsigned char get_standard_s2k_count_rfc4880 (void);
+unsigned long get_standard_s2k_time (void);
+int agent_protect (const unsigned char *plainkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen,
+ unsigned long s2k_count, int use_ocb);
+gpg_error_t agent_unprotect (ctrl_t ctrl,
+ const unsigned char *protectedkey, const char *passphrase,
+ gnupg_isotime_t protected_at,
+ unsigned char **result, size_t *resultlen);
+int agent_private_key_type (const unsigned char *privatekey);
+unsigned char *make_shadow_info (const char *serialno, const char *idstring);
+int agent_shadow_key (const unsigned char *pubkey,
+ const unsigned char *shadow_info,
+ unsigned char **result);
+gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey,
+ unsigned char const **shadow_info);
+gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
+ char **r_hexsn, char **r_idstr, int *r_pinlen);
+gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt,
+ unsigned int s2kcount,
+ unsigned char *key, size_t keylen);
+gpg_error_t agent_write_shadow_key (int maybe_update,
+ const unsigned char *grip,
+ const char *serialno, const char *keyid,
+ const unsigned char *pkbuf, int force,
+ const char *dispserialno);
+
+
+/*-- trustlist.c --*/
+void initialize_module_trustlist (void);
+gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled);
+gpg_error_t agent_listtrusted (void *assuan_context);
+gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
+ const char *fpr, int flag);
+void agent_reload_trustlist (void);
+
+
+/*-- divert-scd.c --*/
+int divert_pksign (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *digest, size_t digestlen, int algo,
+ const unsigned char *grip,
+ const unsigned char *shadow_info, unsigned char **r_sig,
+ size_t *r_siglen);
+int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *cipher,
+ const unsigned char *grip,
+ const unsigned char *shadow_info,
+ char **r_buf, size_t *r_len, int *r_padding);
+int divert_generic_cmd (ctrl_t ctrl,
+ const char *cmdline, void *assuan_context);
+int divert_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata, size_t keydatalen);
+
+
+/*-- call-scd.c --*/
+void initialize_module_call_scd (void);
+void agent_scd_dump_state (void);
+int agent_scd_check_running (void);
+void agent_scd_check_aliveness (void);
+int agent_reset_scd (ctrl_t ctrl);
+int agent_card_learn (ctrl_t ctrl,
+ void (*kpinfo_cb)(void*, const char *),
+ void *kpinfo_cb_arg,
+ void (*certinfo_cb)(void*, const char *),
+ void *certinfo_cb_arg,
+ void (*sinfo_cb)(void*, const char *,
+ size_t, const char *),
+ void *sinfo_cb_arg);
+int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand);
+int agent_card_pksign (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const char *desc_text,
+ int mdalgo,
+ const unsigned char *indata, size_t indatalen,
+ unsigned char **r_buf, size_t *r_buflen);
+int agent_card_pkdecrypt (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*,size_t),
+ void *getpin_cb_arg,
+ const char *desc_text,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen, int *r_padding);
+int agent_card_readcert (ctrl_t ctrl,
+ const char *id, char **r_buf, size_t *r_buflen);
+int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf);
+int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata,
+ size_t keydatalen,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg);
+gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result);
+gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result);
+int agent_card_scd (ctrl_t ctrl, const char *cmdline,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg, void *assuan_context);
+void agent_card_killscd (void);
+
+
+/*-- learncard.c --*/
+int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force);
+
+
+/*-- cvt-openpgp.c --*/
+gpg_error_t
+extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
+ const char **r_algoname, int *r_npkey, int *r_nskey,
+ const char **r_format,
+ gcry_mpi_t *mpi_array, int arraysize,
+ gcry_sexp_t *r_curve, gcry_sexp_t *r_flags);
+
+#endif /*AGENT_H*/
diff --git a/agent/all-tests.scm b/agent/all-tests.scm
new file mode 100644
index 0000000..6449ebb
--- /dev/null
+++ b/agent/all-tests.scm
@@ -0,0 +1,35 @@
+;; Copyright (C) 2017 g10 Code GmbH
+;;
+;; This file is part of GnuPG.
+;;
+;; GnuPG 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.
+;;
+;; GnuPG 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 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 <http://www.gnu.org/licenses/>.
+
+(export all-tests
+ ;; Parse the Makefile.am to find all tests.
+
+ (load (with-path "makefile.scm"))
+
+ (define (expander filename port key)
+ (parse-makefile port key))
+
+ (define (parse filename key)
+ (parse-makefile-expand filename expander key))
+
+ (map (lambda (name)
+ (test::binary #f
+ (path-join "agent" name)
+ (path-join (getenv "objdir") "agent" name)))
+ (parse-makefile-expand (in-srcdir "agent" "Makefile.am")
+ (lambda (filename port key) (parse-makefile port key))
+ "TESTS")))
diff --git a/agent/cache.c b/agent/cache.c
new file mode 100644
index 0000000..238b6e2
--- /dev/null
+++ b/agent/cache.c
@@ -0,0 +1,530 @@
+/* cache.c - keep a cache of passphrases
+ * Copyright (C) 2002, 2010 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <npth.h>
+
+#include "agent.h"
+
+/* The size of the encryption key in bytes. */
+#define ENCRYPTION_KEYSIZE (128/8)
+
+/* A mutex used to serialize access to the cache. */
+static npth_mutex_t cache_lock;
+/* The encryption context. This is the only place where the
+ encryption key for all cached entries is available. It would be nice
+ to keep this (or just the key) in some hardware device, for example
+ a TPM. Libgcrypt could be extended to provide such a service.
+ With the current scheme it is easy to retrieve the cached entries
+ if access to Libgcrypt's memory is available. The encryption
+ merely avoids grepping for clear texts in the memory. Nevertheless
+ the encryption provides the necessary infrastructure to make it
+ more secure. */
+static gcry_cipher_hd_t encryption_handle;
+
+
+struct secret_data_s {
+ int totallen; /* This includes the padding and space for AESWRAP. */
+ char data[1]; /* A string. */
+};
+
+typedef struct cache_item_s *ITEM;
+struct cache_item_s {
+ ITEM next;
+ time_t created;
+ time_t accessed;
+ int ttl; /* max. lifetime given in seconds, -1 one means infinite */
+ struct secret_data_s *pw;
+ cache_mode_t cache_mode;
+ int restricted; /* The value of ctrl->restricted is part of the key. */
+ char key[1];
+};
+
+/* The cache himself. */
+static ITEM thecache;
+
+/* NULL or the last cache key stored by agent_store_cache_hit. */
+static char *last_stored_cache_key;
+
+
+/* This function must be called once to initialize this module. It
+ has to be done before a second thread is spawned. */
+void
+initialize_module_cache (void)
+{
+ int err;
+
+ err = npth_mutex_init (&cache_lock, NULL);
+
+ if (err)
+ log_fatal ("error initializing cache module: %s\n", strerror (err));
+}
+
+
+void
+deinitialize_module_cache (void)
+{
+ gcry_cipher_close (encryption_handle);
+ encryption_handle = NULL;
+}
+
+
+/* We do the encryption init on the fly. We can't do it in the module
+ init code because that is run before we listen for connections and
+ in case we are started on demand by gpg etc. it will only wait for
+ a few seconds to decide whether the agent may now accept
+ connections. Thus we should get into listen state as soon as
+ possible. */
+static gpg_error_t
+init_encryption (void)
+{
+ gpg_error_t err;
+ void *key;
+
+ if (encryption_handle)
+ return 0; /* Shortcut - Already initialized. */
+
+ err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128,
+ GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE);
+ if (!err)
+ {
+ key = gcry_random_bytes (ENCRYPTION_KEYSIZE, GCRY_STRONG_RANDOM);
+ if (!key)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ err = gcry_cipher_setkey (encryption_handle, key, ENCRYPTION_KEYSIZE);
+ xfree (key);
+ }
+ if (err)
+ {
+ gcry_cipher_close (encryption_handle);
+ encryption_handle = NULL;
+ }
+ }
+ if (err)
+ log_error ("error initializing cache encryption context: %s\n",
+ gpg_strerror (err));
+
+ return err? gpg_error (GPG_ERR_NOT_INITIALIZED) : 0;
+}
+
+
+
+static void
+release_data (struct secret_data_s *data)
+{
+ xfree (data);
+}
+
+static gpg_error_t
+new_data (const char *string, struct secret_data_s **r_data)
+{
+ gpg_error_t err;
+ struct secret_data_s *d, *d_enc;
+ size_t length;
+ int total;
+
+ *r_data = NULL;
+
+ err = init_encryption ();
+ if (err)
+ return err;
+
+ length = strlen (string) + 1;
+
+ /* We pad the data to 32 bytes so that it get more complicated
+ finding something out by watching allocation patterns. This is
+ usually not possible but we better assume nothing about our secure
+ storage provider. To support the AESWRAP mode we need to add 8
+ extra bytes as well. */
+ total = (length + 8) + 32 - ((length+8) % 32);
+
+ d = xtrymalloc_secure (sizeof *d + total - 1);
+ if (!d)
+ return gpg_error_from_syserror ();
+ memcpy (d->data, string, length);
+
+ d_enc = xtrymalloc (sizeof *d_enc + total - 1);
+ if (!d_enc)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (d);
+ return err;
+ }
+
+ d_enc->totallen = total;
+ err = gcry_cipher_encrypt (encryption_handle, d_enc->data, total,
+ d->data, total - 8);
+ xfree (d);
+ if (err)
+ {
+ xfree (d_enc);
+ return err;
+ }
+ *r_data = d_enc;
+ return 0;
+}
+
+
+
+/* Check whether there are items to expire. */
+static void
+housekeeping (void)
+{
+ ITEM r, rprev;
+ time_t current = gnupg_get_time ();
+
+ /* First expire the actual data */
+ for (r=thecache; r; r = r->next)
+ {
+ if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current)
+ {
+ if (DBG_CACHE)
+ log_debug (" expired '%s'.%d (%ds after last access)\n",
+ r->key, r->restricted, r->ttl);
+ release_data (r->pw);
+ r->pw = NULL;
+ r->accessed = current;
+ }
+ }
+
+ /* Second, make sure that we also remove them based on the created stamp so
+ that the user has to enter it from time to time. */
+ for (r=thecache; r; r = r->next)
+ {
+ unsigned long maxttl;
+
+ switch (r->cache_mode)
+ {
+ case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break;
+ default: maxttl = opt.max_cache_ttl; break;
+ }
+ if (r->pw && r->created + maxttl < current)
+ {
+ if (DBG_CACHE)
+ log_debug (" expired '%s'.%d (%lus after creation)\n",
+ r->key, r->restricted, opt.max_cache_ttl);
+ release_data (r->pw);
+ r->pw = NULL;
+ r->accessed = current;
+ }
+ }
+
+ /* Third, make sure that we don't have too many items in the list.
+ * Expire old and unused entries after 30 minutes. */
+ for (rprev=NULL, r=thecache; r; )
+ {
+ if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current)
+ {
+ ITEM r2 = r->next;
+ if (DBG_CACHE)
+ log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n",
+ r->key, r->restricted, r->cache_mode);
+ xfree (r);
+ if (!rprev)
+ thecache = r2;
+ else
+ rprev->next = r2;
+ r = r2;
+ }
+ else
+ {
+ rprev = r;
+ r = r->next;
+ }
+ }
+}
+
+
+void
+agent_cache_housekeeping (void)
+{
+ int res;
+
+ if (DBG_CACHE)
+ log_debug ("agent_cache_housekeeping\n");
+
+ res = npth_mutex_lock (&cache_lock);
+ if (res)
+ log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
+
+ housekeeping ();
+
+ res = npth_mutex_unlock (&cache_lock);
+ if (res)
+ log_fatal ("failed to release cache mutex: %s\n", strerror (res));
+}
+
+
+void
+agent_flush_cache (void)
+{
+ ITEM r;
+ int res;
+
+ if (DBG_CACHE)
+ log_debug ("agent_flush_cache\n");
+
+ res = npth_mutex_lock (&cache_lock);
+ if (res)
+ log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
+
+ for (r=thecache; r; r = r->next)
+ {
+ if (r->pw)
+ {
+ if (DBG_CACHE)
+ log_debug (" flushing '%s'.%d\n", r->key, r->restricted);
+ release_data (r->pw);
+ r->pw = NULL;
+ r->accessed = 0;
+ }
+ }
+
+ res = npth_mutex_unlock (&cache_lock);
+ if (res)
+ log_fatal ("failed to release cache mutex: %s\n", strerror (res));
+}
+
+
+/* Compare two cache modes. */
+static int
+cache_mode_equal (cache_mode_t a, cache_mode_t b)
+{
+ /* CACHE_MODE_ANY matches any mode other than CACHE_MODE_IGNORE. */
+ return ((a == CACHE_MODE_ANY && b != CACHE_MODE_IGNORE)
+ || (b == CACHE_MODE_ANY && a != CACHE_MODE_IGNORE) || a == b);
+}
+
+
+/* Store the string DATA in the cache under KEY and mark it with a
+ maximum lifetime of TTL seconds. If there is already data under
+ this key, it will be replaced. Using a DATA of NULL deletes the
+ entry. A TTL of 0 is replaced by the default TTL and a TTL of -1
+ set infinite timeout. CACHE_MODE is stored with the cache entry
+ and used to select different timeouts. */
+int
+agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
+ const char *data, int ttl)
+{
+ gpg_error_t err = 0;
+ ITEM r;
+ int res;
+ int restricted = ctrl? ctrl->restricted : -1;
+
+ res = npth_mutex_lock (&cache_lock);
+ if (res)
+ log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
+
+ if (DBG_CACHE)
+ log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n",
+ key, restricted, cache_mode, ttl);
+ housekeeping ();
+
+ if (!ttl)
+ {
+ switch(cache_mode)
+ {
+ case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break;
+ default: ttl = opt.def_cache_ttl; break;
+ }
+ }
+ if ((!ttl && data) || cache_mode == CACHE_MODE_IGNORE)
+ goto out;
+
+ for (r=thecache; r; r = r->next)
+ {
+ if (((cache_mode != CACHE_MODE_USER
+ && cache_mode != CACHE_MODE_NONCE)
+ || cache_mode_equal (r->cache_mode, cache_mode))
+ && r->restricted == restricted
+ && !strcmp (r->key, key))
+ break;
+ }
+ if (r) /* Replace. */
+ {
+ if (r->pw)
+ {
+ release_data (r->pw);
+ r->pw = NULL;
+ }
+ if (data)
+ {
+ r->created = r->accessed = gnupg_get_time ();
+ r->ttl = ttl;
+ r->cache_mode = cache_mode;
+ err = new_data (data, &r->pw);
+ if (err)
+ log_error ("error replacing cache item: %s\n", gpg_strerror (err));
+ }
+ }
+ else if (data) /* Insert. */
+ {
+ r = xtrycalloc (1, sizeof *r + strlen (key));
+ if (!r)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ strcpy (r->key, key);
+ r->restricted = restricted;
+ r->created = r->accessed = gnupg_get_time ();
+ r->ttl = ttl;
+ r->cache_mode = cache_mode;
+ err = new_data (data, &r->pw);
+ if (err)
+ xfree (r);
+ else
+ {
+ r->next = thecache;
+ thecache = r;
+ }
+ }
+ if (err)
+ log_error ("error inserting cache item: %s\n", gpg_strerror (err));
+ }
+
+ out:
+ res = npth_mutex_unlock (&cache_lock);
+ if (res)
+ log_fatal ("failed to release cache mutex: %s\n", strerror (res));
+
+ return err;
+}
+
+
+/* Try to find an item in the cache. Note that we currently don't
+ make use of CACHE_MODE except for CACHE_MODE_NONCE and
+ CACHE_MODE_USER. */
+char *
+agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode)
+{
+ gpg_error_t err;
+ ITEM r;
+ char *value = NULL;
+ int res;
+ int last_stored = 0;
+ int restricted = ctrl? ctrl->restricted : -1;
+
+ if (cache_mode == CACHE_MODE_IGNORE)
+ return NULL;
+
+ res = npth_mutex_lock (&cache_lock);
+ if (res)
+ log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
+
+ if (!key)
+ {
+ key = last_stored_cache_key;
+ if (!key)
+ goto out;
+ last_stored = 1;
+ }
+
+ if (DBG_CACHE)
+ log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n",
+ key, ctrl->restricted, cache_mode,
+ last_stored? " (stored cache key)":"");
+ housekeeping ();
+
+ for (r=thecache; r; r = r->next)
+ {
+ if (r->pw
+ && ((cache_mode != CACHE_MODE_USER
+ && cache_mode != CACHE_MODE_NONCE)
+ || cache_mode_equal (r->cache_mode, cache_mode))
+ && r->restricted == restricted
+ && !strcmp (r->key, key))
+ {
+ /* Note: To avoid races KEY may not be accessed anymore below. */
+ r->accessed = gnupg_get_time ();
+ if (DBG_CACHE)
+ log_debug ("... hit\n");
+ if (r->pw->totallen < 32)
+ err = gpg_error (GPG_ERR_INV_LENGTH);
+ else if ((err = init_encryption ()))
+ ;
+ else if (!(value = xtrymalloc_secure (r->pw->totallen - 8)))
+ err = gpg_error_from_syserror ();
+ else
+ {
+ err = gcry_cipher_decrypt (encryption_handle,
+ value, r->pw->totallen - 8,
+ r->pw->data, r->pw->totallen);
+ }
+ if (err)
+ {
+ xfree (value);
+ value = NULL;
+ log_error ("retrieving cache entry '%s'.%d failed: %s\n",
+ key, restricted, gpg_strerror (err));
+ }
+ break;
+ }
+ }
+ if (DBG_CACHE && value == NULL)
+ log_debug ("... miss\n");
+
+ out:
+ res = npth_mutex_unlock (&cache_lock);
+ if (res)
+ log_fatal ("failed to release cache mutex: %s\n", strerror (res));
+
+ return value;
+}
+
+
+/* Store the key for the last successful cache hit. That value is
+ used by agent_get_cache if the requested KEY is given as NULL.
+ NULL may be used to remove that key. */
+void
+agent_store_cache_hit (const char *key)
+{
+ char *new;
+ char *old;
+
+ /* To make sure the update is atomic under the non-preemptive thread
+ * model, we must make sure not to surrender control to a different
+ * thread. Therefore, we avoid calling the allocator during the
+ * update.
+ *
+ * Background: xtrystrdup uses gcry_strdup which may use the secure
+ * memory allocator of Libgcrypt. That allocator takes locks and
+ * since version 1.14 libgpg-error is nPth aware and thus taking a
+ * lock may now lead to thread switch. Note that this only happens
+ * when secure memory is _allocated_ (the standard allocator uses
+ * malloc which is not nPth aware) but not when calling _xfree_
+ * because gcry_free needs to check whether the pointer is in secure
+ * memory and thus needs to take a lock.
+ */
+ new = key ? xtrystrdup (key) : NULL;
+
+ /* Atomic update. */
+ old = last_stored_cache_key;
+ last_stored_cache_key = new;
+ /* Done. */
+
+ xfree (old);
+}
diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c
new file mode 100644
index 0000000..5fcf98b
--- /dev/null
+++ b/agent/call-pinentry.c
@@ -0,0 +1,2091 @@
+/* call-pinentry.c - Spawn the pinentry to query stuff from the user
+ * Copyright (C) 2001, 2002, 2004, 2007, 2008,
+ * 2010 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#ifndef HAVE_W32_SYSTEM
+# include <sys/wait.h>
+# include <sys/types.h>
+# include <signal.h>
+# include <sys/utsname.h>
+#endif
+#include <npth.h>
+
+#include "agent.h"
+#include <assuan.h>
+#include "../common/sysutils.h"
+#include "../common/i18n.h"
+#include "../common/zb32.h"
+
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+
+/* Because access to the pinentry must be serialized (it is and shall
+ be a global mutually exclusive dialog) we better timeout pending
+ requests after some time. 1 minute seem to be a reasonable
+ time. */
+#define LOCK_TIMEOUT (1*60)
+
+/* Define the number of bits to use for a generated pin. The
+ * passphrase will be rendered as zbase32 which results for 150 bits
+ * in a string of 30 characters. That fits nicely into the 5
+ * character blocking which pinentry can do. 128 bits would actually
+ * be sufficient but can't be formatted nicely. */
+#define DEFAULT_GENPIN_BITS 150
+
+/* The assuan context of the current pinentry. */
+static assuan_context_t entry_ctx;
+
+/* A list of features of the current pinentry. */
+static struct
+{
+ /* The Pinentry support RS+US tabbing. This means that a RS (0x1e)
+ * starts a new tabbing block in which a US (0x1f) followed by a
+ * colon marks a colon. A pinentry can use this to pretty print
+ * name value pairs. */
+ unsigned int tabbing:1;
+} entry_features;
+
+
+/* A mutex used to serialize access to the pinentry. */
+static npth_mutex_t entry_lock;
+
+/* The thread ID of the popup working thread. */
+static npth_t popup_tid;
+
+/* A flag used in communication between the popup working thread and
+ its stop function. */
+static int popup_finished;
+
+
+
+/* Data to be passed to our callbacks, */
+struct entry_parm_s
+{
+ int lines;
+ size_t size;
+ unsigned char *buffer;
+ int status;
+ unsigned int constraints_flags;
+};
+
+
+
+
+/* This function must be called once to initialize this module. This
+ has to be done before a second thread is spawned. We can't do the
+ static initialization because Pth emulation code might not be able
+ to do a static init; in particular, it is not possible for W32. */
+void
+initialize_module_call_pinentry (void)
+{
+ static int initialized;
+ int err;
+
+ if (!initialized)
+ {
+ err = npth_mutex_init (&entry_lock, NULL);
+ if (err)
+ log_fatal ("error initializing mutex: %s\n", strerror (err));
+
+ initialized = 1;
+ }
+}
+
+
+
+/* This function may be called to print information pertaining to the
+ current state of this module to the log. */
+void
+agent_query_dump_state (void)
+{
+ log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n",
+ entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid);
+}
+
+/* Called to make sure that a popup window owned by the current
+ connection gets closed. */
+void
+agent_reset_query (ctrl_t ctrl)
+{
+ if (entry_ctx && popup_tid && ctrl->pinentry_active)
+ {
+ agent_popup_message_stop (ctrl);
+ }
+}
+
+
+/* Unlock the pinentry so that another thread can start one and
+ disconnect that pinentry - we do this after the unlock so that a
+ stalled pinentry does not block other threads. Fixme: We should
+ have a timeout in Assuan for the disconnect operation. */
+static gpg_error_t
+unlock_pinentry (ctrl_t ctrl, gpg_error_t rc)
+{
+ assuan_context_t ctx = entry_ctx;
+ int err;
+
+ if (rc)
+ {
+ if (DBG_IPC)
+ log_debug ("error calling pinentry: %s <%s>\n",
+ gpg_strerror (rc), gpg_strsource (rc));
+
+ /* Change the source of the error to pinentry so that the final
+ consumer of the error code knows that the problem is with
+ pinentry. For backward compatibility we do not do that for
+ some common error codes. */
+ switch (gpg_err_code (rc))
+ {
+ case GPG_ERR_NO_PIN_ENTRY:
+ case GPG_ERR_CANCELED:
+ case GPG_ERR_FULLY_CANCELED:
+ case GPG_ERR_ASS_UNKNOWN_INQUIRE:
+ case GPG_ERR_ASS_TOO_MUCH_DATA:
+ case GPG_ERR_NO_PASSPHRASE:
+ case GPG_ERR_BAD_PASSPHRASE:
+ case GPG_ERR_BAD_PIN:
+ break;
+
+ case GPG_ERR_CORRUPTED_PROTECTION:
+ /* This comes from gpg-agent. */
+ break;
+
+ default:
+ rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc));
+ break;
+ }
+ }
+
+ if (--ctrl->pinentry_active == 0)
+ {
+ entry_ctx = NULL;
+ err = npth_mutex_unlock (&entry_lock);
+ if (err)
+ {
+ log_error ("failed to release the entry lock: %s\n", strerror (err));
+ if (!rc)
+ rc = gpg_error_from_errno (err);
+ }
+ assuan_release (ctx);
+ }
+ return rc;
+}
+
+
+/* Helper for at_fork_cb which can also be called by the parent to
+ * show which envvars will be set. */
+static void
+atfork_core (ctrl_t ctrl, int debug_mode)
+{
+ int iterator = 0;
+ const char *name, *assname, *value;
+
+ while ((name = session_env_list_stdenvnames (&iterator, &assname)))
+ {
+ /* For all new envvars (!ASSNAME) and the two medium old ones
+ * which do have an assuan name but are conveyed using
+ * environment variables, update the environment of the forked
+ * process. We also pass DISPLAY despite that --display is also
+ * used when exec-ing the pinentry. The reason is that for
+ * example the qt5ct tool does not have any arguments and thus
+ * relies on the DISPLAY envvar. The use case here is a global
+ * envvar like "QT_QPA_PLATFORMTHEME=qt5ct" which for example is
+ * useful when using the Qt pinentry under GNOME or XFCE.
+ */
+ if (!assname
+ || (!opt.keep_display && !strcmp (name, "DISPLAY"))
+ || !strcmp (name, "XAUTHORITY")
+ || !strcmp (name, "PINENTRY_USER_DATA"))
+ {
+ value = session_env_getenv (ctrl->session_env, name);
+ if (value)
+ {
+ if (debug_mode)
+ log_debug ("pinentry: atfork used setenv(%s,%s)\n",name,value);
+ else
+ gnupg_setenv (name, value, 1);
+ }
+ }
+ }
+}
+
+
+/* To make sure we leave no secrets in our image after forking of the
+ pinentry, we use this callback. */
+static void
+atfork_cb (void *opaque, int where)
+{
+ ctrl_t ctrl = opaque;
+
+ if (!where)
+ {
+ gcry_control (GCRYCTL_TERM_SECMEM);
+ atfork_core (ctrl, 0);
+ }
+}
+
+
+/* Status line callback for the FEATURES status. */
+static gpg_error_t
+getinfo_features_cb (void *opaque, const char *line)
+{
+ const char *args;
+ char **tokens;
+ int i;
+
+ (void)opaque;
+
+ if ((args = has_leading_keyword (line, "FEATURES")))
+ {
+ tokens = strtokenize (args, " ");
+ if (!tokens)
+ return gpg_error_from_syserror ();
+ for (i=0; tokens[i]; i++)
+ if (!strcmp (tokens[i], "tabbing"))
+ entry_features.tabbing = 1;
+ xfree (tokens);
+ }
+
+ return 0;
+}
+
+
+static gpg_error_t
+getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
+{
+ unsigned long *pid = opaque;
+ char pidbuf[50];
+
+ /* There is only the pid in the server's response. */
+ if (length >= sizeof pidbuf)
+ length = sizeof pidbuf -1;
+ if (length)
+ {
+ strncpy (pidbuf, buffer, length);
+ pidbuf[length] = 0;
+ *pid = strtoul (pidbuf, NULL, 10);
+ }
+ return 0;
+}
+
+
+/* Fork off the pin entry if this has not already been done. Note,
+ that this function must always be used to acquire the lock for the
+ pinentry - we will serialize _all_ pinentry calls.
+ */
+static gpg_error_t
+start_pinentry (ctrl_t ctrl)
+{
+ int rc = 0;
+ const char *full_pgmname;
+ const char *pgmname;
+ assuan_context_t ctx;
+ const char *argv[5];
+ assuan_fd_t no_close_list[3];
+ int i;
+ const char *tmpstr;
+ unsigned long pinentry_pid;
+ const char *value;
+ struct timespec abstime;
+ char *flavor_version;
+ int err;
+
+ if (ctrl->pinentry_active)
+ {
+ /* It's trying to use pinentry recursively. In this situation,
+ the thread holds ENTRY_LOCK already. */
+ ctrl->pinentry_active++;
+ return 0;
+ }
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += LOCK_TIMEOUT;
+ err = npth_mutex_timedlock (&entry_lock, &abstime);
+ if (err)
+ {
+ if (err == ETIMEDOUT)
+ rc = gpg_error (GPG_ERR_TIMEOUT);
+ else
+ rc = gpg_error_from_errno (rc);
+ log_error (_("failed to acquire the pinentry lock: %s\n"),
+ gpg_strerror (rc));
+ return rc;
+ }
+
+ if (entry_ctx)
+ return 0;
+
+ if (opt.verbose)
+ log_info ("starting a new PIN Entry\n");
+
+#ifdef HAVE_W32_SYSTEM
+ fflush (stdout);
+ fflush (stderr);
+#endif
+ if (fflush (NULL))
+ {
+#ifndef HAVE_W32_SYSTEM
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+#endif
+ log_error ("error flushing pending output: %s\n", strerror (errno));
+ /* At least Windows XP fails here with EBADF. According to docs
+ and Wine an fflush(NULL) is the same as _flushall. However
+ the Wine implementation does not flush stdin,stdout and stderr
+ - see above. Let's try to ignore the error. */
+#ifndef HAVE_W32_SYSTEM
+ return unlock_pinentry (ctrl, tmperr);
+#endif
+ }
+
+ full_pgmname = opt.pinentry_program;
+ if (!full_pgmname || !*full_pgmname)
+ full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
+ if ( !(pgmname = strrchr (full_pgmname, '/')))
+ pgmname = full_pgmname;
+ else
+ pgmname++;
+
+ /* OS X needs the entire file name in argv[0], so that it can locate
+ the resource bundle. For other systems we stick to the usual
+ convention of supplying only the name of the program. */
+#ifdef __APPLE__
+ argv[0] = full_pgmname;
+#else /*!__APPLE__*/
+ argv[0] = pgmname;
+#endif /*__APPLE__*/
+
+ if (!opt.keep_display
+ && (value = session_env_getenv (ctrl->session_env, "DISPLAY")))
+ {
+ argv[1] = "--display";
+ argv[2] = value;
+ argv[3] = NULL;
+ }
+ else
+ argv[1] = NULL;
+
+ i=0;
+ if (!opt.running_detached)
+ {
+ if (log_get_fd () != -1)
+ no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
+ no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
+ }
+ no_close_list[i] = ASSUAN_INVALID_FD;
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ ctrl->pinentry_active = 1;
+ entry_ctx = ctx;
+
+ /* We don't want to log the pinentry communication to make the logs
+ easier to read. We might want to add a new debug option to enable
+ pinentry logging. */
+#ifdef ASSUAN_NO_LOGGING
+ assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry);
+#endif
+
+ /* Connect to the pinentry and perform initial handshaking. Note
+ that atfork is used to change the environment for pinentry. We
+ start the server in detached mode to suppress the console window
+ under Windows. */
+ rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv,
+ no_close_list, atfork_cb, ctrl,
+ ASSUAN_PIPE_CONNECT_DETACHED);
+ if (rc)
+ {
+ log_error ("can't connect to the PIN entry module '%s': %s\n",
+ full_pgmname, gpg_strerror (rc));
+ return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY));
+ }
+
+ if (DBG_IPC)
+ log_debug ("connection to PIN entry established\n");
+
+ if (opt.debug_pinentry)
+ atfork_core (ctrl, 1); /* Just show the envvars set after the fork. */
+
+ value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA");
+ if (value != NULL)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 )
+ return unlock_pinentry (ctrl, out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ rc = assuan_transact (entry_ctx,
+ opt.no_grab? "OPTION no-grab":"OPTION grab",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ value = session_env_getenv (ctrl->session_env, "GPG_TTY");
+ if (value)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 )
+ return unlock_pinentry (ctrl, out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+ value = session_env_getenv (ctrl->session_env, "TERM");
+ if (value && *value)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 )
+ return unlock_pinentry (ctrl, out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+ if (ctrl->lc_ctype)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 )
+ return unlock_pinentry (ctrl, out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+ if (ctrl->lc_messages)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 )
+ return unlock_pinentry (ctrl, out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+
+ if (opt.allow_external_cache)
+ {
+ /* Indicate to the pinentry that it may read from an external cache.
+
+ It is essential that the pinentry respect this. If the
+ cached password is not up to date and retry == 1, then, using
+ a version of GPG Agent that doesn't support this, won't issue
+ another pin request and the user won't get a chance to
+ correct the password. */
+ rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ if (opt.allow_emacs_pinentry)
+ {
+ /* Indicate to the pinentry that it may read passphrase through
+ Emacs minibuffer, if possible. */
+ rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+
+ {
+ /* Provide a few default strings for use by the pinentries. This
+ may help a pinentry to avoid implementing localization code. */
+ static const struct { const char *key, *value; int what; } tbl[] = {
+ /* TRANSLATORS: These are labels for buttons etc used in
+ Pinentries. An underscore indicates that the next letter
+ should be used as an accelerator. Double the underscore for
+ a literal one. The actual to be translated text starts after
+ the second vertical bar. Note that gpg-agent has been set to
+ utf-8 so that the strings are in the expected encoding. */
+ { "ok", N_("|pinentry-label|_OK") },
+ { "cancel", N_("|pinentry-label|_Cancel") },
+ { "yes", N_("|pinentry-label|_Yes") },
+ { "no", N_("|pinentry-label|_No") },
+ { "prompt", N_("|pinentry-label|PIN:") },
+ { "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 },
+ { "cf-visi",N_("Do you really want to make your "
+ "passphrase visible on the screen?") },
+ { "tt-visi",N_("|pinentry-tt|Make passphrase visible") },
+ { "tt-hide",N_("|pinentry-tt|Hide passphrase") },
+ { NULL, NULL}
+ };
+ char *optstr;
+ int idx;
+ const char *s, *s2;
+
+ for (idx=0; tbl[idx].key; idx++)
+ {
+ if (!opt.allow_external_cache && tbl[idx].what == 1)
+ continue; /* No need for it. */
+ s = L_(tbl[idx].value);
+ if (*s == '|' && (s2=strchr (s+1,'|')))
+ s = s2+1;
+ if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 )
+ return unlock_pinentry (ctrl, out_of_core ());
+ assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ }
+ }
+
+ /* Tell the pinentry that we would prefer that the given character
+ is used as the invisible character by the entry widget. */
+ if (opt.pinentry_invisible_char)
+ {
+ char *optstr;
+ if ((optstr = xtryasprintf ("OPTION invisible-char=%s",
+ opt.pinentry_invisible_char)))
+ {
+ assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ /* We ignore errors because this is just a fancy thing and
+ older pinentries do not support this feature. */
+ xfree (optstr);
+ }
+ }
+
+ if (opt.pinentry_timeout)
+ {
+ char *optstr;
+ if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout)))
+ {
+ assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ /* We ignore errors because this is just a fancy thing. */
+ xfree (optstr);
+ }
+ }
+
+ /* Tell the pinentry the name of a file it shall touch after having
+ messed with the tty. This is optional and only supported by
+ newer pinentries and thus we do no error checking. */
+ tmpstr = opt.pinentry_touch_file;
+ if (tmpstr && !strcmp (tmpstr, "/dev/null"))
+ tmpstr = NULL;
+ else if (!tmpstr)
+ tmpstr = get_agent_socket_name ();
+ if (tmpstr)
+ {
+ char *optstr;
+
+ if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 )
+ ;
+ else
+ {
+ assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ }
+ }
+
+ /* Tell Pinentry about our client. */
+ if (ctrl->client_pid)
+ {
+ char *optstr;
+ const char *nodename = "";
+
+#ifndef HAVE_W32_SYSTEM
+ struct utsname utsbuf;
+ if (!uname (&utsbuf))
+ nodename = utsbuf.nodename;
+#endif /*!HAVE_W32_SYSTEM*/
+
+ if ((optstr = xtryasprintf ("OPTION owner=%lu %s",
+ ctrl->client_pid, nodename)))
+ {
+ assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ /* We ignore errors because this is just a fancy thing and
+ older pinentries do not support this feature. */
+ xfree (optstr);
+ }
+ }
+
+
+ /* Ask the pinentry for its version and flavor and store that as a
+ * string in MB. This information is useful for helping users to
+ * figure out Pinentry problems. Note that "flavor" may also return
+ * a status line with the features; we use a dedicated handler for
+ * that. */
+ {
+ membuf_t mb;
+
+ init_membuf (&mb, 256);
+ if (assuan_transact (entry_ctx, "GETINFO flavor",
+ put_membuf_cb, &mb,
+ NULL, NULL,
+ getinfo_features_cb, NULL))
+ put_membuf_str (&mb, "unknown");
+ put_membuf_str (&mb, " ");
+ if (assuan_transact (entry_ctx, "GETINFO version",
+ put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
+ put_membuf_str (&mb, "unknown");
+ put_membuf_str (&mb, " ");
+ if (assuan_transact (entry_ctx, "GETINFO ttyinfo",
+ put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
+ put_membuf_str (&mb, "? ? ?");
+ put_membuf (&mb, "", 1);
+ flavor_version = get_membuf (&mb, NULL);
+ }
+
+
+ /* Now ask the Pinentry for its PID. If the Pinentry is new enough
+ it will send the pid back and we will use an inquire to notify
+ our client. The client may answer the inquiry either with END or
+ with CAN to cancel the pinentry. */
+ rc = assuan_transact (entry_ctx, "GETINFO pid",
+ getinfo_pid_cb, &pinentry_pid,
+ NULL, NULL, NULL, NULL);
+ if (rc)
+ {
+ log_info ("You may want to update to a newer pinentry\n");
+ rc = 0;
+ }
+ else if (!rc && (pid_t)pinentry_pid == (pid_t)(-1))
+ log_error ("pinentry did not return a PID\n");
+ else
+ {
+ rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version);
+ if (gpg_err_code (rc) == GPG_ERR_CANCELED
+ || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
+ return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT,
+ gpg_err_code (rc)));
+ rc = 0;
+ }
+
+ xfree (flavor_version);
+
+ return rc;
+}
+
+
+/* Returns True if the pinentry is currently active. If WAITSECONDS is
+ greater than zero the function will wait for this many seconds
+ before returning. */
+int
+pinentry_active_p (ctrl_t ctrl, int waitseconds)
+{
+ int err;
+ (void)ctrl;
+
+ if (waitseconds > 0)
+ {
+ struct timespec abstime;
+ int rc;
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += waitseconds;
+ err = npth_mutex_timedlock (&entry_lock, &abstime);
+ if (err)
+ {
+ if (err == ETIMEDOUT)
+ rc = gpg_error (GPG_ERR_TIMEOUT);
+ else
+ rc = gpg_error (GPG_ERR_INTERNAL);
+ return rc;
+ }
+ }
+ else
+ {
+ err = npth_mutex_trylock (&entry_lock);
+ if (err)
+ return gpg_error (GPG_ERR_LOCKED);
+ }
+
+ err = npth_mutex_unlock (&entry_lock);
+ if (err)
+ log_error ("failed to release the entry lock at %d: %s\n", __LINE__,
+ strerror (errno));
+ return 0;
+}
+
+
+static gpg_error_t
+getpin_cb (void *opaque, const void *buffer, size_t length)
+{
+ struct entry_parm_s *parm = opaque;
+
+ if (!buffer)
+ return 0;
+
+ /* we expect the pin to fit on one line */
+ if (parm->lines || length >= parm->size)
+ return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA);
+
+ /* fixme: we should make sure that the assuan buffer is allocated in
+ secure memory or read the response byte by byte */
+ memcpy (parm->buffer, buffer, length);
+ parm->buffer[length] = 0;
+ parm->lines++;
+ return 0;
+}
+
+
+static int
+all_digitsp( const char *s)
+{
+ for (; *s && *s >= '0' && *s <= '9'; s++)
+ ;
+ return !*s;
+}
+
+
+/* Return a new malloced string by unescaping the string S. Escaping
+ is percent escaping and '+'/space mapping. A binary Nul will
+ silently be replaced by a 0xFF. Function returns NULL to indicate
+ an out of memory status. Parsing stops at the end of the string or
+ a white space character. */
+static char *
+unescape_passphrase_string (const unsigned char *s)
+{
+ char *buffer, *d;
+
+ buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1);
+ if (!buffer)
+ return NULL;
+ while (*s && !spacep (s))
+ {
+ if (*s == '%' && s[1] && s[2])
+ {
+ s++;
+ *d = xtoi_2 (s);
+ if (!*d)
+ *d = '\xff';
+ d++;
+ s += 2;
+ }
+ else if (*s == '+')
+ {
+ *d++ = ' ';
+ s++;
+ }
+ else
+ *d++ = *s++;
+ }
+ *d = 0;
+ return buffer;
+}
+
+
+/* Estimate the quality of the passphrase PW and return a value in the
+ range 0..100. */
+static int
+estimate_passphrase_quality (const char *pw)
+{
+ int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3;
+ int length;
+ const char *s;
+
+ if (goodlength < 1)
+ return 0;
+
+ for (length = 0, s = pw; *s; s++)
+ if (!spacep (s))
+ length ++;
+
+ if (length > goodlength)
+ return 100;
+ return ((length*10) / goodlength)*10;
+}
+
+
+/* Generate a random passphrase in zBase32 encoding (RFC-6189) to be
+ * used by Pinentry to suggest a passphrase. */
+static char *
+generate_pin (void)
+{
+ unsigned int nbits = opt.min_passphrase_len * 8;
+ size_t nbytes;
+ void *rand;
+ char *generated;
+
+ if (nbits < 128)
+ nbits = DEFAULT_GENPIN_BITS;
+
+ nbytes = (nbits + 7) / 8;
+
+ rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM);
+ if (!rand)
+ {
+ log_error ("failed to generate random pin\n");
+ return NULL;
+ }
+
+ generated = zb32_encode (rand, nbits);
+ gcry_free (rand);
+ return generated;
+}
+
+
+/* Handle inquiries. */
+struct inq_cb_parm_s
+{
+ assuan_context_t ctx;
+ unsigned int flags; /* CHECK_CONSTRAINTS_... */
+ int genpinhash_valid;
+ char genpinhash[32]; /* Hash of the last generated pin. */
+};
+
+
+/* Return true if PIN is indentical to the last generated pin. */
+static int
+is_generated_pin (struct inq_cb_parm_s *parm, const char *pin)
+{
+ char hashbuf[32];
+
+ if (!parm->genpinhash_valid)
+ return 0;
+ if (!*pin)
+ return 0;
+ /* Note that we compare the hash so that we do not need to save the
+ * generated PIN longer than needed. */
+ gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, pin, strlen (pin));
+
+ if (!memcmp (hashbuf, parm->genpinhash, 32))
+ return 1; /* yes, it is the same. */
+
+ return 0;
+}
+
+
+static gpg_error_t
+inq_cb (void *opaque, const char *line)
+{
+ struct inq_cb_parm_s *parm = opaque;
+ gpg_error_t err;
+ const char *s;
+ char *pin;
+ int percent;
+ char numbuf[20];
+
+ if ((s = has_leading_keyword (line, "QUALITY")))
+ {
+ pin = unescape_passphrase_string (s);
+ if (!pin)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ percent = estimate_passphrase_quality (pin);
+ if (check_passphrase_constraints (NULL, pin, parm->flags, NULL))
+ percent = -percent;
+ snprintf (numbuf, sizeof numbuf, "%d", percent);
+ err = assuan_send_data (parm->ctx, numbuf, strlen (numbuf));
+ xfree (pin);
+ }
+ }
+ else if ((s = has_leading_keyword (line, "CHECKPIN")))
+ {
+ char *errtext = NULL;
+ size_t errtextlen;
+
+ if (!opt.enforce_passphrase_constraints)
+ {
+ log_error ("unexpected inquiry 'CHECKPIN' without enforced "
+ "passphrase constraints\n");
+ err = gpg_error (GPG_ERR_ASS_UNEXPECTED_CMD);
+ goto leave;
+ }
+
+ pin = unescape_passphrase_string (s);
+ if (!pin)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ if (!is_generated_pin (parm, pin)
+ && check_passphrase_constraints (NULL, pin,parm->flags, &errtext))
+ {
+ if (errtext)
+ {
+ /* Unescape the percent-escaped errtext because
+ assuan_send_data escapes it again. */
+ errtextlen = percent_unescape_inplace (errtext, 0);
+ err = assuan_send_data (parm->ctx, errtext, errtextlen);
+ }
+ else
+ {
+ log_error ("passphrase check failed without error text\n");
+ err = gpg_error (GPG_ERR_GENERAL);
+ }
+ }
+ else
+ {
+ err = assuan_send_data (parm->ctx, NULL, 0);
+ }
+ xfree (errtext);
+ xfree (pin);
+ }
+ }
+ else if ((s = has_leading_keyword (line, "GENPIN")))
+ {
+ int wasconf;
+
+ parm->genpinhash_valid = 0;
+ pin = generate_pin ();
+ if (!pin)
+ {
+ log_error ("failed to generate a passphrase\n");
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+ wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
+ assuan_begin_confidential (parm->ctx);
+ err = assuan_send_data (parm->ctx, pin, strlen (pin));
+ if (!wasconf)
+ assuan_end_confidential (parm->ctx);
+ gcry_md_hash_buffer (GCRY_MD_SHA256, parm->genpinhash, pin, strlen (pin));
+ parm->genpinhash_valid = 1;
+ xfree (pin);
+ }
+ else
+ {
+ log_error ("unsupported inquiry '%s' from pinentry\n", line);
+ err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
+ }
+
+ leave:
+ return err;
+}
+
+
+/* Helper to setup pinentry for genpin action. */
+static gpg_error_t
+setup_genpin (ctrl_t ctrl)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ char *tmpstr, *tmpstr2;
+ const char *tooltip;
+
+ (void)ctrl;
+
+ /* TRANSLATORS: This string is displayed by Pinentry as the label
+ for generating a passphrase. */
+ tmpstr = try_percent_escape (L_("Suggest"), "\t\r\n\f\v");
+ snprintf (line, DIM(line), "SETGENPIN %s", tmpstr? tmpstr:"");
+ xfree (tmpstr);
+ err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (gpg_err_code (err) == 103 /*(Old assuan error code)*/
+ || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
+ ; /* Ignore Unknown Command from old Pinentry versions. */
+ else if (err)
+ return err;
+
+ tmpstr2 = gnupg_get_help_string ("pinentry.genpin.tooltip", 0);
+ if (tmpstr2)
+ tooltip = tmpstr2;
+ else
+ {
+ /* TRANSLATORS: This string is a tooltip, shown by pinentry when
+ hovering over the generate button. Please use an appropriate
+ string to describe what this is about. The length of the
+ tooltip is limited to about 900 characters. If you do not
+ translate this entry, a default English text (see source)
+ will be used. The strcmp thingy is there to detect a
+ non-translated string. */
+ tooltip = L_("pinentry.genpin.tooltip");
+ if (!strcmp ("pinentry.genpin.tooltip", tooltip))
+ tooltip = "Suggest a random passphrase.";
+ }
+ tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
+ xfree (tmpstr2);
+ snprintf (line, DIM(line), "SETGENPIN_TT %s", tmpstr? tmpstr:"");
+ xfree (tmpstr);
+ err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (gpg_err_code (err) == 103 /*(Old assuan error code)*/
+ || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
+ ; /* Ignore Unknown Command from old pinentry versions. */
+ else if (err)
+ return err;
+
+ return 0;
+}
+
+
+/* Helper to setup pinentry for formatted passphrase. */
+static gpg_error_t
+setup_formatted_passphrase (ctrl_t ctrl)
+{
+ static const struct { const char *key, *help_id, *value; } tbl[] = {
+ /* TRANSLATORS: This is a text shown by pinentry if the option
+ for formatted passphrase is enabled. The length is
+ limited to about 900 characters. */
+ { "hint", "pinentry.formatted_passphrase.hint",
+ N_("Note: The blanks are not part of the passphrase.") },
+ { NULL, NULL }
+ };
+
+ gpg_error_t rc;
+ char line[ASSUAN_LINELENGTH];
+ int idx;
+ char *tmpstr;
+ const char *s;
+ char *escapedstr;
+
+ (void)ctrl;
+
+ if (opt.pinentry_formatted_passphrase)
+ {
+ snprintf (line, DIM(line), "OPTION formatted-passphrase");
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return rc;
+
+ for (idx=0; tbl[idx].key; idx++)
+ {
+ tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0);
+ if (tmpstr)
+ s = tmpstr;
+ else
+ s = L_(tbl[idx].value);
+ escapedstr = try_percent_escape (s, "\t\r\n\f\v");
+ xfree (tmpstr);
+ if (escapedstr && *escapedstr)
+ {
+ snprintf (line, DIM(line), "OPTION formatted-passphrase-%s=%s",
+ tbl[idx].key, escapedstr);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+ else
+ rc = 0;
+ xfree (escapedstr);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Helper to setup pinentry for enforced passphrase constraints. */
+static gpg_error_t
+setup_enforced_constraints (ctrl_t ctrl)
+{
+ static const struct { const char *key, *help_id, *value; } tbl[] = {
+ { "hint-short", "pinentry.constraints.hint.short", NULL },
+ { "hint-long", "pinentry.constraints.hint.long", NULL },
+ /* TRANSLATORS: This is a text shown by pinentry as title of a dialog
+ telling the user that the entered new passphrase does not satisfy
+ the passphrase constraints. Please keep it short. */
+ { "error-title", NULL, N_("Passphrase Not Allowed") },
+ { NULL, NULL }
+ };
+
+ gpg_error_t rc;
+ char line[ASSUAN_LINELENGTH];
+ int idx;
+ char *tmpstr;
+ const char *s;
+ char *escapedstr;
+
+ (void)ctrl;
+
+ if (opt.enforce_passphrase_constraints)
+ {
+ snprintf (line, DIM(line), "OPTION constraints-enforce");
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return rc;
+
+ for (idx=0; tbl[idx].key; idx++)
+ {
+ tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0);
+ if (tmpstr)
+ s = tmpstr;
+ else if (tbl[idx].value)
+ s = L_(tbl[idx].value);
+ else
+ {
+ log_error ("no help string found for %s\n", tbl[idx].help_id);
+ continue;
+ }
+ escapedstr = try_percent_escape (s, "\t\r\n\f\v");
+ xfree (tmpstr);
+ if (escapedstr && *escapedstr)
+ {
+ snprintf (line, DIM(line), "OPTION constraints-%s=%s",
+ tbl[idx].key, escapedstr);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+ else
+ rc = 0; /* Ignore an empty string (would give an IPC error). */
+ xfree (escapedstr);
+ if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Helper for agent_askpin and agent_get_passphrase. */
+static gpg_error_t
+setup_qualitybar (ctrl_t ctrl)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ char *tmpstr, *tmpstr2;
+ const char *tooltip;
+
+ (void)ctrl;
+
+ /* TRANSLATORS: This string is displayed by Pinentry as the label
+ for the quality bar. */
+ tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v");
+ snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:"");
+ xfree (tmpstr);
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc == 103 /*(Old assuan error code)*/
+ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
+ ; /* Ignore Unknown Command from old Pinentry versions. */
+ else if (rc)
+ return rc;
+
+ tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0);
+ if (tmpstr2)
+ tooltip = tmpstr2;
+ else
+ {
+ /* TRANSLATORS: This string is a tooltip, shown by pinentry when
+ hovering over the quality bar. Please use an appropriate
+ string to describe what this is about. The length of the
+ tooltip is limited to about 900 characters. If you do not
+ translate this entry, a default english text (see source)
+ will be used. */
+ tooltip = L_("pinentry.qualitybar.tooltip");
+ if (!strcmp ("pinentry.qualitybar.tooltip", tooltip))
+ tooltip = ("The quality of the text entered above.\n"
+ "Please ask your administrator for "
+ "details about the criteria.");
+ }
+ tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
+ xfree (tmpstr2);
+ snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
+ xfree (tmpstr);
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc == 103 /*(Old assuan error code)*/
+ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
+ ; /* Ignore Unknown Command from old pinentry versions. */
+ else if (rc)
+ return rc;
+
+ return 0;
+}
+
+/* Check the button_info line for a close action. Also check for the
+ PIN_REPEATED flag. */
+static gpg_error_t
+pinentry_status_cb (void *opaque, const char *line)
+{
+ unsigned int *flag = opaque;
+ const char *args;
+
+ if ((args = has_leading_keyword (line, "BUTTON_INFO")))
+ {
+ if (!strcmp (args, "close"))
+ *flag |= PINENTRY_STATUS_CLOSE_BUTTON;
+ }
+ else if (has_leading_keyword (line, "PIN_REPEATED"))
+ {
+ *flag |= PINENTRY_STATUS_PIN_REPEATED;
+ }
+ else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE"))
+ {
+ *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
+ }
+
+ return 0;
+}
+
+
+/* Build a SETDESC command line. This is a dedicated function so that
+ * it can remove control characters which are not supported by the
+ * current Pinentry. */
+static void
+build_cmd_setdesc (char *line, size_t linelen, const char *desc)
+{
+ char *src, *dst;
+
+ snprintf (line, linelen, "SETDESC %s", desc);
+ if (!entry_features.tabbing)
+ {
+ /* Remove RS and US. */
+ for (src=dst=line; *src; src++)
+ if (!strchr ("\x1e\x1f", *src))
+ *dst++ = *src;
+ *dst = 0;
+ }
+}
+
+
+/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread
+ * detecting the socket's EOF. */
+static gpg_error_t
+do_getpin (ctrl_t ctrl, struct entry_parm_s *parm)
+{
+ gpg_error_t rc;
+ int wasconf;
+ struct inq_cb_parm_s inq_cb_parm;
+
+ (void)ctrl;
+
+ inq_cb_parm.ctx = entry_ctx;
+ inq_cb_parm.flags = parm->constraints_flags;
+ inq_cb_parm.genpinhash_valid = 0;
+
+ wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
+ assuan_begin_confidential (entry_ctx);
+ rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm,
+ inq_cb, &inq_cb_parm,
+ pinentry_status_cb, &parm->status);
+ if (!wasconf)
+ assuan_end_confidential (entry_ctx);
+
+ if (!rc && parm->buffer && is_generated_pin (&inq_cb_parm, parm->buffer))
+ parm->status |= PINENTRY_STATUS_PASSWORD_GENERATED;
+ else
+ parm->status &= ~PINENTRY_STATUS_PASSWORD_GENERATED;
+
+ /* Most pinentries out in the wild return the old Assuan error code
+ for canceled which gets translated to an assuan Cancel error and
+ not to the code for a user cancel. Fix this here. */
+ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+ /* Change error code in case the window close button was clicked
+ to cancel the operation. */
+ if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON)
+ && gpg_err_code (rc) == GPG_ERR_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED);
+
+ return rc;
+}
+
+
+
+/* Call the Entry and ask for the PIN. We do check for a valid PIN
+ number here and repeat it as long as we have invalid formed
+ numbers. KEYINFO and CACHE_MODE are used to tell pinentry something
+ about the key. */
+gpg_error_t
+agent_askpin (ctrl_t ctrl,
+ const char *desc_text, const char *prompt_text,
+ const char *initial_errtext,
+ struct pin_entry_info_s *pininfo,
+ const char *keyinfo, cache_mode_t cache_mode)
+{
+ gpg_error_t rc;
+ char line[ASSUAN_LINELENGTH];
+ struct entry_parm_s parm;
+ const char *errtext = NULL;
+ int is_pin = 0;
+ int is_generated;
+
+ if (opt.batch)
+ return 0; /* fixme: we should return BAD PIN */
+
+ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+ {
+ if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+ return gpg_error (GPG_ERR_CANCELED);
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ {
+ unsigned char *passphrase;
+ size_t size;
+
+ *pininfo->pin = 0; /* Reset the PIN. */
+ rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size,
+ pininfo->max_length - 1);
+ if (rc)
+ return rc;
+
+ memcpy(&pininfo->pin, passphrase, size);
+ xfree(passphrase);
+ pininfo->pin[size] = 0;
+ if (pininfo->check_cb)
+ {
+ /* More checks by utilizing the optional callback. */
+ pininfo->cb_errtext = NULL;
+ rc = pininfo->check_cb (pininfo);
+ }
+ return rc;
+ }
+ return gpg_error(GPG_ERR_NO_PIN_ENTRY);
+ }
+
+ if (!pininfo || pininfo->max_length < 1)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!desc_text && pininfo->min_digits)
+ desc_text = L_("Please enter your PIN, so that the secret key "
+ "can be unlocked for this session");
+ else if (!desc_text)
+ desc_text = L_("Please enter your passphrase, so that the secret key "
+ "can be unlocked for this session");
+
+ if (prompt_text)
+ is_pin = !!strstr (prompt_text, "PIN");
+ else
+ is_pin = desc_text && strstr (desc_text, "PIN");
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ /* If we have a KEYINFO string and are normal, user, or ssh cache
+ mode, we tell that the Pinentry so it may use it for own caching
+ purposes. Most pinentries won't have this implemented and thus
+ we do not error out in this case. */
+ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+ || cache_mode == CACHE_MODE_USER
+ || cache_mode == CACHE_MODE_SSH))
+ snprintf (line, DIM(line), "SETKEYINFO %c/%s",
+ cache_mode == CACHE_MODE_USER? 'u' :
+ cache_mode == CACHE_MODE_SSH? 's' : 'n',
+ keyinfo);
+ else
+ snprintf (line, DIM(line), "SETKEYINFO --clear");
+
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
+ return unlock_pinentry (ctrl, rc);
+
+ build_cmd_setdesc (line, DIM(line), desc_text);
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ snprintf (line, DIM(line), "SETPROMPT %s",
+ prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:"));
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ /* If a passphrase quality indicator has been requested and a
+ minimum passphrase length has not been disabled, send the command
+ to the pinentry. */
+ if (pininfo->with_qualitybar && opt.min_passphrase_len )
+ {
+ rc = setup_qualitybar (ctrl);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ if (initial_errtext)
+ {
+ snprintf (line, DIM(line), "SETERROR %s", initial_errtext);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ if (pininfo->with_repeat)
+ {
+ snprintf (line, DIM(line), "SETREPEATERROR %s",
+ L_("does not match - try again"));
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ pininfo->with_repeat = 0; /* Pinentry does not support it. */
+ }
+ pininfo->repeat_okay = 0;
+ pininfo->status = 0;
+
+ for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
+ {
+ memset (&parm, 0, sizeof parm);
+ parm.size = pininfo->max_length;
+ *pininfo->pin = 0; /* Reset the PIN. */
+ parm.buffer = (unsigned char*)pininfo->pin;
+ parm.constraints_flags = pininfo->constraints_flags;
+
+ if (errtext)
+ {
+ /* TRANSLATORS: The string is appended to an error message in
+ the pinentry. The %s is the actual error message, the
+ two %d give the current and maximum number of tries.
+ Do not translate the "SETERROR" keyword. */
+ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
+ errtext, pininfo->failed_tries+1, pininfo->max_tries);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ errtext = NULL;
+ }
+
+ if (pininfo->with_repeat)
+ {
+ snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ rc = do_getpin (ctrl, &parm);
+ pininfo->status = parm.status;
+ is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED);
+
+ if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
+ errtext = is_pin? L_("PIN too long")
+ : L_("Passphrase too long");
+ else if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if (!errtext && pininfo->min_digits && !is_generated)
+ {
+ /* do some basic checks on the entered PIN. */
+ if (!all_digitsp (pininfo->pin))
+ errtext = L_("Invalid characters in PIN");
+ else if (pininfo->max_digits
+ && strlen (pininfo->pin) > pininfo->max_digits)
+ errtext = L_("PIN too long");
+ else if (strlen (pininfo->pin) < pininfo->min_digits)
+ errtext = L_("PIN too short");
+ }
+
+ if (!errtext && pininfo->check_cb && !is_generated)
+ {
+ /* More checks by utilizing the optional callback. */
+ pininfo->cb_errtext = NULL;
+ rc = pininfo->check_cb (pininfo);
+ /* When pinentry cache causes an error, return now. */
+ if (rc
+ && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+ return unlock_pinentry (ctrl, rc);
+
+ if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
+ {
+ if (pininfo->cb_errtext)
+ errtext = pininfo->cb_errtext;
+ else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
+ || gpg_err_code (rc) == GPG_ERR_BAD_PIN)
+ errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
+ }
+ else if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ if (!errtext)
+ {
+ if (pininfo->with_repeat
+ && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
+ pininfo->repeat_okay = 1;
+ return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
+ }
+
+ if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+ {
+ /* The password was read from the cache. Don't count this
+ against the retry count. */
+ pininfo->failed_tries --;
+ }
+ }
+
+ return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
+ : GPG_ERR_BAD_PASSPHRASE));
+}
+
+
+
+/* Ask for the passphrase using the supplied arguments. The returned
+ passphrase needs to be freed by the caller. PININFO is optional
+ and can be used to have constraints checinkg while the pinentry
+ dialog is open (like what we do in agent_askpin). This is very
+ similar to agent_akpin and we should eventually merge the two
+ functions. */
+int
+agent_get_passphrase (ctrl_t ctrl,
+ char **retpass, const char *desc, const char *prompt,
+ const char *errtext, int with_qualitybar,
+ const char *keyinfo, cache_mode_t cache_mode,
+ struct pin_entry_info_s *pininfo)
+{
+ int rc;
+ int is_pin;
+ int is_generated;
+ char line[ASSUAN_LINELENGTH];
+ struct entry_parm_s parm;
+
+ *retpass = NULL;
+ if (opt.batch)
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+
+ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+ {
+ unsigned char *passphrase;
+ size_t size;
+
+ if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+ return gpg_error (GPG_ERR_CANCELED);
+
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo)
+ {
+ *pininfo->pin = 0; /* Reset the PIN. */
+ rc = pinentry_loopback (ctrl, "PASSPHRASE",
+ &passphrase, &size,
+ pininfo->max_length - 1);
+ if (rc)
+ return rc;
+
+ memcpy (&pininfo->pin, passphrase, size);
+ wipememory (passphrase, size);
+ xfree (passphrase);
+ pininfo->pin[size] = 0;
+ if (pininfo->check_cb)
+ {
+ /* More checks by utilizing the optional callback. */
+ pininfo->cb_errtext = NULL;
+ rc = pininfo->check_cb (pininfo);
+ }
+ return rc;
+
+ }
+ else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ {
+ /* Legacy variant w/o PININFO. */
+ return pinentry_loopback (ctrl, "PASSPHRASE",
+ (unsigned char **)retpass, &size,
+ MAX_PASSPHRASE_LEN);
+ }
+
+ return gpg_error (GPG_ERR_NO_PIN_ENTRY);
+ }
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ /* Set IS_PIN and if needed a default prompt. */
+ if (prompt)
+ is_pin = !!strstr (prompt, "PIN");
+ else
+ {
+ is_pin = desc && strstr (desc, "PIN");
+ prompt = is_pin? L_("PIN:"): L_("Passphrase:");
+ }
+
+ /* If we have a KEYINFO string and are normal, user, or ssh cache
+ mode, we tell that the Pinentry so it may use it for own caching
+ purposes. Most pinentries won't have this implemented and thus
+ we do not error out in this case. */
+ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+ || cache_mode == CACHE_MODE_USER
+ || cache_mode == CACHE_MODE_SSH))
+ snprintf (line, DIM(line), "SETKEYINFO %c/%s",
+ cache_mode == CACHE_MODE_USER? 'u' :
+ cache_mode == CACHE_MODE_SSH? 's' : 'n',
+ keyinfo);
+ else
+ snprintf (line, DIM(line), "SETKEYINFO --clear");
+
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
+ return unlock_pinentry (ctrl, rc);
+
+ if (desc)
+ build_cmd_setdesc (line, DIM(line), desc);
+ else
+ snprintf (line, DIM(line), "RESET");
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ snprintf (line, DIM(line), "SETPROMPT %s", prompt);
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if ((with_qualitybar || (pininfo && pininfo->with_qualitybar))
+ && opt.min_passphrase_len)
+ {
+ rc = setup_qualitybar (ctrl);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ if (errtext)
+ {
+ snprintf (line, DIM(line), "SETERROR %s", errtext);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ rc = setup_formatted_passphrase (ctrl);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if (!pininfo)
+ {
+ /* Legacy method without PININFO. */
+ memset (&parm, 0, sizeof parm);
+ parm.size = ASSUAN_LINELENGTH/2 - 5;
+ parm.buffer = gcry_malloc_secure (parm.size+10);
+ if (!parm.buffer)
+ return unlock_pinentry (ctrl, out_of_core ());
+
+ rc = do_getpin (ctrl, &parm);
+ if (rc)
+ xfree (parm.buffer);
+ else
+ *retpass = parm.buffer;
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ /* We got PININFO. */
+
+ if (pininfo->with_repeat)
+ {
+ snprintf (line, DIM(line), "SETREPEATERROR %s",
+ L_("does not match - try again"));
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ pininfo->with_repeat = 0; /* Pinentry does not support it. */
+
+ (void)setup_genpin (ctrl);
+
+ rc = setup_enforced_constraints (ctrl);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+ pininfo->repeat_okay = 0;
+ pininfo->status = 0;
+
+ for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
+ {
+ memset (&parm, 0, sizeof parm);
+ parm.constraints_flags = pininfo->constraints_flags;
+ parm.size = pininfo->max_length;
+ parm.buffer = (unsigned char*)pininfo->pin;
+ *pininfo->pin = 0; /* Reset the PIN. */
+
+ if (errtext)
+ {
+ /* TRANSLATORS: The string is appended to an error message in
+ the pinentry. The %s is the actual error message, the
+ two %d give the current and maximum number of tries. */
+ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
+ errtext, pininfo->failed_tries+1, pininfo->max_tries);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ errtext = NULL;
+ }
+
+ if (pininfo->with_repeat)
+ {
+ snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ rc = do_getpin (ctrl, &parm);
+ pininfo->status = parm.status;
+ is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED);
+
+ if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
+ errtext = is_pin? L_("PIN too long")
+ : L_("Passphrase too long");
+ else if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if (!errtext && pininfo->min_digits && !is_generated)
+ {
+ /* do some basic checks on the entered PIN. */
+ if (!all_digitsp (pininfo->pin))
+ errtext = L_("Invalid characters in PIN");
+ else if (pininfo->max_digits
+ && strlen (pininfo->pin) > pininfo->max_digits)
+ errtext = L_("PIN too long");
+ else if (strlen (pininfo->pin) < pininfo->min_digits)
+ errtext = L_("PIN too short");
+ }
+
+ if (!errtext && pininfo->check_cb && !is_generated)
+ {
+ /* More checks by utilizing the optional callback. */
+ pininfo->cb_errtext = NULL;
+ rc = pininfo->check_cb (pininfo);
+ /* When pinentry cache causes an error, return now. */
+ if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+ return unlock_pinentry (ctrl, rc);
+
+ if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
+ {
+ if (pininfo->cb_errtext)
+ errtext = pininfo->cb_errtext;
+ else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
+ || gpg_err_code (rc) == GPG_ERR_BAD_PIN)
+ errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
+ }
+ else if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ if (!errtext)
+ {
+ if (pininfo->with_repeat
+ && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
+ pininfo->repeat_okay = 1;
+ return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
+ }
+
+ if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+ {
+ /* The password was read from the Pinentry's own cache.
+ Don't count this against the retry count. */
+ pininfo->failed_tries--;
+ }
+ }
+
+ return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
+ : GPG_ERR_BAD_PASSPHRASE));
+}
+
+
+
+/* Pop up the PIN-entry, display the text and the prompt and ask the
+ user to confirm this. We return 0 for success, ie. the user
+ confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an
+ other error. If WITH_CANCEL it true an extra cancel button is
+ displayed to allow the user to easily return a GPG_ERR_CANCELED.
+ if the Pinentry does not support this, the user can still cancel by
+ closing the Pinentry window. */
+int
+agent_get_confirmation (ctrl_t ctrl,
+ const char *desc, const char *ok,
+ const char *notok, int with_cancel)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+
+ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+ {
+ if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+ return gpg_error (GPG_ERR_CANCELED);
+
+ return gpg_error (GPG_ERR_NO_PIN_ENTRY);
+ }
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (desc)
+ build_cmd_setdesc (line, DIM(line), desc);
+ else
+ snprintf (line, DIM(line), "RESET");
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ /* Most pinentries out in the wild return the old Assuan error code
+ for canceled which gets translated to an assuan Cancel error and
+ not to the code for a user cancel. Fix this here. */
+ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if (ok)
+ {
+ snprintf (line, DIM(line), "SETOK %s", ok);
+ rc = assuan_transact (entry_ctx,
+ line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+ if (notok)
+ {
+ /* Try to use the newer NOTOK feature if a cancel button is
+ requested. If no cancel button is requested we keep on using
+ the standard cancel. */
+ if (with_cancel)
+ {
+ snprintf (line, DIM(line), "SETNOTOK %s", notok);
+ rc = assuan_transact (entry_ctx,
+ line, NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+ else
+ rc = GPG_ERR_ASS_UNKNOWN_CMD;
+
+ if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
+ {
+ snprintf (line, DIM(line), "SETCANCEL %s", notok);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ rc = assuan_transact (entry_ctx, "CONFIRM",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+
+ return unlock_pinentry (ctrl, rc);
+}
+
+
+
+/* Pop up the PINentry, display the text DESC and a button with the
+ text OK_BTN (which may be NULL to use the default of "OK") and wait
+ for the user to hit this button. The return value is not
+ relevant. */
+int
+agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+
+ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+ return gpg_error (GPG_ERR_CANCELED);
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (desc)
+ build_cmd_setdesc (line, DIM(line), desc);
+ else
+ snprintf (line, DIM(line), "RESET");
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ /* Most pinentries out in the wild return the old Assuan error code
+ for canceled which gets translated to an assuan Cancel error and
+ not to the code for a user cancel. Fix this here. */
+ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if (ok_btn)
+ {
+ snprintf (line, DIM(line), "SETOK %s", ok_btn);
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+ rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+
+ return unlock_pinentry (ctrl, rc);
+}
+
+
+/* The thread running the popup message. */
+static void *
+popup_message_thread (void *arg)
+{
+ (void)arg;
+
+ /* We use the --one-button hack instead of the MESSAGE command to
+ allow the use of old Pinentries. Those old Pinentries will then
+ show an additional Cancel button but that is mostly a visual
+ annoyance. */
+ assuan_transact (entry_ctx, "CONFIRM --one-button",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ popup_finished = 1;
+ return NULL;
+}
+
+
+/* Pop up a message window similar to the confirm one but keep it open
+ until agent_popup_message_stop has been called. It is crucial for
+ the caller to make sure that the stop function gets called as soon
+ as the message is not anymore required because the message is
+ system modal and all other attempts to use the pinentry will fail
+ (after a timeout). */
+int
+agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ npth_attr_t tattr;
+ int err;
+
+ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+ return gpg_error (GPG_ERR_CANCELED);
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (desc)
+ build_cmd_setdesc (line, DIM(line), desc);
+ else
+ snprintf (line, DIM(line), "RESET");
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+
+ if (ok_btn)
+ {
+ snprintf (line, DIM(line), "SETOK %s", ok_btn);
+ rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL);
+ if (rc)
+ return unlock_pinentry (ctrl, rc);
+ }
+
+ err = npth_attr_init (&tattr);
+ if (err)
+ return unlock_pinentry (ctrl, gpg_error_from_errno (err));
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
+
+ popup_finished = 0;
+ err = npth_create (&popup_tid, &tattr, popup_message_thread, NULL);
+ npth_attr_destroy (&tattr);
+ if (err)
+ {
+ rc = gpg_error_from_errno (err);
+ log_error ("error spawning popup message handler: %s\n",
+ strerror (err) );
+ return unlock_pinentry (ctrl, rc);
+ }
+ npth_setname_np (popup_tid, "popup-message");
+
+ return 0;
+}
+
+/* Close a popup window. */
+void
+agent_popup_message_stop (ctrl_t ctrl)
+{
+ int rc;
+ pid_t pid;
+
+ (void)ctrl;
+
+ if (!popup_tid || !entry_ctx)
+ {
+ log_debug ("agent_popup_message_stop called with no active popup\n");
+ return;
+ }
+
+ pid = assuan_get_pid (entry_ctx);
+ if (pid == (pid_t)(-1))
+ ; /* No pid available can't send a kill. */
+ else if (popup_finished)
+ ; /* Already finished and ready for joining. */
+#ifdef HAVE_W32_SYSTEM
+ /* Older versions of assuan set PID to 0 on Windows to indicate an
+ invalid value. */
+ else if (pid != (pid_t) INVALID_HANDLE_VALUE
+ && pid != 0)
+ {
+ HANDLE process = (HANDLE) pid;
+
+ /* Arbitrary error code. */
+ TerminateProcess (process, 1);
+ }
+#else
+ else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) )
+ { /* The daemon already died. No need to send a kill. However
+ because we already waited for the process, we need to tell
+ assuan that it should not wait again (done by
+ unlock_pinentry). */
+ if (rc == pid)
+ assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1);
+ }
+ else if (pid > 0)
+ kill (pid, SIGINT);
+#endif
+
+ /* Now wait for the thread to terminate. */
+ rc = npth_join (popup_tid, NULL);
+ if (rc)
+ log_debug ("agent_popup_message_stop: pth_join failed: %s\n",
+ strerror (rc));
+ /* Thread IDs are opaque, but we try our best here by resetting it
+ to the same content that a static global variable has. */
+ memset (&popup_tid, '\0', sizeof (popup_tid));
+
+ /* Now we can close the connection. */
+ unlock_pinentry (ctrl, 0);
+}
+
+int
+agent_clear_passphrase (ctrl_t ctrl,
+ const char *keyinfo, cache_mode_t cache_mode)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+
+ if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+ || cache_mode == CACHE_MODE_USER
+ || cache_mode == CACHE_MODE_SSH)))
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s",
+ cache_mode == CACHE_MODE_USER? 'u' :
+ cache_mode == CACHE_MODE_SSH? 's' : 'n',
+ keyinfo);
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+
+ return unlock_pinentry (ctrl, rc);
+}
diff --git a/agent/call-scd.c b/agent/call-scd.c
new file mode 100644
index 0000000..c5b95f4
--- /dev/null
+++ b/agent/call-scd.c
@@ -0,0 +1,1340 @@
+/* call-scd.c - fork of the scdaemon to do SC operations
+ * Copyright (C) 2001, 2002, 2005, 2007, 2010,
+ * 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2013 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/wait.h>
+#endif
+#include <npth.h>
+
+#include "agent.h"
+#include <assuan.h>
+#include "../common/strlist.h"
+
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+/* Definition of module local data of the CTRL structure. */
+struct scd_local_s
+{
+ /* We keep a list of all allocated context with an anchor at
+ SCD_LOCAL_LIST (see below). */
+ struct scd_local_s *next_local;
+
+ /* We need to get back to the ctrl object actually referencing this
+ structure. This is really an awkward way of enumerating the local
+ contexts. A much cleaner way would be to keep a global list of
+ ctrl objects to enumerate them. */
+ ctrl_t ctrl_backlink;
+
+ assuan_context_t ctx; /* NULL or session context for the SCdaemon
+ used with this connection. */
+ int locked; /* This flag is used to assert proper use of
+ start_scd and unlock_scd. */
+
+};
+
+
+/* Callback parameter for learn card */
+struct learn_parm_s
+{
+ void (*kpinfo_cb)(void*, const char *);
+ void *kpinfo_cb_arg;
+ void (*certinfo_cb)(void*, const char *);
+ void *certinfo_cb_arg;
+ void (*sinfo_cb)(void*, const char *, size_t, const char *);
+ void *sinfo_cb_arg;
+};
+
+
+/* Callback parameter used by inq_getpin and inq_writekey_parms. */
+struct inq_needpin_parm_s
+{
+ assuan_context_t ctx;
+ int (*getpin_cb)(void *, const char *, const char *, char*, size_t);
+ void *getpin_cb_arg;
+ const char *getpin_cb_desc;
+ assuan_context_t passthru; /* If not NULL, pass unknown inquiries
+ up to the caller. */
+
+ /* The next fields are used by inq_writekey_parm. */
+ const unsigned char *keydata;
+ size_t keydatalen;
+};
+
+
+/* To keep track of all active SCD contexts, we keep a linked list
+ anchored at this variable. */
+static struct scd_local_s *scd_local_list;
+
+/* A Mutex used inside the start_scd function. */
+static npth_mutex_t start_scd_lock;
+
+/* A malloced string with the name of the socket to be used for
+ additional connections. May be NULL if not provided by
+ SCdaemon. */
+static char *socket_name;
+
+/* The context of the primary connection. This is also used as a flag
+ to indicate whether the scdaemon has been started. */
+static assuan_context_t primary_scd_ctx;
+
+/* To allow reuse of the primary connection, the following flag is set
+ to true if the primary context has been reset and is not in use by
+ any connection. */
+static int primary_scd_ctx_reusable;
+
+
+
+/* Local prototypes. */
+
+
+
+
+/* This function must be called once to initialize this module. This
+ has to be done before a second thread is spawned. We can't do the
+ static initialization because NPth emulation code might not be able
+ to do a static init; in particular, it is not possible for W32. */
+void
+initialize_module_call_scd (void)
+{
+ static int initialized;
+ int err;
+
+ if (!initialized)
+ {
+ err = npth_mutex_init (&start_scd_lock, NULL);
+ if (err)
+ log_fatal ("error initializing mutex: %s\n", strerror (err));
+ initialized = 1;
+ }
+}
+
+
+/* This function may be called to print information pertaining to the
+ current state of this module to the log. */
+void
+agent_scd_dump_state (void)
+{
+ log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n",
+ primary_scd_ctx,
+ (long)assuan_get_pid (primary_scd_ctx),
+ primary_scd_ctx_reusable);
+ if (socket_name)
+ log_info ("agent_scd_dump_state: socket='%s'\n", socket_name);
+}
+
+
+/* The unlock_scd function shall be called after having accessed the
+ SCD. It is currently not very useful but gives an opportunity to
+ keep track of connections currently calling SCD. Note that the
+ "lock" operation is done by the start_scd() function which must be
+ called and error checked before any SCD operation. CTRL is the
+ usual connection context and RC the error code to be passed trhough
+ the function. */
+static int
+unlock_scd (ctrl_t ctrl, int rc)
+{
+ if (ctrl->scd_local->locked != 1)
+ {
+ log_error ("unlock_scd: invalid lock count (%d)\n",
+ ctrl->scd_local->locked);
+ if (!rc)
+ rc = gpg_error (GPG_ERR_INTERNAL);
+ }
+ ctrl->scd_local->locked = 0;
+ return rc;
+}
+
+/* To make sure we leave no secrets in our image after forking of the
+ scdaemon, we use this callback. */
+static void
+atfork_cb (void *opaque, int where)
+{
+ (void)opaque;
+
+ if (!where)
+ gcry_control (GCRYCTL_TERM_SECMEM);
+}
+
+
+/* Fork off the SCdaemon if this has not already been done. Lock the
+ daemon and make sure that a proper context has been setup in CTRL.
+ This function might also lock the daemon, which means that the
+ caller must call unlock_scd after this function has returned
+ success and the actual Assuan transaction been done. */
+static int
+start_scd (ctrl_t ctrl)
+{
+ gpg_error_t err = 0;
+ const char *pgmname;
+ assuan_context_t ctx = NULL;
+ const char *argv[5];
+ assuan_fd_t no_close_list[3];
+ int i;
+ int rc;
+ char *abs_homedir = NULL;
+
+ if (opt.disable_scdaemon)
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ /* If this is the first call for this session, setup the local data
+ structure. */
+ if (!ctrl->scd_local)
+ {
+ ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local);
+ if (!ctrl->scd_local)
+ return gpg_error_from_syserror ();
+ ctrl->scd_local->ctrl_backlink = ctrl;
+ ctrl->scd_local->next_local = scd_local_list;
+ scd_local_list = ctrl->scd_local;
+ }
+
+
+ /* Assert that the lock count is as expected. */
+ if (ctrl->scd_local->locked)
+ {
+ log_error ("start_scd: invalid lock count (%d)\n",
+ ctrl->scd_local->locked);
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+ ctrl->scd_local->locked++;
+
+ if (ctrl->scd_local->ctx)
+ return 0; /* Okay, the context is fine. We used to test for an
+ alive context here and do an disconnect. Now that we
+ have a ticker function to check for it, it is easier
+ not to check here but to let the connection run on an
+ error instead. */
+
+
+ /* We need to protect the following code. */
+ rc = npth_mutex_lock (&start_scd_lock);
+ if (rc)
+ {
+ log_error ("failed to acquire the start_scd lock: %s\n",
+ strerror (rc));
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+
+ /* Check whether the pipe server has already been started and in
+ this case either reuse a lingering pipe connection or establish a
+ new socket based one. */
+ if (primary_scd_ctx && primary_scd_ctx_reusable)
+ {
+ ctx = primary_scd_ctx;
+ primary_scd_ctx_reusable = 0;
+ if (opt.verbose)
+ log_info ("new connection to SCdaemon established (reusing)\n");
+ goto leave;
+ }
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
+ err = rc;
+ goto leave;
+ }
+
+ if (socket_name)
+ {
+ rc = assuan_socket_connect (ctx, socket_name, 0, 0);
+ if (rc)
+ {
+ log_error ("can't connect to socket '%s': %s\n",
+ socket_name, gpg_strerror (rc));
+ err = gpg_error (GPG_ERR_NO_SCDAEMON);
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("new connection to SCdaemon established\n");
+ goto leave;
+ }
+
+ if (primary_scd_ctx)
+ {
+ log_info ("SCdaemon is running but won't accept further connections\n");
+ err = gpg_error (GPG_ERR_NO_SCDAEMON);
+ goto leave;
+ }
+
+ /* Nope, it has not been started. Fire it up now. */
+ if (opt.verbose)
+ log_info ("no running SCdaemon - starting it\n");
+
+ if (fflush (NULL))
+ {
+#ifndef HAVE_W32_SYSTEM
+ err = gpg_error_from_syserror ();
+#endif
+ log_error ("error flushing pending output: %s\n", strerror (errno));
+ /* At least Windows XP fails here with EBADF. According to docs
+ and Wine an fflush(NULL) is the same as _flushall. However
+ the Wime implementation does not flush stdin,stdout and stderr
+ - see above. Lets try to ignore the error. */
+#ifndef HAVE_W32_SYSTEM
+ goto leave;
+#endif
+ }
+
+ if (!opt.scdaemon_program || !*opt.scdaemon_program)
+ opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON);
+ if ( !(pgmname = strrchr (opt.scdaemon_program, '/')))
+ pgmname = opt.scdaemon_program;
+ else
+ pgmname++;
+
+ argv[0] = pgmname;
+ argv[1] = "--multi-server";
+ if (gnupg_default_homedir_p ())
+ argv[2] = NULL;
+ else
+ {
+ abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
+ if (!abs_homedir)
+ {
+ log_error ("error building filename: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ goto leave;
+ }
+
+ argv[2] = "--homedir";
+ argv[3] = abs_homedir;
+ argv[4] = NULL;
+ }
+
+ i=0;
+ if (!opt.running_detached)
+ {
+ if (log_get_fd () != -1)
+ no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
+ no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
+ }
+ no_close_list[i] = ASSUAN_INVALID_FD;
+
+ /* Connect to the scdaemon and perform initial handshaking. Use
+ detached flag so that under Windows SCDAEMON does not show up a
+ new window. */
+ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv,
+ no_close_list, atfork_cb, NULL,
+ ASSUAN_PIPE_CONNECT_DETACHED);
+ if (rc)
+ {
+ log_error ("can't connect to the SCdaemon: %s\n",
+ gpg_strerror (rc));
+ err = gpg_error (GPG_ERR_NO_SCDAEMON);
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_debug ("first connection to SCdaemon established\n");
+
+
+ /* Get the name of the additional socket opened by scdaemon. */
+ {
+ membuf_t data;
+ unsigned char *databuf;
+ size_t datalen;
+
+ xfree (socket_name);
+ socket_name = NULL;
+ init_membuf (&data, 256);
+ assuan_transact (ctx, "GETINFO socket_name",
+ put_membuf_cb, &data, NULL, NULL, NULL, NULL);
+
+ databuf = get_membuf (&data, &datalen);
+ if (databuf && datalen)
+ {
+ socket_name = xtrymalloc (datalen + 1);
+ if (!socket_name)
+ log_error ("warning: can't store socket name: %s\n",
+ strerror (errno));
+ else
+ {
+ memcpy (socket_name, databuf, datalen);
+ socket_name[datalen] = 0;
+ if (DBG_IPC)
+ log_debug ("additional connections at '%s'\n", socket_name);
+ }
+ }
+ xfree (databuf);
+ }
+
+ /* Tell the scdaemon we want him to send us an event signal. We
+ don't support this for W32CE. */
+#ifndef HAVE_W32CE_SYSTEM
+ if (opt.sigusr2_enabled)
+ {
+ char buf[100];
+
+#ifdef HAVE_W32_SYSTEM
+ snprintf (buf, sizeof buf, "OPTION event-signal=%p",
+ get_agent_scd_notify_event ());
+#else
+ snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2);
+#endif
+ assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+#endif /*HAVE_W32CE_SYSTEM*/
+
+ primary_scd_ctx = ctx;
+ primary_scd_ctx_reusable = 0;
+
+ leave:
+ xfree (abs_homedir);
+ if (err)
+ {
+ unlock_scd (ctrl, err);
+ if (ctx)
+ assuan_release (ctx);
+ }
+ else
+ {
+ ctrl->scd_local->ctx = ctx;
+ }
+ rc = npth_mutex_unlock (&start_scd_lock);
+ if (rc)
+ log_error ("failed to release the start_scd lock: %s\n", strerror (rc));
+ return err;
+}
+
+
+/* Check whether the SCdaemon is active. This is a fast check without
+ any locking and might give a wrong result if another thread is about
+ to start the daemon or the daemon is about to be stopped.. */
+int
+agent_scd_check_running (void)
+{
+ return !!primary_scd_ctx;
+}
+
+
+/* Check whether the Scdaemon is still alive and clean it up if not. */
+void
+agent_scd_check_aliveness (void)
+{
+ pid_t pid;
+#ifdef HAVE_W32_SYSTEM
+ DWORD rc;
+#else
+ int rc;
+#endif
+ struct timespec abstime;
+ int err;
+
+ if (!primary_scd_ctx)
+ return; /* No scdaemon running. */
+
+ /* This is not a critical function so we use a short timeout while
+ acquiring the lock. */
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += 1;
+ err = npth_mutex_timedlock (&start_scd_lock, &abstime);
+ if (err)
+ {
+ if (err == ETIMEDOUT)
+ {
+ if (opt.verbose > 1)
+ log_info ("failed to acquire the start_scd lock while"
+ " doing an aliveness check: %s\n", strerror (err));
+ }
+ else
+ log_error ("failed to acquire the start_scd lock while"
+ " doing an aliveness check: %s\n", strerror (err));
+ return;
+ }
+
+ if (primary_scd_ctx)
+ {
+ pid = assuan_get_pid (primary_scd_ctx);
+#ifdef HAVE_W32_SYSTEM
+ /* If we have a PID we disconnect if either GetExitProcessCode
+ fails or if ir returns the exit code of the scdaemon. 259 is
+ the error code for STILL_ALIVE. */
+ if (pid != (pid_t)(void*)(-1) && pid
+ && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259))
+#else
+ if (pid != (pid_t)(-1) && pid
+ && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) )
+#endif
+ {
+ /* Okay, scdaemon died. Disconnect the primary connection
+ now but take care that it won't do another wait. Also
+ cleanup all other connections and release their
+ resources. The next use will start a new daemon then.
+ Due to the use of the START_SCD_LOCAL we are sure that
+ none of these context are actually in use. */
+ struct scd_local_s *sl;
+
+ assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1);
+ assuan_release (primary_scd_ctx);
+
+ for (sl=scd_local_list; sl; sl = sl->next_local)
+ {
+ if (sl->ctx)
+ {
+ if (sl->ctx != primary_scd_ctx)
+ assuan_release (sl->ctx);
+ sl->ctx = NULL;
+ }
+ }
+
+ primary_scd_ctx = NULL;
+ primary_scd_ctx_reusable = 0;
+
+ xfree (socket_name);
+ socket_name = NULL;
+ }
+ }
+
+ err = npth_mutex_unlock (&start_scd_lock);
+ if (err)
+ log_error ("failed to release the start_scd lock while"
+ " doing the aliveness check: %s\n", strerror (err));
+}
+
+
+
+/* Reset the SCD if it has been used. Actually it is not a reset but
+ a cleanup of resources used by the current connection. */
+int
+agent_reset_scd (ctrl_t ctrl)
+{
+ if (ctrl->scd_local)
+ {
+ if (ctrl->scd_local->ctx)
+ {
+ /* We can't disconnect the primary context because libassuan
+ does a waitpid on it and thus the system would hang.
+ Instead we send a reset and keep that connection for
+ reuse. */
+ if (ctrl->scd_local->ctx == primary_scd_ctx)
+ {
+ /* Send a RESTART to the SCD. This is required for the
+ primary connection as a kind of virtual EOF; we don't
+ have another way to tell it that the next command
+ should be viewed as if a new connection has been
+ made. For the non-primary connections this is not
+ needed as we simply close the socket. We don't check
+ for an error here because the RESTART may fail for
+ example if the scdaemon has already been terminated.
+ Anyway, we need to set the reusable flag to make sure
+ that the aliveness check can clean it up. */
+ assuan_transact (primary_scd_ctx, "RESTART",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ primary_scd_ctx_reusable = 1;
+ }
+ else
+ assuan_release (ctrl->scd_local->ctx);
+ ctrl->scd_local->ctx = NULL;
+ }
+
+ /* Remove the local context from our list and release it. */
+ if (!scd_local_list)
+ BUG ();
+ else if (scd_local_list == ctrl->scd_local)
+ scd_local_list = ctrl->scd_local->next_local;
+ else
+ {
+ struct scd_local_s *sl;
+
+ for (sl=scd_local_list; sl->next_local; sl = sl->next_local)
+ if (sl->next_local == ctrl->scd_local)
+ break;
+ if (!sl->next_local)
+ BUG ();
+ sl->next_local = ctrl->scd_local->next_local;
+ }
+ xfree (ctrl->scd_local);
+ ctrl->scd_local = NULL;
+ }
+
+ return 0;
+}
+
+
+
+static gpg_error_t
+learn_status_cb (void *opaque, const char *line)
+{
+ struct learn_parm_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+ if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen))
+ {
+ parm->certinfo_cb (parm->certinfo_cb_arg, line);
+ }
+ else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
+ {
+ parm->kpinfo_cb (parm->kpinfo_cb_arg, line);
+ }
+ else if (keywordlen && *line)
+ {
+ parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line);
+ }
+
+ return 0;
+}
+
+/* Perform the LEARN command and return a list of all private keys
+ stored on the card. */
+int
+agent_card_learn (ctrl_t ctrl,
+ void (*kpinfo_cb)(void*, const char *),
+ void *kpinfo_cb_arg,
+ void (*certinfo_cb)(void*, const char *),
+ void *certinfo_cb_arg,
+ void (*sinfo_cb)(void*, const char *, size_t, const char *),
+ void *sinfo_cb_arg)
+{
+ int rc;
+ struct learn_parm_s parm;
+
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ memset (&parm, 0, sizeof parm);
+ parm.kpinfo_cb = kpinfo_cb;
+ parm.kpinfo_cb_arg = kpinfo_cb_arg;
+ parm.certinfo_cb = certinfo_cb;
+ parm.certinfo_cb_arg = certinfo_cb_arg;
+ parm.sinfo_cb = sinfo_cb;
+ parm.sinfo_cb_arg = sinfo_cb_arg;
+ rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force",
+ NULL, NULL, NULL, NULL,
+ learn_status_cb, &parm);
+ if (rc)
+ return unlock_scd (ctrl, rc);
+
+ return unlock_scd (ctrl, 0);
+}
+
+
+
+static gpg_error_t
+get_serialno_cb (void *opaque, const char *line)
+{
+ char **serialno = opaque;
+ const char *keyword = line;
+ const char *s;
+ int keywordlen, n;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+ {
+ if (*serialno)
+ return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+ if (!n || (n&1)|| !(spacep (s) || !*s) )
+ return gpg_error (GPG_ERR_ASS_PARAMETER);
+ *serialno = xtrymalloc (n+1);
+ if (!*serialno)
+ return out_of_core ();
+ memcpy (*serialno, line, n);
+ (*serialno)[n] = 0;
+ }
+
+ return 0;
+}
+
+/* Return the serial number of the card or an appropriate error. The
+ serial number is returned as a hexstring. */
+int
+agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand)
+{
+ int rc;
+ char *serialno = NULL;
+ char line[ASSUAN_LINELENGTH];
+
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ if (!demand)
+ strcpy (line, "SERIALNO");
+ else
+ snprintf (line, DIM(line), "SERIALNO --demand=%s", demand);
+
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ NULL, NULL, NULL, NULL,
+ get_serialno_cb, &serialno);
+ if (rc)
+ {
+ xfree (serialno);
+ return unlock_scd (ctrl, rc);
+ }
+ *r_serialno = serialno;
+ return unlock_scd (ctrl, 0);
+}
+
+
+
+
+/* Handle the NEEDPIN inquiry. */
+static gpg_error_t
+inq_needpin (void *opaque, const char *line)
+{
+ struct inq_needpin_parm_s *parm = opaque;
+ const char *s;
+ char *pin;
+ size_t pinlen;
+ int rc;
+
+ if ((s = has_leading_keyword (line, "NEEDPIN")))
+ {
+ line = s;
+ pinlen = 90;
+ pin = gcry_malloc_secure (pinlen);
+ if (!pin)
+ return out_of_core ();
+
+ rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc,
+ line, pin, pinlen);
+ if (!rc)
+ {
+ assuan_begin_confidential (parm->ctx);
+ rc = assuan_send_data (parm->ctx, pin, pinlen);
+ assuan_end_confidential (parm->ctx);
+ }
+ wipememory (pin, pinlen);
+ xfree (pin);
+ }
+ else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT")))
+ {
+ rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc,
+ s, NULL, 1);
+ }
+ else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT")))
+ {
+ rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc,
+ "", NULL, 0);
+ }
+ else if (parm->passthru)
+ {
+ unsigned char *value;
+ size_t valuelen;
+ int rest;
+ int needrest = !strncmp (line, "KEYDATA", 8);
+
+ /* Pass the inquiry up to our caller. We limit the maximum
+ amount to an arbitrary value. As we know that the KEYDATA
+ enquiry is pretty sensitive we disable logging then */
+ if ((rest = (needrest
+ && !assuan_get_flag (parm->passthru, ASSUAN_CONFIDENTIAL))))
+ assuan_begin_confidential (parm->passthru);
+ rc = assuan_inquire (parm->passthru, line, &value, &valuelen, 8096);
+ if (rest)
+ assuan_end_confidential (parm->passthru);
+ if (!rc)
+ {
+ if ((rest = (needrest
+ && !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL))))
+ assuan_begin_confidential (parm->ctx);
+ rc = assuan_send_data (parm->ctx, value, valuelen);
+ if (rest)
+ assuan_end_confidential (parm->ctx);
+ xfree (value);
+ }
+ else
+ log_error ("error forwarding inquiry '%s': %s\n",
+ line, gpg_strerror (rc));
+ }
+ else
+ {
+ log_error ("unsupported inquiry '%s'\n", line);
+ rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
+ }
+
+ return rc;
+}
+
+
+/* Helper returning a command option to describe the used hash
+ algorithm. See scd/command.c:cmd_pksign. */
+static const char *
+hash_algo_option (int algo)
+{
+ switch (algo)
+ {
+ case GCRY_MD_MD5 : return "--hash=md5";
+ case GCRY_MD_RMD160: return "--hash=rmd160";
+ case GCRY_MD_SHA1 : return "--hash=sha1";
+ case GCRY_MD_SHA224: return "--hash=sha224";
+ case GCRY_MD_SHA256: return "--hash=sha256";
+ case GCRY_MD_SHA384: return "--hash=sha384";
+ case GCRY_MD_SHA512: return "--hash=sha512";
+ default: return "";
+ }
+}
+
+
+/* Create a signature using the current card. MDALGO is either 0 or
+ * gives the digest algorithm. DESC_TEXT is an additional parameter
+ * passed to GETPIN_CB. */
+int
+agent_card_pksign (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const char *desc_text,
+ int mdalgo,
+ const unsigned char *indata, size_t indatalen,
+ unsigned char **r_buf, size_t *r_buflen)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ struct inq_needpin_parm_s inqparm;
+
+ *r_buf = NULL;
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ if (indatalen*2 + 50 > DIM(line))
+ return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL));
+
+ bin2hex (indata, indatalen, stpcpy (line, "SETDATA "));
+
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_scd (ctrl, rc);
+
+ init_membuf (&data, 1024);
+ inqparm.ctx = ctrl->scd_local->ctx;
+ inqparm.getpin_cb = getpin_cb;
+ inqparm.getpin_cb_arg = getpin_cb_arg;
+ inqparm.getpin_cb_desc = desc_text;
+ inqparm.passthru = 0;
+ inqparm.keydata = NULL;
+ inqparm.keydatalen = 0;
+
+ if (ctrl->use_auth_call)
+ snprintf (line, sizeof line, "PKAUTH %s", keyid);
+ else
+ snprintf (line, sizeof line, "PKSIGN %s %s",
+ hash_algo_option (mdalgo), keyid);
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ put_membuf_cb, &data,
+ inq_needpin, &inqparm,
+ NULL, NULL);
+
+ if (rc)
+ {
+ size_t len;
+
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (ctrl, rc);
+ }
+
+ *r_buf = get_membuf (&data, r_buflen);
+ return unlock_scd (ctrl, 0);
+}
+
+
+
+
+/* Check whether there is any padding info from scdaemon. */
+static gpg_error_t
+padding_info_cb (void *opaque, const char *line)
+{
+ int *r_padding = opaque;
+ const char *s;
+
+ if ((s=has_leading_keyword (line, "PADDING")))
+ {
+ *r_padding = atoi (s);
+ }
+
+ return 0;
+}
+
+
+/* Decipher INDATA using the current card. Note that the returned
+ * value is not an s-expression but the raw data as returned by
+ * scdaemon. The padding information is stored at R_PADDING with -1
+ * for not known. DESC_TEXT is an additional parameter passed to
+ * GETPIN_CB. */
+int
+agent_card_pkdecrypt (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const char *desc_text,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen, int *r_padding)
+{
+ int rc, i;
+ char *p, line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ struct inq_needpin_parm_s inqparm;
+ size_t len;
+
+ *r_buf = NULL;
+ *r_padding = -1; /* Unknown. */
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ /* FIXME: use secure memory where appropriate */
+
+ for (len = 0; len < indatalen;)
+ {
+ p = stpcpy (line, "SETDATA ");
+ if (len)
+ p = stpcpy (p, "--append ");
+ for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++)
+ {
+ sprintf (p, "%02X", indata[len]);
+ p += 2;
+ }
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_scd (ctrl, rc);
+ }
+
+ init_membuf (&data, 1024);
+ inqparm.ctx = ctrl->scd_local->ctx;
+ inqparm.getpin_cb = getpin_cb;
+ inqparm.getpin_cb_arg = getpin_cb_arg;
+ inqparm.getpin_cb_desc = desc_text;
+ inqparm.passthru = 0;
+ inqparm.keydata = NULL;
+ inqparm.keydatalen = 0;
+ snprintf (line, DIM(line), "PKDECRYPT %s", keyid);
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ put_membuf_cb, &data,
+ inq_needpin, &inqparm,
+ padding_info_cb, r_padding);
+
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (ctrl, rc);
+ }
+ *r_buf = get_membuf (&data, r_buflen);
+ if (!*r_buf)
+ return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM));
+
+ return unlock_scd (ctrl, 0);
+}
+
+
+
+/* Read a certificate with ID into R_BUF and R_BUFLEN. */
+int
+agent_card_readcert (ctrl_t ctrl,
+ const char *id, char **r_buf, size_t *r_buflen)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ size_t len;
+
+ *r_buf = NULL;
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line), "READCERT %s", id);
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ put_membuf_cb, &data,
+ NULL, NULL,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (ctrl, rc);
+ }
+ *r_buf = get_membuf (&data, r_buflen);
+ if (!*r_buf)
+ return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM));
+
+ return unlock_scd (ctrl, 0);
+}
+
+
+
+/* Read a key with ID and return it in an allocate buffer pointed to
+ by r_BUF as a valid S-expression. */
+int
+agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ size_t len, buflen;
+
+ *r_buf = NULL;
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line), "READKEY %s", id);
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ put_membuf_cb, &data,
+ NULL, NULL,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (ctrl, rc);
+ }
+ *r_buf = get_membuf (&data, &buflen);
+ if (!*r_buf)
+ return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM));
+
+ if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL))
+ {
+ xfree (*r_buf); *r_buf = NULL;
+ return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE));
+ }
+
+ return unlock_scd (ctrl, 0);
+}
+
+
+/* Handle a KEYDATA inquiry. Note, we only send the data,
+ assuan_transact takes care of flushing and writing the end */
+static gpg_error_t
+inq_writekey_parms (void *opaque, const char *line)
+{
+ struct inq_needpin_parm_s *parm = opaque;
+
+ if (has_leading_keyword (line, "KEYDATA"))
+ return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen);
+ else
+ return inq_needpin (opaque, line);
+}
+
+
+int
+agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata, size_t keydatalen,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct inq_needpin_parm_s parms;
+
+ (void)serialno;
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id);
+ parms.ctx = ctrl->scd_local->ctx;
+ parms.getpin_cb = getpin_cb;
+ parms.getpin_cb_arg = getpin_cb_arg;
+ parms.getpin_cb_desc= NULL;
+ parms.passthru = 0;
+ parms.keydata = keydata;
+ parms.keydatalen = keydatalen;
+
+ rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL,
+ inq_writekey_parms, &parms, NULL, NULL);
+ return unlock_scd (ctrl, rc);
+}
+
+
+
+/* Type used with the card_getattr_cb. */
+struct card_getattr_parm_s {
+ const char *keyword; /* Keyword to look for. */
+ size_t keywordlen; /* strlen of KEYWORD. */
+ char *data; /* Malloced and unescaped data. */
+ int error; /* ERRNO value or 0 on success. */
+};
+
+/* Callback function for agent_card_getattr. */
+static gpg_error_t
+card_getattr_cb (void *opaque, const char *line)
+{
+ struct card_getattr_parm_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+
+ if (parm->data)
+ return 0; /* We want only the first occurrence. */
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == parm->keywordlen
+ && !memcmp (keyword, parm->keyword, keywordlen))
+ {
+ parm->data = percent_plus_unescape ((const unsigned char*)line, 0xff);
+ if (!parm->data)
+ parm->error = errno;
+ }
+
+ return 0;
+}
+
+
+/* Call the agent to retrieve a single line data object. On success
+ the object is malloced and stored at RESULT; it is guaranteed that
+ NULL is never stored in this case. On error an error code is
+ returned and NULL stored at RESULT. */
+gpg_error_t
+agent_card_getattr (ctrl_t ctrl, const char *name, char **result)
+{
+ int err;
+ struct card_getattr_parm_s parm;
+ char line[ASSUAN_LINELENGTH];
+
+ *result = NULL;
+
+ if (!*name)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ memset (&parm, 0, sizeof parm);
+ parm.keyword = name;
+ parm.keywordlen = strlen (name);
+
+ /* We assume that NAME does not need escaping. */
+ if (8 + strlen (name) > DIM(line)-1)
+ return gpg_error (GPG_ERR_TOO_LARGE);
+ stpcpy (stpcpy (line, "GETATTR "), name);
+
+ err = start_scd (ctrl);
+ if (err)
+ return err;
+
+ err = assuan_transact (ctrl->scd_local->ctx, line,
+ NULL, NULL, NULL, NULL,
+ card_getattr_cb, &parm);
+ if (!err && parm.error)
+ err = gpg_error_from_errno (parm.error);
+
+ if (!err && !parm.data)
+ err = gpg_error (GPG_ERR_NO_DATA);
+
+ if (!err)
+ *result = parm.data;
+ else
+ xfree (parm.data);
+
+ return unlock_scd (ctrl, err);
+}
+
+
+
+struct card_cardlist_parm_s {
+ int error;
+ strlist_t list;
+};
+
+/* Callback function for agent_card_cardlist. */
+static gpg_error_t
+card_cardlist_cb (void *opaque, const char *line)
+{
+ struct card_cardlist_parm_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+ {
+ const char *s;
+ int n;
+
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+
+ if (!n || (n&1) || *s)
+ parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
+ else
+ add_to_strlist (&parm->list, line);
+ }
+
+ return 0;
+}
+
+/* Call the scdaemon to retrieve list of available cards. On success
+ the allocated strlist is stored at RESULT. On error an error code is
+ returned and NULL stored at RESULT. */
+gpg_error_t
+agent_card_cardlist (ctrl_t ctrl, strlist_t *result)
+{
+ int err;
+ struct card_cardlist_parm_s parm;
+ char line[ASSUAN_LINELENGTH];
+
+ *result = NULL;
+
+ memset (&parm, 0, sizeof parm);
+ strcpy (line, "GETINFO card_list");
+
+ err = start_scd (ctrl);
+ if (err)
+ return err;
+
+ err = assuan_transact (ctrl->scd_local->ctx, line,
+ NULL, NULL, NULL, NULL,
+ card_cardlist_cb, &parm);
+ if (!err && parm.error)
+ err = parm.error;
+
+ if (!err)
+ *result = parm.list;
+ else
+ free_strlist (parm.list);
+
+ return unlock_scd (ctrl, err);
+}
+
+
+
+static gpg_error_t
+pass_status_thru (void *opaque, const char *line)
+{
+ assuan_context_t ctx = opaque;
+ char keyword[200];
+ int i;
+
+ if (line[0] == '#' && (!line[1] || spacep (line+1)))
+ {
+ /* We are called in convey comments mode. Now, if we see a
+ comment marker as keyword we forward the line verbatim to the
+ the caller. This way the comment lines from scdaemon won't
+ appear as status lines with keyword '#'. */
+ assuan_write_line (ctx, line);
+ }
+ else
+ {
+ for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++)
+ keyword[i] = *line;
+ keyword[i] = 0;
+
+ /* Truncate any remaining keyword stuff. */
+ for (; *line && !spacep (line); line++)
+ ;
+ while (spacep (line))
+ line++;
+
+ assuan_write_status (ctx, keyword, line);
+ }
+ return 0;
+}
+
+static gpg_error_t
+pass_data_thru (void *opaque, const void *buffer, size_t length)
+{
+ assuan_context_t ctx = opaque;
+
+ assuan_send_data (ctx, buffer, length);
+ return 0;
+}
+
+
+/* Send the line CMDLINE with command for the SCDdaemon to it and send
+ all status messages back. This command is used as a general quoting
+ mechanism to pass everything verbatim to SCDAEMON. The PIN
+ inquiry is handled inside gpg-agent. */
+int
+agent_card_scd (ctrl_t ctrl, const char *cmdline,
+ int (*getpin_cb)(void *, const char *,
+ const char *, char*, size_t),
+ void *getpin_cb_arg, void *assuan_context)
+{
+ int rc;
+ struct inq_needpin_parm_s inqparm;
+ int saveflag;
+
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ inqparm.ctx = ctrl->scd_local->ctx;
+ inqparm.getpin_cb = getpin_cb;
+ inqparm.getpin_cb_arg = getpin_cb_arg;
+ inqparm.getpin_cb_desc = NULL;
+ inqparm.passthru = assuan_context;
+ inqparm.keydata = NULL;
+ inqparm.keydatalen = 0;
+
+ saveflag = assuan_get_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS);
+ assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, 1);
+ rc = assuan_transact (ctrl->scd_local->ctx, cmdline,
+ pass_data_thru, assuan_context,
+ inq_needpin, &inqparm,
+ pass_status_thru, assuan_context);
+
+ assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, saveflag);
+ if (rc)
+ {
+ return unlock_scd (ctrl, rc);
+ }
+
+ return unlock_scd (ctrl, 0);
+}
+
+void
+agent_card_killscd (void)
+{
+ if (primary_scd_ctx == NULL)
+ return;
+ assuan_transact (primary_scd_ctx, "KILLSCD",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+}
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
new file mode 100644
index 0000000..21dd53c
--- /dev/null
+++ b/agent/command-ssh.c
@@ -0,0 +1,3870 @@
+/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol.
+ * Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc.
+ * Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs
+ are:
+
+ RFC-4250 - Protocol Assigned Numbers
+ RFC-4251 - Protocol Architecture
+ RFC-4252 - Authentication Protocol
+ RFC-4253 - Transport Layer Protocol
+ RFC-5656 - ECC support
+
+ The protocol for the agent is defined in:
+
+ https://tools.ietf.org/html/draft-miller-ssh-agent
+
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif /*!HAVE_W32_SYSTEM*/
+#ifdef HAVE_SYS_UCRED_H
+#include <sys/ucred.h>
+#endif
+#ifdef HAVE_UCRED_H
+#include <ucred.h>
+#endif
+
+#include "agent.h"
+
+#include "../common/i18n.h"
+#include "../common/util.h"
+#include "../common/ssh-utils.h"
+
+
+
+
+/* Request types. */
+#define SSH_REQUEST_REQUEST_IDENTITIES 11
+#define SSH_REQUEST_SIGN_REQUEST 13
+#define SSH_REQUEST_ADD_IDENTITY 17
+#define SSH_REQUEST_REMOVE_IDENTITY 18
+#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19
+#define SSH_REQUEST_LOCK 22
+#define SSH_REQUEST_UNLOCK 23
+#define SSH_REQUEST_ADD_ID_CONSTRAINED 25
+
+/* Options. */
+#define SSH_OPT_CONSTRAIN_LIFETIME 1
+#define SSH_OPT_CONSTRAIN_CONFIRM 2
+
+/* Response types. */
+#define SSH_RESPONSE_SUCCESS 6
+#define SSH_RESPONSE_FAILURE 5
+#define SSH_RESPONSE_IDENTITIES_ANSWER 12
+#define SSH_RESPONSE_SIGN_RESPONSE 14
+
+/* Other constants. */
+#define SSH_DSA_SIGNATURE_PADDING 20
+#define SSH_DSA_SIGNATURE_ELEMS 2
+#define SSH_AGENT_RSA_SHA2_256 0x02
+#define SSH_AGENT_RSA_SHA2_512 0x04
+#define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
+#define SPEC_FLAG_IS_ECDSA (1 << 1)
+#define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/
+#define SPEC_FLAG_WITH_CERT (1 << 7)
+
+/* The name of the control file. */
+#define SSH_CONTROL_FILE_NAME "sshcontrol"
+
+/* The blurb we put into the header of a newly created control file. */
+static const char sshcontrolblurb[] =
+"# List of allowed ssh keys. Only keys present in this file are used\n"
+"# in the SSH protocol. The ssh-add tool may add new entries to this\n"
+"# file to enable them; you may also add them manually. Comment\n"
+"# lines, like this one, as well as empty lines are ignored. Lines do\n"
+"# have a certain length limit but this is not serious limitation as\n"
+"# the format of the entries is fixed and checked by gpg-agent. A\n"
+"# non-comment line starts with optional white spaces, followed by the\n"
+"# keygrip of the key given as 40 hex digits, optionally followed by a\n"
+"# caching TTL in seconds, and another optional field for arbitrary\n"
+"# flags. Prepend the keygrip with an '!' mark to disable it.\n"
+"\n";
+
+
+/* Macros. */
+
+/* Return a new uint32 with b0 being the most significant byte and b3
+ being the least significant byte. */
+#define uint32_construct(b0, b1, b2, b3) \
+ ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
+
+
+
+
+/*
+ * Basic types.
+ */
+
+/* Type for a request handler. */
+typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+
+
+struct ssh_key_type_spec;
+typedef struct ssh_key_type_spec ssh_key_type_spec_t;
+
+/* Type, which is used for associating request handlers with the
+ appropriate request IDs. */
+typedef struct ssh_request_spec
+{
+ unsigned char type;
+ ssh_request_handler_t handler;
+ const char *identifier;
+ unsigned int secret_input;
+} ssh_request_spec_t;
+
+/* Type for "key modifier functions", which are necessary since
+ OpenSSH and GnuPG treat key material slightly different. A key
+ modifier is called right after a new key identity has been received
+ in order to "sanitize" the material. */
+typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems,
+ gcry_mpi_t *mpis);
+
+/* The encoding of a generated signature is dependent on the
+ algorithm; therefore algorithm specific signature encoding
+ functions are necessary. */
+typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t sig);
+
+/* Type, which is used for boundling all the algorithm specific
+ information together in a single object. */
+struct ssh_key_type_spec
+{
+ /* Algorithm identifier as used by OpenSSH. */
+ const char *ssh_identifier;
+
+ /* Human readable name of the algorithm. */
+ const char *name;
+
+ /* Algorithm identifier as used by GnuPG. */
+ int algo;
+
+ /* List of MPI names for secret keys; order matches the one of the
+ agent protocol. */
+ const char *elems_key_secret;
+
+ /* List of MPI names for public keys; order matches the one of the
+ agent protocol. */
+ const char *elems_key_public;
+
+ /* List of MPI names for signature data. */
+ const char *elems_signature;
+
+ /* List of MPI names for secret keys; order matches the one, which
+ is required by gpg-agent's key access layer. */
+ const char *elems_sexp_order;
+
+ /* Key modifier function. Key modifier functions are necessary in
+ order to fix any inconsistencies between the representation of
+ keys on the SSH and on the GnuPG side. */
+ ssh_key_modifier_t key_modifier;
+
+ /* Signature encoder function. Signature encoder functions are
+ necessary since the encoding of signatures depends on the used
+ algorithm. */
+ ssh_signature_encoder_t signature_encoder;
+
+ /* The name of the ECC curve or NULL for non-ECC algos. This is the
+ * canonical name for the curve as specified by RFC-5656. */
+ const char *curve_name;
+
+ /* An alias for curve_name or NULL. Actually this is Libcgrypt's
+ * primary name of the curve. */
+ const char *alt_curve_name;
+
+ /* The hash algorithm to be used with this key. 0 for using the
+ default. */
+ int hash_algo;
+
+ /* Misc flags. */
+ unsigned int flags;
+};
+
+
+/* Definition of an object to access the sshcontrol file. */
+struct ssh_control_file_s
+{
+ char *fname; /* Name of the file. */
+ estream_t fp; /* This is never NULL. */
+ int lnr; /* The current line number. */
+ struct {
+ int valid; /* True if the data of this structure is valid. */
+ int disabled; /* The item is disabled. */
+ int ttl; /* The TTL of the item. */
+ int confirm; /* The confirm flag is set. */
+ char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */
+ } item;
+};
+
+
+/* Prototypes. */
+static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+static gpg_error_t ssh_handler_lock (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+static gpg_error_t ssh_handler_unlock (ctrl_t ctrl,
+ estream_t request,
+ estream_t response);
+
+static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis);
+static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t signature);
+static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t signature);
+static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t signature);
+static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t signature);
+static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
+
+
+
+/* Global variables. */
+
+
+/* Associating request types with the corresponding request
+ handlers. */
+
+static const ssh_request_spec_t request_specs[] =
+ {
+#define REQUEST_SPEC_DEFINE(id, name, secret_input) \
+ { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input }
+
+ REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1),
+ REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0),
+ REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1),
+ REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1),
+ REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0),
+ REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0),
+ REQUEST_SPEC_DEFINE (LOCK, lock, 0),
+ REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0)
+#undef REQUEST_SPEC_DEFINE
+ };
+
+
+/* Table holding key type specifications. */
+static const ssh_key_type_spec_t ssh_key_types[] =
+ {
+ {
+ "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_eddsa,
+ "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA
+ },
+ {
+ "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
+ ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
+ NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2
+ },
+ {
+ "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
+ NULL, ssh_signature_encoder_dsa,
+ NULL, NULL, 0, 0
+ },
+ {
+ "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_ecdsa,
+ "nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
+ },
+ {
+ "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_ecdsa,
+ "nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
+ },
+ {
+ "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_ecdsa,
+ "nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
+ },
+ {
+ "ssh-ed25519-cert-v01@openssh.com", "Ed25519",
+ GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_eddsa,
+ "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
+ },
+ {
+ "ssh-rsa-cert-v01@openssh.com", "RSA",
+ GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
+ ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
+ NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
+ },
+ {
+ "ssh-dss-cert-v01@openssh.com", "DSA",
+ GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
+ NULL, ssh_signature_encoder_dsa,
+ NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
+ },
+ {
+ "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
+ GCRY_PK_ECC, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_ecdsa,
+ "nistp256", "NIST P-256", GCRY_MD_SHA256,
+ SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
+ },
+ {
+ "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
+ GCRY_PK_ECC, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_ecdsa,
+ "nistp384", "NIST P-384", GCRY_MD_SHA384,
+ SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
+ },
+ {
+ "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
+ GCRY_PK_ECC, "qd", "q", "rs", "qd",
+ NULL, ssh_signature_encoder_ecdsa,
+ "nistp521", "NIST P-521", GCRY_MD_SHA512,
+ SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
+ }
+ };
+
+
+
+
+
+/*
+ General utility functions.
+ */
+
+/* A secure realloc, i.e. it makes sure to allocate secure memory if A
+ is NULL. This is required because the standard gcry_realloc does
+ not know whether to allocate secure or normal if NULL is passed as
+ existing buffer. */
+static void *
+realloc_secure (void *a, size_t n)
+{
+ void *p;
+
+ if (a)
+ p = gcry_realloc (a, n);
+ else
+ p = gcry_malloc_secure (n);
+
+ return p;
+}
+
+
+/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns
+ * NULL if not found. If found the ssh indetifier is returned and a
+ * pointer to the canonical curve name as specified for ssh is stored
+ * at R_CANON_NAME. */
+static const char *
+ssh_identifier_from_curve_name (const char *curve_name,
+ const char **r_canon_name)
+{
+ int i;
+
+ for (i = 0; i < DIM (ssh_key_types); i++)
+ if (ssh_key_types[i].curve_name
+ && (!strcmp (ssh_key_types[i].curve_name, curve_name)
+ || (ssh_key_types[i].alt_curve_name
+ && !strcmp (ssh_key_types[i].alt_curve_name, curve_name))))
+ {
+ *r_canon_name = ssh_key_types[i].curve_name;
+ return ssh_key_types[i].ssh_identifier;
+ }
+
+ return NULL;
+}
+
+
+/*
+ Primitive I/O functions.
+ */
+
+
+/* Read a byte from STREAM, store it in B. */
+static gpg_error_t
+stream_read_byte (estream_t stream, unsigned char *b)
+{
+ gpg_error_t err;
+ int ret;
+
+ ret = es_fgetc (stream);
+ if (ret == EOF)
+ {
+ if (es_ferror (stream))
+ err = gpg_error_from_syserror ();
+ else
+ err = gpg_error (GPG_ERR_EOF);
+ *b = 0;
+ }
+ else
+ {
+ *b = ret & 0xFF;
+ err = 0;
+ }
+
+ return err;
+}
+
+/* Write the byte contained in B to STREAM. */
+static gpg_error_t
+stream_write_byte (estream_t stream, unsigned char b)
+{
+ gpg_error_t err;
+ int ret;
+
+ ret = es_fputc (b, stream);
+ if (ret == EOF)
+ err = gpg_error_from_syserror ();
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* Read a uint32 from STREAM, store it in UINT32. */
+static gpg_error_t
+stream_read_uint32 (estream_t stream, u32 *uint32)
+{
+ unsigned char buffer[4];
+ size_t bytes_read;
+ gpg_error_t err;
+ int ret;
+
+ ret = es_read (stream, buffer, sizeof (buffer), &bytes_read);
+ if (ret)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ if (bytes_read != sizeof (buffer))
+ err = gpg_error (GPG_ERR_EOF);
+ else
+ {
+ u32 n;
+
+ n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]);
+ *uint32 = n;
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+/* Write the uint32 contained in UINT32 to STREAM. */
+static gpg_error_t
+stream_write_uint32 (estream_t stream, u32 uint32)
+{
+ unsigned char buffer[4];
+ gpg_error_t err;
+ int ret;
+
+ buffer[0] = uint32 >> 24;
+ buffer[1] = uint32 >> 16;
+ buffer[2] = uint32 >> 8;
+ buffer[3] = uint32 >> 0;
+
+ ret = es_write (stream, buffer, sizeof (buffer), NULL);
+ if (ret)
+ err = gpg_error_from_syserror ();
+ else
+ err = 0;
+
+ return err;
+}
+
+/* Read SIZE bytes from STREAM into BUFFER. */
+static gpg_error_t
+stream_read_data (estream_t stream, unsigned char *buffer, size_t size)
+{
+ gpg_error_t err;
+ size_t bytes_read;
+ int ret;
+
+ ret = es_read (stream, buffer, size, &bytes_read);
+ if (ret)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ if (bytes_read != size)
+ err = gpg_error (GPG_ERR_EOF);
+ else
+ err = 0;
+ }
+
+ return err;
+}
+
+/* Skip over SIZE bytes from STREAM. */
+static gpg_error_t
+stream_read_skip (estream_t stream, size_t size)
+{
+ char buffer[128];
+ size_t bytes_to_read, bytes_read;
+ int ret;
+
+ do
+ {
+ bytes_to_read = size;
+ if (bytes_to_read > sizeof buffer)
+ bytes_to_read = sizeof buffer;
+
+ ret = es_read (stream, buffer, bytes_to_read, &bytes_read);
+ if (ret)
+ return gpg_error_from_syserror ();
+ else if (bytes_read != bytes_to_read)
+ return gpg_error (GPG_ERR_EOF);
+ else
+ size -= bytes_to_read;
+ }
+ while (size);
+
+ return 0;
+}
+
+
+/* Write SIZE bytes from BUFFER to STREAM. */
+static gpg_error_t
+stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
+{
+ gpg_error_t err;
+ int ret;
+
+ ret = es_write (stream, buffer, size, NULL);
+ if (ret)
+ err = gpg_error_from_syserror ();
+ else
+ err = 0;
+
+ return err;
+}
+
+/* Read a binary string from STREAM into STRING, store size of string
+ in STRING_SIZE. Append a hidden nul so that the result may
+ directly be used as a C string. Depending on SECURE use secure
+ memory for STRING. If STRING is NULL do only a dummy read. */
+static gpg_error_t
+stream_read_string (estream_t stream, unsigned int secure,
+ unsigned char **string, u32 *string_size)
+{
+ gpg_error_t err;
+ unsigned char *buffer = NULL;
+ u32 length = 0;
+
+ if (string_size)
+ *string_size = 0;
+
+ /* Read string length. */
+ err = stream_read_uint32 (stream, &length);
+ if (err)
+ goto out;
+
+ if (string)
+ {
+ /* Allocate space. */
+ if (secure)
+ buffer = xtrymalloc_secure (length + 1);
+ else
+ buffer = xtrymalloc (length + 1);
+ if (! buffer)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* Read data. */
+ err = length? stream_read_data (stream, buffer, length) : 0;
+ if (err)
+ goto out;
+
+ /* Finalize string object. */
+ buffer[length] = 0;
+ *string = buffer;
+ }
+ else /* Dummy read requested. */
+ {
+ err = length? stream_read_skip (stream, length) : 0;
+ if (err)
+ goto out;
+ }
+
+ if (string_size)
+ *string_size = length;
+
+ out:
+
+ if (err)
+ xfree (buffer);
+
+ return err;
+}
+
+
+/* Read a binary string from STREAM and store it as an opaque MPI at
+ R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
+ Depending on SECURE use secure memory. If the string is too large
+ for key material return an error. */
+static gpg_error_t
+stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
+{
+ gpg_error_t err;
+ unsigned char *buffer = NULL;
+ u32 length = 0;
+
+ *r_mpi = NULL;
+
+ /* Read string length. */
+ err = stream_read_uint32 (stream, &length);
+ if (err)
+ goto leave;
+
+ /* To avoid excessive use of secure memory we check that an MPI is
+ not too large. */
+ if (length > (4096/8) + 8)
+ {
+ log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
+ err = GPG_ERR_TOO_LARGE;
+ goto leave;
+ }
+
+ /* Allocate space. */
+ if (secure)
+ buffer = xtrymalloc_secure (length+1);
+ else
+ buffer = xtrymalloc (length+1);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Read data. */
+ err = stream_read_data (stream, buffer + 1, length);
+ if (err)
+ goto leave;
+
+ buffer[0] = 0x40;
+ *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
+ buffer = NULL;
+
+ leave:
+ xfree (buffer);
+ return err;
+}
+
+
+/* Read a C-string from STREAM, store copy in STRING. */
+static gpg_error_t
+stream_read_cstring (estream_t stream, char **string)
+{
+ return stream_read_string (stream, 0, (unsigned char **)string, NULL);
+}
+
+
+/* Write a binary string from STRING of size STRING_N to STREAM. */
+static gpg_error_t
+stream_write_string (estream_t stream,
+ const unsigned char *string, u32 string_n)
+{
+ gpg_error_t err;
+
+ err = stream_write_uint32 (stream, string_n);
+ if (err)
+ goto out;
+
+ err = stream_write_data (stream, string, string_n);
+
+ out:
+
+ return err;
+}
+
+/* Write a C-string from STRING to STREAM. */
+static gpg_error_t
+stream_write_cstring (estream_t stream, const char *string)
+{
+ gpg_error_t err;
+
+ err = stream_write_string (stream,
+ (const unsigned char *) string, strlen (string));
+
+ return err;
+}
+
+/* Read an MPI from STREAM, store it in MPINT. Depending on SECURE
+ use secure memory. */
+static gpg_error_t
+stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint)
+{
+ unsigned char *mpi_data;
+ u32 mpi_data_size;
+ gpg_error_t err;
+ gcry_mpi_t mpi;
+
+ mpi_data = NULL;
+
+ err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size);
+ if (err)
+ goto out;
+
+ /* To avoid excessive use of secure memory we check that an MPI is
+ not too large. */
+ if (mpi_data_size > 520)
+ {
+ log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
+ err = GPG_ERR_TOO_LARGE;
+ goto out;
+ }
+
+ err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL);
+ if (err)
+ goto out;
+
+ *mpint = mpi;
+
+ out:
+
+ xfree (mpi_data);
+
+ return err;
+}
+
+/* Write the MPI contained in MPINT to STREAM. */
+static gpg_error_t
+stream_write_mpi (estream_t stream, gcry_mpi_t mpint)
+{
+ unsigned char *mpi_buffer;
+ size_t mpi_buffer_n;
+ gpg_error_t err;
+
+ mpi_buffer = NULL;
+
+ err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint);
+ if (err)
+ goto out;
+
+ err = stream_write_string (stream, mpi_buffer, mpi_buffer_n);
+
+ out:
+
+ xfree (mpi_buffer);
+
+ return err;
+}
+
+
+/* Copy data from SRC to DST until EOF is reached. */
+static gpg_error_t
+stream_copy (estream_t dst, estream_t src)
+{
+ char buffer[BUFSIZ];
+ size_t bytes_read;
+ gpg_error_t err;
+ int ret;
+
+ err = 0;
+ while (1)
+ {
+ ret = es_read (src, buffer, sizeof (buffer), &bytes_read);
+ if (ret || (! bytes_read))
+ {
+ if (ret)
+ err = gpg_error_from_syserror ();
+ break;
+ }
+ ret = es_write (dst, buffer, bytes_read, NULL);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ break;
+ }
+ }
+
+ return err;
+}
+
+/* Open the ssh control file and create it if not available. With
+ APPEND passed as true the file will be opened in append mode,
+ otherwise in read only mode. On success 0 is returned and a new
+ control file object stored at R_CF. On error an error code is
+ returned and NULL is stored at R_CF. */
+static gpg_error_t
+open_control_file (ssh_control_file_t *r_cf, int append)
+{
+ gpg_error_t err;
+ ssh_control_file_t cf;
+
+ cf = xtrycalloc (1, sizeof *cf);
+ if (!cf)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Note: As soon as we start to use non blocking functions here
+ (i.e. where Pth might switch threads) we need to employ a
+ mutex. */
+ cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
+ if (!cf->fname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ /* FIXME: With "a+" we are not able to check whether this will
+ be created and thus the blurb needs to be written first. */
+ cf->fp = es_fopen (cf->fname, append? "a+":"r");
+ if (!cf->fp && errno == ENOENT)
+ {
+ estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r");
+ if (!stream)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't create '%s': %s\n"),
+ cf->fname, gpg_strerror (err));
+ goto leave;
+ }
+ es_fputs (sshcontrolblurb, stream);
+ es_fclose (stream);
+ cf->fp = es_fopen (cf->fname, append? "a+":"r");
+ }
+
+ if (!cf->fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't open '%s': %s\n"),
+ cf->fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ err = 0;
+
+ leave:
+ if (err && cf)
+ {
+ if (cf->fp)
+ es_fclose (cf->fp);
+ xfree (cf->fname);
+ xfree (cf);
+ }
+ else
+ *r_cf = cf;
+
+ return err;
+}
+
+
+static void
+rewind_control_file (ssh_control_file_t cf)
+{
+ es_fseek (cf->fp, 0, SEEK_SET);
+ cf->lnr = 0;
+ es_clearerr (cf->fp);
+}
+
+
+static void
+close_control_file (ssh_control_file_t cf)
+{
+ if (!cf)
+ return;
+ es_fclose (cf->fp);
+ xfree (cf->fname);
+ xfree (cf);
+}
+
+
+
+/* Read the next line from the control file and store the data in CF.
+ Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
+static gpg_error_t
+read_control_file_item (ssh_control_file_t cf)
+{
+ int c, i, n;
+ char *p, *pend, line[256];
+ long ttl = 0;
+
+ cf->item.valid = 0;
+ es_clearerr (cf->fp);
+
+ do
+ {
+ if (!es_fgets (line, DIM(line)-1, cf->fp) )
+ {
+ if (es_feof (cf->fp))
+ return gpg_error (GPG_ERR_EOF);
+ return gpg_error_from_syserror ();
+ }
+ cf->lnr++;
+
+ if (!*line || line[strlen(line)-1] != '\n')
+ {
+ /* Eat until end of line */
+ while ((c = es_getc (cf->fp)) != EOF && c != '\n')
+ ;
+ return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+ : GPG_ERR_INCOMPLETE_LINE);
+ }
+
+ /* Allow for empty lines and spaces */
+ for (p=line; spacep (p); p++)
+ ;
+ }
+ while (!*p || *p == '\n' || *p == '#');
+
+ cf->item.disabled = 0;
+ if (*p == '!')
+ {
+ cf->item.disabled = 1;
+ for (p++; spacep (p); p++)
+ ;
+ }
+
+ for (i=0; hexdigitp (p) && i < 40; p++, i++)
+ cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p);
+ cf->item.hexgrip[i] = 0;
+ if (i != 40 || !(spacep (p) || *p == '\n'))
+ {
+ log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr);
+ return gpg_error (GPG_ERR_BAD_DATA);
+ }
+
+ ttl = strtol (p, &pend, 10);
+ p = pend;
+ if (!(spacep (p) || *p == '\n') || (int)ttl < -1)
+ {
+ log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr);
+ cf->item.ttl = 0;
+ }
+ cf->item.ttl = ttl;
+
+ /* Now check for key-value pairs of the form NAME[=VALUE]. */
+ cf->item.confirm = 0;
+ while (*p)
+ {
+ for (; spacep (p) && *p != '\n'; p++)
+ ;
+ if (!*p || *p == '\n')
+ break;
+ n = strcspn (p, "= \t\n");
+ if (p[n] == '=')
+ {
+ log_error ("%s:%d: assigning a value to a flag is not yet supported; "
+ "flag ignored\n", cf->fname, cf->lnr);
+ p++;
+ }
+ else if (n == 7 && !memcmp (p, "confirm", 7))
+ {
+ cf->item.confirm = 1;
+ }
+ else
+ log_error ("%s:%d: invalid flag '%.*s'; ignored\n",
+ cf->fname, cf->lnr, n, p);
+ p += n;
+ }
+
+ /* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */
+ /* cf->fname, cf->lnr, */
+ /* cf->item.hexgrip, cf->item.ttl, */
+ /* cf->item.disabled? " disabled":"", */
+ /* cf->item.confirm? " confirm":""); */
+
+ cf->item.valid = 1;
+ return 0; /* Okay: valid entry found. */
+}
+
+
+
+/* Search the control file CF from the beginning until a matching
+ HEXGRIP is found; return success in this case and store true at
+ DISABLED if the found key has been disabled. If R_TTL is not NULL
+ a specified TTL for that key is stored there. If R_CONFIRM is not
+ NULL it is set to 1 if the key has the confirm flag set. */
+static gpg_error_t
+search_control_file (ssh_control_file_t cf, const char *hexgrip,
+ int *r_disabled, int *r_ttl, int *r_confirm)
+{
+ gpg_error_t err;
+
+ assert (strlen (hexgrip) == 40 );
+
+ if (r_disabled)
+ *r_disabled = 0;
+ if (r_ttl)
+ *r_ttl = 0;
+ if (r_confirm)
+ *r_confirm = 0;
+
+ rewind_control_file (cf);
+ while (!(err=read_control_file_item (cf)))
+ {
+ if (!cf->item.valid)
+ continue; /* Should not happen. */
+ if (!strcmp (hexgrip, cf->item.hexgrip))
+ break;
+ }
+ if (!err)
+ {
+ if (r_disabled)
+ *r_disabled = cf->item.disabled;
+ if (r_ttl)
+ *r_ttl = cf->item.ttl;
+ if (r_confirm)
+ *r_confirm = cf->item.confirm;
+ }
+ return err;
+}
+
+
+
+/* Add an entry to the control file to mark the key with the keygrip
+ HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
+ for it. FMTFPR is the fingerprint string. This function is in
+ general used to add a key received through the ssh-add function.
+ We can assume that the user wants to allow ssh using this key. */
+static gpg_error_t
+add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
+ const char *hexgrip, gcry_sexp_t key,
+ int ttl, int confirm)
+{
+ gpg_error_t err;
+ ssh_control_file_t cf;
+ int disabled;
+ char *fpr_md5 = NULL;
+ char *fpr_sha256 = NULL;
+
+ (void)ctrl;
+
+ err = open_control_file (&cf, 1);
+ if (err)
+ return err;
+
+ err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
+ if (err && gpg_err_code(err) == GPG_ERR_EOF)
+ {
+ struct tm *tp;
+ time_t atime = time (NULL);
+
+ err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5);
+ if (err)
+ goto out;
+
+ err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256);
+ if (err)
+ goto out;
+
+ /* Not yet in the file - add it. Because the file has been
+ opened in append mode, we simply need to write to it. */
+ tp = localtime (&atime);
+ es_fprintf (cf->fp,
+ ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
+ "# Fingerprints: %s\n"
+ "# %s\n"
+ "%s %d%s\n"),
+ spec->name,
+ 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
+ tp->tm_hour, tp->tm_min, tp->tm_sec,
+ fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":"");
+
+ }
+ out:
+ xfree (fpr_md5);
+ xfree (fpr_sha256);
+ close_control_file (cf);
+ return 0;
+}
+
+
+/* Scan the sshcontrol file and return the TTL. */
+static int
+ttl_from_sshcontrol (const char *hexgrip)
+{
+ ssh_control_file_t cf;
+ int disabled, ttl;
+
+ if (!hexgrip || strlen (hexgrip) != 40)
+ return 0; /* Wrong input: Use global default. */
+
+ if (open_control_file (&cf, 0))
+ return 0; /* Error: Use the global default TTL. */
+
+ if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL)
+ || disabled)
+ ttl = 0; /* Use the global default if not found or disabled. */
+
+ close_control_file (cf);
+
+ return ttl;
+}
+
+
+/* Scan the sshcontrol file and return the confirm flag. */
+static int
+confirm_flag_from_sshcontrol (const char *hexgrip)
+{
+ ssh_control_file_t cf;
+ int disabled, confirm;
+
+ if (!hexgrip || strlen (hexgrip) != 40)
+ return 1; /* Wrong input: Better ask for confirmation. */
+
+ if (open_control_file (&cf, 0))
+ return 1; /* Error: Better ask for confirmation. */
+
+ if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm)
+ || disabled)
+ confirm = 0; /* If not found or disabled, there is no reason to
+ ask for confirmation. */
+
+ close_control_file (cf);
+
+ return confirm;
+}
+
+
+
+
+/* Open the ssh control file for reading. This is a public version of
+ open_control_file. The caller must use ssh_close_control_file to
+ release the returned handle. */
+ssh_control_file_t
+ssh_open_control_file (void)
+{
+ ssh_control_file_t cf;
+
+ /* Then look at all the registered and non-disabled keys. */
+ if (open_control_file (&cf, 0))
+ return NULL;
+ return cf;
+}
+
+/* Close an ssh control file handle. This is the public version of
+ close_control_file. CF may be NULL. */
+void
+ssh_close_control_file (ssh_control_file_t cf)
+{
+ close_control_file (cf);
+}
+
+/* Read the next item from the ssh control file. The function returns
+ 0 if a item was read, GPG_ERR_EOF on eof or another error value.
+ R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
+ R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
+ file; they are only set on success. */
+gpg_error_t
+ssh_read_control_file (ssh_control_file_t cf,
+ char *r_hexgrip,
+ int *r_disabled, int *r_ttl, int *r_confirm)
+{
+ gpg_error_t err;
+
+ do
+ err = read_control_file_item (cf);
+ while (!err && !cf->item.valid);
+ if (!err)
+ {
+ if (r_hexgrip)
+ strcpy (r_hexgrip, cf->item.hexgrip);
+ if (r_disabled)
+ *r_disabled = cf->item.disabled;
+ if (r_ttl)
+ *r_ttl = cf->item.ttl;
+ if (r_confirm)
+ *r_confirm = cf->item.confirm;
+ }
+ return err;
+}
+
+
+/* Search for a key with HEXGRIP in sshcontrol and return all
+ info. */
+gpg_error_t
+ssh_search_control_file (ssh_control_file_t cf,
+ const char *hexgrip,
+ int *r_disabled, int *r_ttl, int *r_confirm)
+{
+ gpg_error_t err;
+ int i;
+ const char *s;
+ char uphexgrip[41];
+
+ /* We need to make sure that HEXGRIP is all uppercase. The easiest
+ way to do this and also check its length is by copying to a
+ second buffer. */
+ for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
+ uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
+ uphexgrip[i] = 0;
+ if (i != 40)
+ err = gpg_error (GPG_ERR_INV_LENGTH);
+ else
+ err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ return err;
+}
+
+
+
+
+/*
+
+ MPI lists.
+
+ */
+
+/* Free the list of MPIs MPI_LIST. */
+static void
+mpint_list_free (gcry_mpi_t *mpi_list)
+{
+ if (mpi_list)
+ {
+ unsigned int i;
+
+ for (i = 0; mpi_list[i]; i++)
+ gcry_mpi_release (mpi_list[i]);
+ xfree (mpi_list);
+ }
+}
+
+/* Receive key material MPIs from STREAM according to KEY_SPEC;
+ depending on SECRET expect a public key or secret key. CERT is the
+ certificate blob used if KEY_SPEC indicates the certificate format;
+ it needs to be positioned to the end of the nonce. The newly
+ allocated list of MPIs is stored in MPI_LIST. Returns usual error
+ code. */
+static gpg_error_t
+ssh_receive_mpint_list (estream_t stream, int secret,
+ ssh_key_type_spec_t *spec, estream_t cert,
+ gcry_mpi_t **mpi_list)
+{
+ const char *elems_public;
+ unsigned int elems_n;
+ const char *elems;
+ int elem_is_secret;
+ gcry_mpi_t *mpis = NULL;
+ gpg_error_t err = 0;
+ unsigned int i;
+
+ if (secret)
+ elems = spec->elems_key_secret;
+ else
+ elems = spec->elems_key_public;
+ elems_n = strlen (elems);
+ elems_public = spec->elems_key_public;
+
+ /* Check that either both, CERT and the WITH_CERT flag, are given or
+ none of them. */
+ if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
+ {
+ err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+ goto out;
+ }
+
+ mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
+ if (!mpis)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ elem_is_secret = 0;
+ for (i = 0; i < elems_n; i++)
+ {
+ if (secret)
+ elem_is_secret = !strchr (elems_public, elems[i]);
+
+ if (cert && !elem_is_secret)
+ err = stream_read_mpi (cert, elem_is_secret, &mpis[i]);
+ else
+ err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
+ if (err)
+ goto out;
+ }
+
+ *mpi_list = mpis;
+ mpis = NULL;
+
+ out:
+ if (err)
+ mpint_list_free (mpis);
+
+ return err;
+}
+
+
+
+/* Key modifier function for RSA. */
+static gpg_error_t
+ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis)
+{
+ gcry_mpi_t p;
+ gcry_mpi_t q;
+ gcry_mpi_t u;
+
+ if (strcmp (elems, "nedupq"))
+ /* Modifying only necessary for secret keys. */
+ goto out;
+
+ u = mpis[3];
+ p = mpis[4];
+ q = mpis[5];
+
+ if (gcry_mpi_cmp (p, q) > 0)
+ {
+ /* P shall be smaller then Q! Swap primes. iqmp becomes u. */
+ gcry_mpi_t tmp;
+
+ tmp = mpis[4];
+ mpis[4] = mpis[5];
+ mpis[5] = tmp;
+ }
+ else
+ /* U needs to be recomputed. */
+ gcry_mpi_invm (u, p, q);
+
+ out:
+
+ return 0;
+}
+
+/* Signature encoder function for RSA. */
+static gpg_error_t
+ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t s_signature)
+{
+ gpg_error_t err = 0;
+ gcry_sexp_t valuelist = NULL;
+ gcry_sexp_t sublist = NULL;
+ gcry_mpi_t sig_value = NULL;
+ gcry_mpi_t *mpis = NULL;
+ const char *elems;
+ size_t elems_n;
+ int i;
+
+ unsigned char *data;
+ size_t data_n;
+ gcry_mpi_t s;
+
+ valuelist = gcry_sexp_nth (s_signature, 1);
+ if (!valuelist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ elems = spec->elems_signature;
+ elems_n = strlen (elems);
+
+ mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
+ if (!mpis)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ for (i = 0; i < elems_n; i++)
+ {
+ sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+ if (!sublist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ break;
+ }
+
+ sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
+ if (!sig_value)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
+ break;
+ }
+ gcry_sexp_release (sublist);
+ sublist = NULL;
+
+ mpis[i] = sig_value;
+ }
+ if (err)
+ goto out;
+
+ /* RSA specific */
+ s = mpis[0];
+
+ err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s);
+ if (err)
+ goto out;
+
+ err = stream_write_string (signature_blob, data, data_n);
+ xfree (data);
+
+ out:
+ gcry_sexp_release (valuelist);
+ gcry_sexp_release (sublist);
+ mpint_list_free (mpis);
+ return err;
+}
+
+
+/* Signature encoder function for DSA. */
+static gpg_error_t
+ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
+ estream_t signature_blob,
+ gcry_sexp_t s_signature)
+{
+ gpg_error_t err = 0;
+ gcry_sexp_t valuelist = NULL;
+ gcry_sexp_t sublist = NULL;
+ gcry_mpi_t sig_value = NULL;
+ gcry_mpi_t *mpis = NULL;
+ const char *elems;
+ size_t elems_n;
+ int i;
+
+ unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
+ unsigned char *data = NULL;
+ size_t data_n;
+
+ valuelist = gcry_sexp_nth (s_signature, 1);
+ if (!valuelist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ elems = spec->elems_signature;
+ elems_n = strlen (elems);
+
+ mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
+ if (!mpis)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ for (i = 0; i < elems_n; i++)
+ {
+ sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+ if (!sublist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ break;
+ }
+
+ sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
+ if (!sig_value)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
+ break;
+ }
+ gcry_sexp_release (sublist);
+ sublist = NULL;
+
+ mpis[i] = sig_value;
+ }
+ if (err)
+ goto out;
+
+ /* DSA specific code. */
+
+ /* FIXME: Why this complicated code? Why collecting boths mpis in a
+ buffer instead of writing them out one after the other? */
+ for (i = 0; i < 2; i++)
+ {
+ err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]);
+ if (err)
+ break;
+
+ if (data_n > SSH_DSA_SIGNATURE_PADDING)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
+ break;
+ }
+
+ memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0,
+ SSH_DSA_SIGNATURE_PADDING - data_n);
+ memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING)
+ + (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n);
+
+ xfree (data);
+ data = NULL;
+ }
+ if (err)
+ goto out;
+
+ err = stream_write_string (signature_blob, buffer, sizeof (buffer));
+
+ out:
+ xfree (data);
+ gcry_sexp_release (valuelist);
+ gcry_sexp_release (sublist);
+ mpint_list_free (mpis);
+ return err;
+}
+
+
+/* Signature encoder function for ECDSA. */
+static gpg_error_t
+ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
+ estream_t stream, gcry_sexp_t s_signature)
+{
+ gpg_error_t err = 0;
+ gcry_sexp_t valuelist = NULL;
+ gcry_sexp_t sublist = NULL;
+ gcry_mpi_t sig_value = NULL;
+ gcry_mpi_t *mpis = NULL;
+ const char *elems;
+ size_t elems_n;
+ int i;
+
+ unsigned char *data[2] = {NULL, NULL};
+ size_t data_n[2];
+ size_t innerlen;
+
+ valuelist = gcry_sexp_nth (s_signature, 1);
+ if (!valuelist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ elems = spec->elems_signature;
+ elems_n = strlen (elems);
+
+ mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
+ if (!mpis)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ for (i = 0; i < elems_n; i++)
+ {
+ sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+ if (!sublist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ break;
+ }
+
+ sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
+ if (!sig_value)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
+ break;
+ }
+ gcry_sexp_release (sublist);
+ sublist = NULL;
+
+ mpis[i] = sig_value;
+ }
+ if (err)
+ goto out;
+
+ /* ECDSA specific */
+
+ innerlen = 0;
+ for (i = 0; i < DIM(data); i++)
+ {
+ err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]);
+ if (err)
+ goto out;
+ innerlen += 4 + data_n[i];
+ }
+
+ err = stream_write_uint32 (stream, innerlen);
+ if (err)
+ goto out;
+
+ for (i = 0; i < DIM(data); i++)
+ {
+ err = stream_write_string (stream, data[i], data_n[i]);
+ if (err)
+ goto out;
+ }
+
+ out:
+ for (i = 0; i < DIM(data); i++)
+ xfree (data[i]);
+ gcry_sexp_release (valuelist);
+ gcry_sexp_release (sublist);
+ mpint_list_free (mpis);
+ return err;
+}
+
+
+/* Signature encoder function for EdDSA. */
+static gpg_error_t
+ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
+ estream_t stream, gcry_sexp_t s_signature)
+{
+ gpg_error_t err = 0;
+ gcry_sexp_t valuelist = NULL;
+ gcry_sexp_t sublist = NULL;
+ const char *elems;
+ size_t elems_n;
+ int i;
+
+ unsigned char *data[2] = {NULL, NULL};
+ size_t data_n[2];
+ size_t totallen = 0;
+
+ valuelist = gcry_sexp_nth (s_signature, 1);
+ if (!valuelist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ elems = spec->elems_signature;
+ elems_n = strlen (elems);
+
+ if (elems_n != DIM(data))
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ for (i = 0; i < DIM(data); i++)
+ {
+ sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+ if (!sublist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ break;
+ }
+
+ data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
+ if (!data[i])
+ {
+ err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
+ break;
+ }
+ totallen += data_n[i];
+ gcry_sexp_release (sublist);
+ sublist = NULL;
+ }
+ if (err)
+ goto out;
+
+ err = stream_write_uint32 (stream, totallen);
+ if (err)
+ goto out;
+
+ for (i = 0; i < DIM(data); i++)
+ {
+ err = stream_write_data (stream, data[i], data_n[i]);
+ if (err)
+ goto out;
+ }
+
+ out:
+ for (i = 0; i < DIM(data); i++)
+ xfree (data[i]);
+ gcry_sexp_release (valuelist);
+ gcry_sexp_release (sublist);
+ return err;
+}
+
+
+/*
+ S-Expressions.
+ */
+
+
+/* This function constructs a new S-Expression for the key identified
+ by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to
+ be stored at R_SEXP. Returns an error code. */
+static gpg_error_t
+sexp_key_construct (gcry_sexp_t *r_sexp,
+ ssh_key_type_spec_t key_spec, int secret,
+ const char *curve_name, gcry_mpi_t *mpis,
+ const char *comment)
+{
+ gpg_error_t err;
+ gcry_sexp_t sexp_new = NULL;
+ void *formatbuf = NULL;
+ void **arg_list = NULL;
+ estream_t format = NULL;
+ char *algo_name = NULL;
+
+ /* We can't encode an empty string in an S-expression, thus to keep
+ * the code simple we use "(none)" instead. */
+ if (!comment || !*comment)
+ comment = "(none)";
+
+ if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
+ {
+ /* It is much easier and more readable to use a separate code
+ path for EdDSA. */
+ if (!curve_name)
+ err = gpg_error (GPG_ERR_INV_CURVE);
+ else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE))
+ err = gpg_error (GPG_ERR_BAD_PUBKEY);
+ else if (secret
+ && (!mpis[1]
+ || !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE)))
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else if (secret)
+ err = gcry_sexp_build (&sexp_new, NULL,
+ "(private-key(ecc(curve %s)"
+ "(flags eddsa)(q %m)(d %m))"
+ "(comment%s))",
+ curve_name,
+ mpis[0], mpis[1],
+ comment);
+ else
+ err = gcry_sexp_build (&sexp_new, NULL,
+ "(public-key(ecc(curve %s)"
+ "(flags eddsa)(q %m))"
+ "(comment%s))",
+ curve_name,
+ mpis[0],
+ comment);
+
+ }
+ else
+ {
+ const char *key_identifier[] = { "public-key", "private-key" };
+ int arg_idx;
+ const char *elems;
+ size_t elems_n;
+ unsigned int i, j;
+
+ if (secret)
+ elems = key_spec.elems_sexp_order;
+ else
+ elems = key_spec.elems_key_public;
+ elems_n = strlen (elems);
+
+ format = es_fopenmem (0, "a+b");
+ if (!format)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* Key identifier, algorithm identifier, mpis, comment, and a NULL
+ as a safeguard. */
+ arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1));
+ if (!arg_list)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ arg_idx = 0;
+
+ es_fputs ("(%s(%s", format);
+ arg_list[arg_idx++] = &key_identifier[secret];
+ algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
+ if (!algo_name)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ strlwr (algo_name);
+ arg_list[arg_idx++] = &algo_name;
+ if (curve_name)
+ {
+ es_fputs ("(curve%s)", format);
+ arg_list[arg_idx++] = &curve_name;
+ }
+
+ for (i = 0; i < elems_n; i++)
+ {
+ es_fprintf (format, "(%c%%m)", elems[i]);
+ if (secret)
+ {
+ for (j = 0; j < elems_n; j++)
+ if (key_spec.elems_key_secret[j] == elems[i])
+ break;
+ }
+ else
+ j = i;
+ arg_list[arg_idx++] = &mpis[j];
+ }
+ es_fputs (")(comment%s))", format);
+ arg_list[arg_idx++] = &comment;
+ arg_list[arg_idx] = NULL;
+
+ es_putc (0, format);
+ if (es_ferror (format))
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ if (es_fclose_snatch (format, &formatbuf, NULL))
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ format = NULL;
+
+ err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list);
+ }
+
+ if (!err)
+ *r_sexp = sexp_new;
+
+ out:
+ es_fclose (format);
+ xfree (arg_list);
+ xfree (formatbuf);
+ xfree (algo_name);
+
+ return err;
+}
+
+
+/* This function extracts the key from the s-expression SEXP according
+ to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If
+ WITH_SECRET is true, the secret key parts are also extracted if
+ possible. Returns 0 on success or an error code. Note that data
+ stored at R_BLOB must be freed using es_free! */
+static gpg_error_t
+ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
+ ssh_key_type_spec_t key_spec,
+ void **r_blob, size_t *r_blob_size)
+{
+ gpg_error_t err = 0;
+ gcry_sexp_t value_list = NULL;
+ gcry_sexp_t value_pair = NULL;
+ estream_t stream = NULL;
+ void *blob = NULL;
+ size_t blob_size;
+ const char *elems, *p_elems;
+ const char *data;
+ size_t datalen;
+
+ *r_blob = NULL;
+ *r_blob_size = 0;
+
+ stream = es_fopenmem (0, "r+b");
+ if (!stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* Get the type of the key expression. */
+ data = gcry_sexp_nth_data (sexp, 0, &datalen);
+ if (!data)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ if ((datalen == 10 && !strncmp (data, "public-key", 10))
+ || (datalen == 21 && !strncmp (data, "protected-private-key", 21))
+ || (datalen == 20 && !strncmp (data, "shadowed-private-key", 20)))
+ elems = key_spec.elems_key_public;
+ else if (datalen == 11 && !strncmp (data, "private-key", 11))
+ elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public;
+ else
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ /* Get key value list. */
+ value_list = gcry_sexp_cadr (sexp);
+ if (!value_list)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ /* Write the ssh algorithm identifier. */
+ if ((key_spec.flags & SPEC_FLAG_IS_ECDSA))
+ {
+ /* Map the curve name to the ssh name. */
+ const char *name, *sshname, *canon_name;
+
+ name = gcry_pk_get_curve (sexp, 0, NULL);
+ if (!name)
+ {
+ err = gpg_error (GPG_ERR_INV_CURVE);
+ goto out;
+ }
+
+ sshname = ssh_identifier_from_curve_name (name, &canon_name);
+ if (!sshname)
+ {
+ err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+ goto out;
+ }
+ err = stream_write_cstring (stream, sshname);
+ if (err)
+ goto out;
+ err = stream_write_cstring (stream, canon_name);
+ if (err)
+ goto out;
+ }
+ else
+ {
+ /* Note: This is also used for EdDSA. */
+ err = stream_write_cstring (stream, key_spec.ssh_identifier);
+ if (err)
+ goto out;
+ }
+
+ /* Write the parameters. */
+ for (p_elems = elems; *p_elems; p_elems++)
+ {
+ gcry_sexp_release (value_pair);
+ value_pair = gcry_sexp_find_token (value_list, p_elems, 1);
+ if (!value_pair)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+ if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
+ {
+
+ data = gcry_sexp_nth_data (value_pair, 1, &datalen);
+ if (!data)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+ if (*p_elems == 'q' && datalen)
+ { /* Remove the prefix 0x40. */
+ data++;
+ datalen--;
+ }
+ err = stream_write_string (stream, data, datalen);
+ if (err)
+ goto out;
+ }
+ else
+ {
+ gcry_mpi_t mpi;
+
+ /* Note that we need to use STD format; i.e. prepend a 0x00
+ to indicate a positive number if the high bit is set. */
+ mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
+ if (!mpi)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+ err = stream_write_mpi (stream, mpi);
+ gcry_mpi_release (mpi);
+ if (err)
+ goto out;
+ }
+ }
+
+ if (es_fclose_snatch (stream, &blob, &blob_size))
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ stream = NULL;
+
+ *r_blob = blob;
+ blob = NULL;
+ *r_blob_size = blob_size;
+
+ out:
+ gcry_sexp_release (value_list);
+ gcry_sexp_release (value_pair);
+ es_fclose (stream);
+ es_free (blob);
+
+ return err;
+}
+
+
+/*
+
+ Key I/O.
+
+*/
+
+/* Search for a key specification entry. If SSH_NAME is not NULL,
+ search for an entry whose "ssh_name" is equal to SSH_NAME;
+ otherwise, search for an entry whose algorithm is equal to ALGO.
+ Store found entry in SPEC on success, return error otherwise. */
+static gpg_error_t
+ssh_key_type_lookup (const char *ssh_name, int algo,
+ ssh_key_type_spec_t *spec)
+{
+ gpg_error_t err;
+ unsigned int i;
+
+ for (i = 0; i < DIM (ssh_key_types); i++)
+ if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
+ || algo == ssh_key_types[i].algo)
+ break;
+
+ if (i == DIM (ssh_key_types))
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ else
+ {
+ *spec = ssh_key_types[i];
+ err = 0;
+ }
+
+ return err;
+}
+
+
+/* Receive a key from STREAM, according to the key specification given
+ as KEY_SPEC. Depending on SECRET, receive a secret or a public
+ key. If READ_COMMENT is true, receive a comment string as well.
+ Constructs a new S-Expression from received data and stores it in
+ KEY_NEW. Returns zero on success or an error code. */
+static gpg_error_t
+ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
+ int read_comment, ssh_key_type_spec_t *key_spec)
+{
+ gpg_error_t err;
+ char *key_type = NULL;
+ char *comment = NULL;
+ estream_t cert = NULL;
+ gcry_sexp_t key = NULL;
+ ssh_key_type_spec_t spec;
+ gcry_mpi_t *mpi_list = NULL;
+ const char *elems;
+ const char *curve_name = NULL;
+
+
+ err = stream_read_cstring (stream, &key_type);
+ if (err)
+ goto out;
+
+ err = ssh_key_type_lookup (key_type, 0, &spec);
+ if (err)
+ goto out;
+
+ if ((spec.flags & SPEC_FLAG_WITH_CERT))
+ {
+ /* This is an OpenSSH certificate+private key. The certificate
+ is an SSH string and which we store in an estream object. */
+ unsigned char *buffer;
+ u32 buflen;
+ char *cert_key_type;
+
+ err = stream_read_string (stream, 0, &buffer, &buflen);
+ if (err)
+ goto out;
+ cert = es_fopenmem_init (0, "rb", buffer, buflen);
+ xfree (buffer);
+ if (!cert)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* Check that the key type matches. */
+ err = stream_read_cstring (cert, &cert_key_type);
+ if (err)
+ goto out;
+ if (strcmp (cert_key_type, key_type) )
+ {
+ xfree (cert_key_type);
+ log_error ("key types in received ssh certificate do not match\n");
+ err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+ goto out;
+ }
+ xfree (cert_key_type);
+
+ /* Skip the nonce. */
+ err = stream_read_string (cert, 0, NULL, NULL);
+ if (err)
+ goto out;
+ }
+
+ if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+ {
+ /* The format of an EdDSA key is:
+ * string key_type ("ssh-ed25519")
+ * string public_key
+ * string private_key
+ *
+ * Note that the private key is the concatenation of the private
+ * key with the public key. Thus there's are 64 bytes; however
+ * we only want the real 32 byte private key - Libgcrypt expects
+ * this.
+ */
+ mpi_list = xtrycalloc (3, sizeof *mpi_list);
+ if (!mpi_list)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
+ if (err)
+ goto out;
+ if (secret)
+ {
+ u32 len = 0;
+ unsigned char *buffer;
+
+ /* Read string length. */
+ err = stream_read_uint32 (stream, &len);
+ if (err)
+ goto out;
+ if (len != 32 && len != 64)
+ {
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ goto out;
+ }
+ buffer = xtrymalloc_secure (32);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ err = stream_read_data (stream, buffer, 32);
+ if (err)
+ {
+ xfree (buffer);
+ goto out;
+ }
+ mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32);
+ buffer = NULL;
+ if (len == 64)
+ {
+ err = stream_read_skip (stream, 32);
+ if (err)
+ goto out;
+ }
+ }
+ }
+ else if ((spec.flags & SPEC_FLAG_IS_ECDSA))
+ {
+ /* The format of an ECDSA key is:
+ * string key_type ("ecdsa-sha2-nistp256" |
+ * "ecdsa-sha2-nistp384" |
+ * "ecdsa-sha2-nistp521" )
+ * string ecdsa_curve_name
+ * string ecdsa_public_key
+ * mpint ecdsa_private
+ *
+ * Note that we use the mpint reader instead of the string
+ * reader for ecsa_public_key. For the certificate variante
+ * ecdsa_curve_name+ecdsa_public_key are replaced by the
+ * certificate.
+ */
+ unsigned char *buffer;
+
+ err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
+ if (err)
+ goto out;
+ /* Get the canonical name. Should be the same as the read
+ * string but we use this mapping to validate that name. */
+ if (!ssh_identifier_from_curve_name (buffer, &curve_name))
+ {
+ err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+ xfree (buffer);
+ goto out;
+ }
+ xfree (buffer);
+
+ err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
+ if (err)
+ goto out;
+ }
+ else
+ {
+ err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
+ if (err)
+ goto out;
+ }
+
+ if (read_comment)
+ {
+ err = stream_read_cstring (stream, &comment);
+ if (err)
+ goto out;
+ }
+
+ if (secret)
+ elems = spec.elems_key_secret;
+ else
+ elems = spec.elems_key_public;
+
+ if (spec.key_modifier)
+ {
+ err = (*spec.key_modifier) (elems, mpi_list);
+ if (err)
+ goto out;
+ }
+
+ if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+ {
+ if (secret)
+ {
+ err = gcry_sexp_build (&key, NULL,
+ "(private-key(ecc(curve \"Ed25519\")"
+ "(flags eddsa)(q %m)(d %m))"
+ "(comment%s))",
+ mpi_list[0], mpi_list[1],
+ comment? comment:"");
+ }
+ else
+ {
+ err = gcry_sexp_build (&key, NULL,
+ "(public-key(ecc(curve \"Ed25519\")"
+ "(flags eddsa)(q %m))"
+ "(comment%s))",
+ mpi_list[0],
+ comment? comment:"");
+ }
+ }
+ else
+ {
+ err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
+ comment? comment:"");
+ if (err)
+ goto out;
+ }
+
+ if (key_spec)
+ *key_spec = spec;
+ *key_new = key;
+
+ out:
+ es_fclose (cert);
+ mpint_list_free (mpi_list);
+ xfree (key_type);
+ xfree (comment);
+
+ return err;
+}
+
+
+/* Write the public key from KEY to STREAM in SSH key format. If
+ OVERRIDE_COMMENT is not NULL, it will be used instead of the
+ comment stored in the key. */
+static gpg_error_t
+ssh_send_key_public (estream_t stream, gcry_sexp_t key,
+ const char *override_comment)
+{
+ ssh_key_type_spec_t spec;
+ int algo;
+ char *comment = NULL;
+ void *blob = NULL;
+ size_t bloblen;
+ gpg_error_t err = 0;
+
+ algo = get_pk_algo_from_key (key);
+ if (algo == 0)
+ goto out;
+
+ err = ssh_key_type_lookup (NULL, algo, &spec);
+ if (err)
+ goto out;
+
+ err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
+ if (err)
+ goto out;
+
+ err = stream_write_string (stream, blob, bloblen);
+ if (err)
+ goto out;
+
+ if (override_comment)
+ err = stream_write_cstring (stream, override_comment);
+ else
+ {
+ err = ssh_key_extract_comment (key, &comment);
+ if (err)
+ err = stream_write_cstring (stream, "(none)");
+ else
+ err = stream_write_cstring (stream, comment);
+ }
+ if (err)
+ goto out;
+
+ out:
+ xfree (comment);
+ es_free (blob);
+
+ return err;
+}
+
+
+/* Read a public key out of BLOB/BLOB_SIZE according to the key
+ specification given as KEY_SPEC, storing the new key in KEY_PUBLIC.
+ Returns zero on success or an error code. */
+static gpg_error_t
+ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
+ gcry_sexp_t *key_public,
+ ssh_key_type_spec_t *key_spec)
+{
+ gpg_error_t err;
+ estream_t blob_stream;
+
+ blob_stream = es_fopenmem (0, "r+b");
+ if (!blob_stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_write_data (blob_stream, blob, blob_size);
+ if (err)
+ goto out;
+
+ err = es_fseek (blob_stream, 0, SEEK_SET);
+ if (err)
+ goto out;
+
+ err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec);
+
+ out:
+ es_fclose (blob_stream);
+ return err;
+}
+
+
+
+/* This function calculates the key grip for the key contained in the
+ S-Expression KEY and writes it to BUFFER, which must be large
+ enough to hold it. Returns usual error code. */
+static gpg_error_t
+ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
+{
+ if (!gcry_pk_get_keygrip (key, buffer))
+ {
+ gpg_error_t err = gcry_pk_testkey (key);
+ return err? err : gpg_error (GPG_ERR_INTERNAL);
+ }
+
+ return 0;
+}
+
+
+static gpg_error_t
+card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result)
+{
+ gpg_error_t err;
+
+ *r_serialno = NULL;
+ *result = NULL;
+
+ err = agent_card_serialno (ctrl, r_serialno, NULL);
+ if (err)
+ {
+ if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose)
+ log_info (_("error getting serial number of card: %s\n"),
+ gpg_strerror (err));
+
+ /* Nothing available. */
+ return 0;
+ }
+
+ err = agent_card_cardlist (ctrl, result);
+ if (err)
+ {
+ xfree (*r_serialno);
+ *r_serialno = NULL;
+ }
+ return err;
+}
+
+/* Check whether a smartcard is available and whether it has a usable
+ key. Store a copy of that key at R_PK and return 0. If no key is
+ available store NULL at R_PK and return an error code. If CARDSN
+ is not NULL, a string with the serial number of the card will be
+ a malloced and stored there. */
+static gpg_error_t
+card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn)
+{
+ gpg_error_t err;
+ char *authkeyid;
+ char *serialno = NULL;
+ unsigned char *pkbuf;
+ size_t pkbuflen;
+ gcry_sexp_t s_pk;
+ unsigned char grip[20];
+
+ *r_pk = NULL;
+ if (cardsn)
+ *cardsn = NULL;
+
+ /* First see whether a card is available and whether the application
+ is supported. */
+ err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
+ if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED )
+ {
+ /* Ask for the serial number to reset the card. */
+ err = agent_card_serialno (ctrl, &serialno, NULL);
+ if (err)
+ {
+ if (opt.verbose)
+ log_info (_("error getting serial number of card: %s\n"),
+ gpg_strerror (err));
+ return err;
+ }
+ log_info (_("detected card with S/N: %s\n"), serialno);
+ err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
+ }
+ if (err)
+ {
+ log_error (_("no authentication key for ssh on card: %s\n"),
+ gpg_strerror (err));
+ xfree (serialno);
+ return err;
+ }
+
+ /* Get the S/N if we don't have it yet. Use the fast getattr method. */
+ if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) )
+ {
+ log_error (_("error getting serial number of card: %s\n"),
+ gpg_strerror (err));
+ xfree (authkeyid);
+ return err;
+ }
+
+ /* Read the public key. */
+ err = agent_card_readkey (ctrl, authkeyid, &pkbuf);
+ if (err)
+ {
+ if (opt.verbose)
+ log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+
+ pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
+ err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen);
+ if (err)
+ {
+ log_error ("failed to build S-Exp from received card key: %s\n",
+ gpg_strerror (err));
+ xfree (pkbuf);
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+
+ err = ssh_key_grip (s_pk, grip);
+ if (err)
+ {
+ log_debug ("error computing keygrip from received card key: %s\n",
+ gcry_strerror (err));
+ xfree (pkbuf);
+ gcry_sexp_release (s_pk);
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+
+ if ( agent_key_available (grip) )
+ {
+ char *dispserialno;
+
+ /* (Shadow)-key is not available in our key storage. */
+ agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno);
+ err = agent_write_shadow_key (0, grip, serialno, authkeyid, pkbuf, 0,
+ dispserialno);
+ xfree (dispserialno);
+ if (err)
+ {
+ xfree (pkbuf);
+ gcry_sexp_release (s_pk);
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+ }
+
+ if (cardsn)
+ {
+ char *dispsn;
+
+ /* If the card handler is able to return a short serialnumber,
+ use that one, else use the complete serialno. */
+ if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn))
+ {
+ *cardsn = xtryasprintf ("cardno:%s", dispsn);
+ xfree (dispsn);
+ }
+ else
+ *cardsn = xtryasprintf ("cardno:%s", serialno);
+ if (!*cardsn)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (pkbuf);
+ gcry_sexp_release (s_pk);
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+ }
+
+ xfree (pkbuf);
+ xfree (serialno);
+ xfree (authkeyid);
+ *r_pk = s_pk;
+ return 0;
+}
+
+
+
+
+/*
+
+ Request handler. Each handler is provided with a CTRL context, a
+ REQUEST object and a RESPONSE object. The actual request is to be
+ read from REQUEST, the response needs to be written to RESPONSE.
+
+*/
+
+
+/* Handler for the "request_identities" command. */
+static gpg_error_t
+ssh_handler_request_identities (ctrl_t ctrl,
+ estream_t request, estream_t response)
+{
+ u32 key_counter;
+ estream_t key_blobs;
+ gcry_sexp_t key_public;
+ gpg_error_t err;
+ int ret;
+ ssh_control_file_t cf = NULL;
+ gpg_error_t ret_err;
+
+ (void)request;
+
+ /* Prepare buffer stream. */
+
+ key_public = NULL;
+ key_counter = 0;
+
+ key_blobs = es_fopenmem (0, "r+b");
+ if (! key_blobs)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* First check whether a key is currently available in the card
+ reader - this should be allowed even without being listed in
+ sshcontrol. */
+
+ if (!opt.disable_scdaemon)
+ {
+ char *serialno;
+ strlist_t card_list, sl;
+
+ err = card_key_list (ctrl, &serialno, &card_list);
+ if (err)
+ {
+ if (opt.verbose)
+ log_info (_("error getting list of cards: %s\n"),
+ gpg_strerror (err));
+ goto scd_out;
+ }
+
+ for (sl = card_list; sl; sl = sl->next)
+ {
+ char *serialno0;
+ char *cardsn;
+
+ err = agent_card_serialno (ctrl, &serialno0, sl->d);
+ if (err)
+ {
+ if (opt.verbose)
+ log_info (_("error getting serial number of card: %s\n"),
+ gpg_strerror (err));
+ continue;
+ }
+
+ xfree (serialno0);
+ if (card_key_available (ctrl, &key_public, &cardsn))
+ continue;
+
+ err = ssh_send_key_public (key_blobs, key_public, cardsn);
+ gcry_sexp_release (key_public);
+ key_public = NULL;
+ xfree (cardsn);
+ if (err)
+ {
+ if (opt.verbose)
+ gcry_log_debugsxp ("pubkey", key_public);
+ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE
+ || gpg_err_code (err) == GPG_ERR_INV_CURVE)
+ {
+ /* For example a Brainpool curve or a curve we don't
+ * support at all but a smartcard lists that curve.
+ * We ignore them. */
+ }
+ else
+ {
+ xfree (serialno);
+ free_strlist (card_list);
+ goto out;
+ }
+ }
+ else
+ key_counter++;
+ }
+
+ xfree (serialno);
+ free_strlist (card_list);
+ }
+
+ scd_out:
+ /* Then look at all the registered and non-disabled keys. */
+ err = open_control_file (&cf, 0);
+ if (err)
+ goto out;
+
+ while (!read_control_file_item (cf))
+ {
+ unsigned char grip[20];
+
+ if (!cf->item.valid)
+ continue; /* Should not happen. */
+ if (cf->item.disabled)
+ continue;
+ assert (strlen (cf->item.hexgrip) == 40);
+ hex2bin (cf->item.hexgrip, grip, sizeof (grip));
+
+ err = agent_public_key_from_file (ctrl, grip, &key_public);
+ if (err)
+ {
+ log_error ("%s:%d: key '%s' skipped: %s\n",
+ cf->fname, cf->lnr, cf->item.hexgrip,
+ gpg_strerror (err));
+ continue;
+ }
+
+ err = ssh_send_key_public (key_blobs, key_public, NULL);
+ if (err)
+ goto out;
+ gcry_sexp_release (key_public);
+ key_public = NULL;
+
+ key_counter++;
+ }
+ err = 0;
+
+ ret = es_fseek (key_blobs, 0, SEEK_SET);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ out:
+ /* Send response. */
+
+ gcry_sexp_release (key_public);
+
+ if (!err)
+ {
+ ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
+ if (!ret_err)
+ ret_err = stream_write_uint32 (response, key_counter);
+ if (!ret_err)
+ ret_err = stream_copy (response, key_blobs);
+ }
+ else
+ {
+ log_error ("ssh request identities failed: %s <%s>\n",
+ gpg_strerror (err), gpg_strsource (err));
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+ }
+
+ es_fclose (key_blobs);
+ close_control_file (cf);
+
+ return ret_err;
+}
+
+
+/* This function hashes the data contained in DATA of size DATA_N
+ according to the message digest algorithm specified by MD_ALGORITHM
+ and writes the message digest to HASH, which needs to large enough
+ for the digest. */
+static gpg_error_t
+data_hash (unsigned char *data, size_t data_n,
+ int md_algorithm, unsigned char *hash)
+{
+ gcry_md_hash_buffer (md_algorithm, hash, data, data_n);
+
+ return 0;
+}
+
+
+/* This function signs the data described by CTRL. If HASH is not
+ NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to
+ allow the use of signature algorithms that implement the hashing
+ internally (e.g. Ed25519). On success the created signature is
+ stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller
+ must use es_free to releaase this memory. */
+static gpg_error_t
+data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
+ const void *hash, size_t hashlen,
+ unsigned char **r_sig, size_t *r_siglen)
+{
+ gpg_error_t err;
+ gcry_sexp_t signature_sexp = NULL;
+ estream_t stream = NULL;
+ void *blob = NULL;
+ size_t bloblen;
+ char hexgrip[40+1];
+
+ *r_sig = NULL;
+ *r_siglen = 0;
+
+ /* Quick check to see whether we have a valid keygrip and convert it
+ to hex. */
+ if (!ctrl->have_keygrip)
+ {
+ err = gpg_error (GPG_ERR_NO_SECKEY);
+ goto out;
+ }
+ bin2hex (ctrl->keygrip, 20, hexgrip);
+
+ /* Ask for confirmation if needed. */
+ if (confirm_flag_from_sshcontrol (hexgrip))
+ {
+ gcry_sexp_t key;
+ char *fpr, *prompt;
+ char *comment = NULL;
+
+ err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
+ if (err)
+ goto out;
+ err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr);
+ if (!err)
+ {
+ gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
+ if (tmpsxp)
+ comment = gcry_sexp_nth_string (tmpsxp, 1);
+ gcry_sexp_release (tmpsxp);
+ }
+ gcry_sexp_release (key);
+ if (err)
+ goto out;
+ prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
+ " %s%%0A"
+ " (%s)%%0A"
+ "Do you want to allow this?"),
+ fpr, comment? comment:"");
+ xfree (fpr);
+ gcry_free (comment);
+ err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
+ xfree (prompt);
+ if (err)
+ goto out;
+ }
+
+ /* Create signature. */
+ ctrl->use_auth_call = 1;
+ err = agent_pksign_do (ctrl, NULL,
+ L_("Please enter the passphrase "
+ "for the ssh key%%0A %F%%0A (%c)"),
+ &signature_sexp,
+ CACHE_MODE_SSH, ttl_from_sshcontrol,
+ hash, hashlen);
+ ctrl->use_auth_call = 0;
+ if (err)
+ goto out;
+
+ stream = es_fopenmem (0, "r+b");
+ if (!stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_write_cstring (stream, spec->ssh_identifier);
+ if (err)
+ goto out;
+
+ err = spec->signature_encoder (spec, stream, signature_sexp);
+ if (err)
+ goto out;
+
+ err = es_fclose_snatch (stream, &blob, &bloblen);
+ if (err)
+ goto out;
+ stream = NULL;
+
+ *r_sig = blob; blob = NULL;
+ *r_siglen = bloblen;
+
+ out:
+ xfree (blob);
+ es_fclose (stream);
+ gcry_sexp_release (signature_sexp);
+
+ return err;
+}
+
+
+/* Handler for the "sign_request" command. */
+static gpg_error_t
+ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
+{
+ gcry_sexp_t key = NULL;
+ ssh_key_type_spec_t spec;
+ unsigned char hash[MAX_DIGEST_LEN];
+ unsigned int hash_n;
+ unsigned char key_grip[20];
+ unsigned char *key_blob = NULL;
+ u32 key_blob_size;
+ unsigned char *data = NULL;
+ unsigned char *sig = NULL;
+ size_t sig_n;
+ u32 data_size;
+ gpg_error_t err;
+ gpg_error_t ret_err;
+ int hash_algo;
+
+ /* Receive key. */
+
+ err = stream_read_string (request, 0, &key_blob, &key_blob_size);
+ if (err)
+ goto out;
+
+ err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec);
+ if (err)
+ goto out;
+
+ /* Receive data to sign. */
+ err = stream_read_string (request, 0, &data, &data_size);
+ if (err)
+ goto out;
+
+ /* Flag processing. */
+ {
+ u32 flags;
+
+ err = stream_read_uint32 (request, &flags);
+ if (err)
+ goto out;
+
+ if (spec.algo == GCRY_PK_RSA)
+ {
+ if ((flags & SSH_AGENT_RSA_SHA2_512))
+ {
+ flags &= ~SSH_AGENT_RSA_SHA2_512;
+ spec.ssh_identifier = "rsa-sha2-512";
+ spec.hash_algo = GCRY_MD_SHA512;
+ }
+ if ((flags & SSH_AGENT_RSA_SHA2_256))
+ {
+ /* Note: We prefer SHA256 over SHA512. */
+ flags &= ~SSH_AGENT_RSA_SHA2_256;
+ spec.ssh_identifier = "rsa-sha2-256";
+ spec.hash_algo = GCRY_MD_SHA256;
+ }
+ }
+
+ /* Some flag is present that we do not know about. Note that
+ * processed or known flags have been cleared at this point. */
+ if (flags)
+ {
+ err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
+ goto out;
+ }
+ }
+
+ hash_algo = spec.hash_algo;
+ if (!hash_algo)
+ hash_algo = GCRY_MD_SHA1; /* Use the default. */
+ ctrl->digest.algo = hash_algo;
+ if ((spec.flags & SPEC_FLAG_USE_PKCS1V2))
+ ctrl->digest.raw_value = 0;
+ else
+ ctrl->digest.raw_value = 1;
+
+ /* Calculate key grip. */
+ err = ssh_key_grip (key, key_grip);
+ if (err)
+ goto out;
+ ctrl->have_keygrip = 1;
+ memcpy (ctrl->keygrip, key_grip, 20);
+
+ /* Hash data unless we use EdDSA. */
+ if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+ {
+ ctrl->digest.valuelen = 0;
+ }
+ else
+ {
+ hash_n = gcry_md_get_algo_dlen (hash_algo);
+ if (!hash_n)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL);
+ goto out;
+ }
+ err = data_hash (data, data_size, hash_algo, hash);
+ if (err)
+ goto out;
+ memcpy (ctrl->digest.value, hash, hash_n);
+ ctrl->digest.valuelen = hash_n;
+ }
+
+ /* Sign data. */
+ if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+ err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n);
+ else
+ err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n);
+
+ out:
+ /* Done. */
+ if (!err)
+ {
+ ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE);
+ if (ret_err)
+ goto leave;
+ ret_err = stream_write_string (response, sig, sig_n);
+ if (ret_err)
+ goto leave;
+ }
+ else
+ {
+ log_error ("ssh sign request failed: %s <%s>\n",
+ gpg_strerror (err), gpg_strsource (err));
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+ if (ret_err)
+ goto leave;
+ }
+
+ leave:
+
+ gcry_sexp_release (key);
+ xfree (key_blob);
+ xfree (data);
+ es_free (sig);
+
+ return ret_err;
+}
+
+
+/* This function extracts the comment contained in the key
+ s-expression KEY and stores a copy in COMMENT. Returns usual error
+ code. */
+static gpg_error_t
+ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
+{
+ gcry_sexp_t comment_list;
+
+ *r_comment = NULL;
+
+ comment_list = gcry_sexp_find_token (key, "comment", 0);
+ if (!comment_list)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ *r_comment = gcry_sexp_nth_string (comment_list, 1);
+ gcry_sexp_release (comment_list);
+ if (!*r_comment)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ return 0;
+}
+
+
+/* This function converts the key contained in the S-Expression KEY
+ into a buffer, which is protected by the passphrase PASSPHRASE.
+ If PASSPHRASE is the empty passphrase, the key is not protected.
+ Returns usual error code. */
+static gpg_error_t
+ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
+ unsigned char **buffer, size_t *buffer_n)
+{
+ unsigned char *buffer_new;
+ unsigned int buffer_new_n;
+ gpg_error_t err;
+
+ buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0);
+ buffer_new = xtrymalloc_secure (buffer_new_n);
+ if (! buffer_new)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON,
+ buffer_new, buffer_new_n);
+
+ if (*passphrase)
+ err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
+ else
+ {
+ /* The key derivation function does not support zero length
+ * strings. Store key unprotected if the user wishes so. */
+ *buffer = buffer_new;
+ *buffer_n = buffer_new_n;
+ buffer_new = NULL;
+ err = 0;
+ }
+
+ out:
+
+ xfree (buffer_new);
+
+ return err;
+}
+
+
+
+/* Callback function to compare the first entered PIN with the one
+ currently being entered. */
+static gpg_error_t
+reenter_compare_cb (struct pin_entry_info_s *pi)
+{
+ const char *pin1 = pi->check_cb_arg;
+
+ if (!strcmp (pin1, pi->pin))
+ return 0; /* okay */
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+}
+
+
+/* Store the ssh KEY into our local key storage and protect it after
+ asking for a passphrase. Cache that passphrase. TTL is the
+ maximum caching time for that key. If the key already exists in
+ our key storage, don't do anything. When entering a key also add
+ an entry to the sshcontrol file. */
+static gpg_error_t
+ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
+ gcry_sexp_t key, int ttl, int confirm)
+{
+ gpg_error_t err;
+ unsigned char key_grip_raw[20];
+ char key_grip[41];
+ unsigned char *buffer = NULL;
+ size_t buffer_n;
+ char *description = NULL;
+ const char *description2 = L_("Please re-enter this passphrase");
+ char *comment = NULL;
+ char *key_fpr = NULL;
+ const char *initial_errtext = NULL;
+ struct pin_entry_info_s *pi = NULL;
+ struct pin_entry_info_s *pi2 = NULL;
+
+ err = ssh_key_grip (key, key_grip_raw);
+ if (err)
+ goto out;
+
+ bin2hex (key_grip_raw, 20, key_grip);
+
+ err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr);
+ if (err)
+ goto out;
+
+ /* Check whether the key is already in our key storage. Don't do
+ anything then besides (re-)adding it to sshcontrol. */
+ if ( !agent_key_available (key_grip_raw) )
+ goto key_exists; /* Yes, key is available. */
+
+ err = ssh_key_extract_comment (key, &comment);
+ if (err)
+ goto out;
+
+ if ( asprintf (&description,
+ L_("Please enter a passphrase to protect"
+ " the received secret key%%0A"
+ " %s%%0A"
+ " %s%%0A"
+ "within gpg-agent's key storage"),
+ key_fpr, comment ? comment : "") < 0)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi2)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi->max_tries = 1;
+ pi->with_repeat = 1;
+ pi2->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi2->max_tries = 1;
+ pi2->check_cb = reenter_compare_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ next_try:
+ err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
+ initial_errtext = NULL;
+ if (err)
+ goto out;
+
+ /* Unless the passphrase is empty or the pinentry told us that
+ it already did the repetition check, ask to confirm it. */
+ if (*pi->pin && !pi->repeat_okay)
+ {
+ err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
+ if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
+ { /* The re-entered one did not match and the user did not
+ hit cancel. */
+ initial_errtext = L_("does not match - try again");
+ goto next_try;
+ }
+ }
+
+ err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n);
+ if (err)
+ goto out;
+
+ /* Store this key to our key storage. We do not store a creation
+ * timestamp because we simply do not know. */
+ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, 0,
+ NULL, NULL, NULL);
+ if (err)
+ goto out;
+
+ /* Cache this passphrase. */
+ err = agent_put_cache (ctrl, key_grip, CACHE_MODE_SSH, pi->pin, ttl);
+ if (err)
+ goto out;
+
+ key_exists:
+ /* And add an entry to the sshcontrol file. */
+ err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm);
+
+
+ out:
+ if (pi2 && pi2->max_length)
+ wipememory (pi2->pin, pi2->max_length);
+ xfree (pi2);
+ if (pi && pi->max_length)
+ wipememory (pi->pin, pi->max_length);
+ xfree (pi);
+ xfree (buffer);
+ xfree (comment);
+ xfree (key_fpr);
+ xfree (description);
+
+ return err;
+}
+
+
+/* This function removes the key contained in the S-Expression KEY
+ from the local key storage, in case it exists there. Returns usual
+ error code. FIXME: this function is a stub. */
+static gpg_error_t
+ssh_identity_drop (gcry_sexp_t key)
+{
+ unsigned char key_grip[21] = { 0 };
+ gpg_error_t err;
+
+ err = ssh_key_grip (key, key_grip);
+ if (err)
+ goto out;
+
+ key_grip[sizeof (key_grip) - 1] = 0;
+
+ /* FIXME: What to do here - forgetting the passphrase or deleting
+ the key from key cache? */
+
+ out:
+
+ return err;
+}
+
+/* Handler for the "add_identity" command. */
+static gpg_error_t
+ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
+{
+ gpg_error_t ret_err;
+ ssh_key_type_spec_t spec;
+ gpg_error_t err;
+ gcry_sexp_t key;
+ unsigned char b;
+ int confirm;
+ int ttl;
+
+ confirm = 0;
+ key = NULL;
+ ttl = 0;
+
+ /* FIXME? */
+ err = ssh_receive_key (request, &key, 1, 1, &spec);
+ if (err)
+ goto out;
+
+ while (1)
+ {
+ err = stream_read_byte (request, &b);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ err = 0;
+ break;
+ }
+
+ switch (b)
+ {
+ case SSH_OPT_CONSTRAIN_LIFETIME:
+ {
+ u32 n = 0;
+
+ err = stream_read_uint32 (request, &n);
+ if (! err)
+ ttl = n;
+ break;
+ }
+
+ case SSH_OPT_CONSTRAIN_CONFIRM:
+ {
+ confirm = 1;
+ break;
+ }
+
+ default:
+ /* FIXME: log/bad? */
+ break;
+ }
+ }
+ if (err)
+ goto out;
+
+ err = ssh_identity_register (ctrl, &spec, key, ttl, confirm);
+
+ out:
+
+ gcry_sexp_release (key);
+
+ if (! err)
+ ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
+ else
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+
+ return ret_err;
+}
+
+/* Handler for the "remove_identity" command. */
+static gpg_error_t
+ssh_handler_remove_identity (ctrl_t ctrl,
+ estream_t request, estream_t response)
+{
+ unsigned char *key_blob;
+ u32 key_blob_size;
+ gcry_sexp_t key;
+ gpg_error_t ret_err;
+ gpg_error_t err;
+
+ (void)ctrl;
+
+ /* Receive key. */
+
+ key_blob = NULL;
+ key = NULL;
+
+ err = stream_read_string (request, 0, &key_blob, &key_blob_size);
+ if (err)
+ goto out;
+
+ err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL);
+ if (err)
+ goto out;
+
+ err = ssh_identity_drop (key);
+
+ out:
+
+ xfree (key_blob);
+ gcry_sexp_release (key);
+
+ if (! err)
+ ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
+ else
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+
+ return ret_err;
+}
+
+/* FIXME: stub function. Actually useful? */
+static gpg_error_t
+ssh_identities_remove_all (void)
+{
+ gpg_error_t err;
+
+ err = 0;
+
+ /* FIXME: shall we remove _all_ cache entries or only those
+ registered through the ssh-agent protocol? */
+
+ return err;
+}
+
+/* Handler for the "remove_all_identities" command. */
+static gpg_error_t
+ssh_handler_remove_all_identities (ctrl_t ctrl,
+ estream_t request, estream_t response)
+{
+ gpg_error_t ret_err;
+ gpg_error_t err;
+
+ (void)ctrl;
+ (void)request;
+
+ err = ssh_identities_remove_all ();
+
+ if (! err)
+ ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
+ else
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+
+ return ret_err;
+}
+
+/* Lock agent? FIXME: stub function. */
+static gpg_error_t
+ssh_lock (void)
+{
+ gpg_error_t err;
+
+ /* FIXME */
+ log_error ("ssh-agent's lock command is not implemented\n");
+ err = 0;
+
+ return err;
+}
+
+/* Unock agent? FIXME: stub function. */
+static gpg_error_t
+ssh_unlock (void)
+{
+ gpg_error_t err;
+
+ log_error ("ssh-agent's unlock command is not implemented\n");
+ err = 0;
+
+ return err;
+}
+
+/* Handler for the "lock" command. */
+static gpg_error_t
+ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response)
+{
+ gpg_error_t ret_err;
+ gpg_error_t err;
+
+ (void)ctrl;
+ (void)request;
+
+ err = ssh_lock ();
+
+ if (! err)
+ ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
+ else
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+
+ return ret_err;
+}
+
+/* Handler for the "unlock" command. */
+static gpg_error_t
+ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response)
+{
+ gpg_error_t ret_err;
+ gpg_error_t err;
+
+ (void)ctrl;
+ (void)request;
+
+ err = ssh_unlock ();
+
+ if (! err)
+ ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
+ else
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+
+ return ret_err;
+}
+
+
+
+/* Return the request specification for the request identified by TYPE
+ or NULL in case the requested request specification could not be
+ found. */
+static const ssh_request_spec_t *
+request_spec_lookup (int type)
+{
+ const ssh_request_spec_t *spec;
+ unsigned int i;
+
+ for (i = 0; i < DIM (request_specs); i++)
+ if (request_specs[i].type == type)
+ break;
+ if (i == DIM (request_specs))
+ {
+ if (opt.verbose)
+ log_info ("ssh request %u is not supported\n", type);
+ spec = NULL;
+ }
+ else
+ spec = request_specs + i;
+
+ return spec;
+}
+
+/* Process a single request. The request is read from and the
+ response is written to STREAM_SOCK. Uses CTRL as context. Returns
+ zero in case of success, non zero in case of failure. */
+static int
+ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
+{
+ const ssh_request_spec_t *spec;
+ estream_t response = NULL;
+ estream_t request = NULL;
+ unsigned char request_type;
+ gpg_error_t err;
+ int send_err = 0;
+ int ret;
+ unsigned char *request_data = NULL;
+ u32 request_data_size;
+ u32 response_size;
+
+ /* Create memory streams for request/response data. The entire
+ request will be stored in secure memory, since it might contain
+ secret key material. The response does not have to be stored in
+ secure memory, since we never give out secret keys.
+
+ Note: we only have little secure memory, but there is NO
+ possibility of DoS here; only trusted clients are allowed to
+ connect to the agent. What could happen is that the agent
+ returns out-of-secure-memory errors on requests in case the
+ agent's owner floods his own agent with many large messages.
+ -moritz */
+
+ /* Retrieve request. */
+ err = stream_read_string (stream_sock, 1, &request_data, &request_data_size);
+ if (err)
+ goto out;
+
+ if (opt.verbose > 1)
+ log_info ("received ssh request of length %u\n",
+ (unsigned int)request_data_size);
+
+ if (! request_data_size)
+ {
+ send_err = 1;
+ goto out;
+ /* Broken request; FIXME. */
+ }
+
+ request_type = request_data[0];
+ spec = request_spec_lookup (request_type);
+ if (! spec)
+ {
+ send_err = 1;
+ goto out;
+ /* Unknown request; FIXME. */
+ }
+
+ if (spec->secret_input)
+ request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b");
+ else
+ request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b");
+ if (! request)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ ret = es_setvbuf (request, NULL, _IONBF, 0);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ err = stream_write_data (request, request_data + 1, request_data_size - 1);
+ if (err)
+ goto out;
+ es_rewind (request);
+
+ response = es_fopenmem (0, "r+b");
+ if (! response)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ if (opt.verbose)
+ log_info ("ssh request handler for %s (%u) started\n",
+ spec->identifier, spec->type);
+
+ err = (*spec->handler) (ctrl, request, response);
+
+ if (opt.verbose)
+ {
+ if (err)
+ log_info ("ssh request handler for %s (%u) failed: %s\n",
+ spec->identifier, spec->type, gpg_strerror (err));
+ else
+ log_info ("ssh request handler for %s (%u) ready\n",
+ spec->identifier, spec->type);
+ }
+
+ if (err)
+ {
+ send_err = 1;
+ goto out;
+ }
+
+ response_size = es_ftell (response);
+ if (opt.verbose > 1)
+ log_info ("sending ssh response of length %u\n",
+ (unsigned int)response_size);
+
+ err = es_fseek (response, 0, SEEK_SET);
+ if (err)
+ {
+ send_err = 1;
+ goto out;
+ }
+
+ err = stream_write_uint32 (stream_sock, response_size);
+ if (err)
+ {
+ send_err = 1;
+ goto out;
+ }
+
+ err = stream_copy (stream_sock, response);
+ if (err)
+ goto out;
+
+ err = es_fflush (stream_sock);
+ if (err)
+ goto out;
+
+ out:
+
+ if (err && es_feof (stream_sock))
+ log_error ("error occurred while processing request: %s\n",
+ gpg_strerror (err));
+
+ if (send_err)
+ {
+ if (opt.verbose > 1)
+ log_info ("sending ssh error response\n");
+ err = stream_write_uint32 (stream_sock, 1);
+ if (err)
+ goto leave;
+ err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE);
+ if (err)
+ goto leave;
+ }
+
+ leave:
+
+ es_fclose (request);
+ es_fclose (response);
+ xfree (request_data);
+
+ return !!err;
+}
+
+
+/* Return the peer's pid. */
+static unsigned long
+get_client_pid (int fd)
+{
+ pid_t client_pid = (pid_t)0;
+
+#ifdef SO_PEERCRED
+ {
+#ifdef HAVE_STRUCT_SOCKPEERCRED_PID
+ struct sockpeercred cr;
+#else
+ struct ucred cr;
+#endif
+ socklen_t cl = sizeof cr;
+
+ if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
+ {
+#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID)
+ client_pid = cr.pid;
+#elif defined (HAVE_STRUCT_UCRED_CR_PID)
+ client_pid = cr.cr_pid;
+#else
+#error "Unknown SO_PEERCRED struct"
+#endif
+ }
+ }
+#elif defined (LOCAL_PEERPID)
+ {
+ socklen_t len = sizeof (pid_t);
+
+ getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
+ }
+#elif defined (LOCAL_PEEREID)
+ {
+ struct unpcbid unp;
+ socklen_t unpl = sizeof unp;
+
+ if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
+ client_pid = unp.unp_pid;
+ }
+#elif defined (HAVE_GETPEERUCRED)
+ {
+ ucred_t *ucred = NULL;
+
+ if (getpeerucred (fd, &ucred) != -1)
+ {
+ client_pid= ucred_getpid (ucred);
+ ucred_free (ucred);
+ }
+ }
+#else
+ (void)fd;
+#endif
+
+ return (unsigned long)client_pid;
+}
+
+
+/* Start serving client on SOCK_CLIENT. */
+void
+start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
+{
+ estream_t stream_sock = NULL;
+ gpg_error_t err;
+ int ret;
+
+ err = agent_copy_startup_env (ctrl);
+ if (err)
+ goto out;
+
+ ctrl->client_pid = get_client_pid (FD2INT(sock_client));
+
+ /* Create stream from socket. */
+ stream_sock = es_fdopen (FD2INT(sock_client), "r+");
+ if (!stream_sock)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("failed to create stream from socket: %s\n"),
+ gpg_strerror (err));
+ goto out;
+ }
+ /* We have to disable the estream buffering, because the estream
+ core doesn't know about secure memory. */
+ ret = es_setvbuf (stream_sock, NULL, _IONBF, 0);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("failed to disable buffering "
+ "on socket stream: %s\n", gpg_strerror (err));
+ goto out;
+ }
+
+ /* Main processing loop. */
+ while ( !ssh_request_process (ctrl, stream_sock) )
+ {
+ /* Check whether we have reached EOF before trying to read
+ another request. */
+ int c;
+
+ c = es_fgetc (stream_sock);
+ if (c == EOF)
+ break;
+ es_ungetc (c, stream_sock);
+ }
+
+ /* Reset the SCD in case it has been used. */
+ agent_reset_scd (ctrl);
+
+
+ out:
+ if (stream_sock)
+ es_fclose (stream_sock);
+}
+
+
+#ifdef HAVE_W32_SYSTEM
+/* Serve one ssh-agent request. This is used for the Putty support.
+ REQUEST is the mmapped memory which may be accessed up to a
+ length of MAXREQLEN. Returns 0 on success which also indicates
+ that a valid SSH response message is now in REQUEST. */
+int
+serve_mmapped_ssh_request (ctrl_t ctrl,
+ unsigned char *request, size_t maxreqlen)
+{
+ gpg_error_t err;
+ int send_err = 0;
+ int valid_response = 0;
+ const ssh_request_spec_t *spec;
+ u32 msglen;
+ estream_t request_stream, response_stream;
+
+ if (agent_copy_startup_env (ctrl))
+ goto leave; /* Error setting up the environment. */
+
+ if (maxreqlen < 5)
+ goto leave; /* Caller error. */
+
+ msglen = uint32_construct (request[0], request[1], request[2], request[3]);
+ if (msglen < 1 || msglen > maxreqlen - 4)
+ {
+ log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
+ goto leave;
+ }
+
+ spec = request_spec_lookup (request[4]);
+ if (!spec)
+ {
+ send_err = 1; /* Unknown request type. */
+ goto leave;
+ }
+
+ /* Create a stream object with the data part of the request. */
+ if (spec->secret_input)
+ request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
+ else
+ request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
+ if (!request_stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ /* We have to disable the estream buffering, because the estream
+ core doesn't know about secure memory. */
+ if (es_setvbuf (request_stream, NULL, _IONBF, 0))
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ /* Copy the request to the stream but omit the request type. */
+ err = stream_write_data (request_stream, request + 5, msglen - 1);
+ if (err)
+ goto leave;
+ es_rewind (request_stream);
+
+ response_stream = es_fopenmem (0, "r+b");
+ if (!response_stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("ssh request handler for %s (%u) started\n",
+ spec->identifier, spec->type);
+
+ err = (*spec->handler) (ctrl, request_stream, response_stream);
+
+ if (opt.verbose)
+ {
+ if (err)
+ log_info ("ssh request handler for %s (%u) failed: %s\n",
+ spec->identifier, spec->type, gpg_strerror (err));
+ else
+ log_info ("ssh request handler for %s (%u) ready\n",
+ spec->identifier, spec->type);
+ }
+
+ es_fclose (request_stream);
+ request_stream = NULL;
+
+ if (err)
+ {
+ send_err = 1;
+ goto leave;
+ }
+
+ /* Put the response back into the mmapped buffer. */
+ {
+ void *response_data;
+ size_t response_size;
+
+ /* NB: In contrast to the request-stream, the response stream
+ includes the message type byte. */
+ if (es_fclose_snatch (response_stream, &response_data, &response_size))
+ {
+ log_error ("snatching ssh response failed: %s",
+ gpg_strerror (gpg_error_from_syserror ()));
+ send_err = 1; /* Ooops. */
+ goto leave;
+ }
+
+ if (opt.verbose > 1)
+ log_info ("sending ssh response of length %u\n",
+ (unsigned int)response_size);
+ if (response_size > maxreqlen - 4)
+ {
+ log_error ("invalid length of the ssh response: %s",
+ gpg_strerror (GPG_ERR_INTERNAL));
+ es_free (response_data);
+ send_err = 1;
+ goto leave;
+ }
+
+ request[0] = response_size >> 24;
+ request[1] = response_size >> 16;
+ request[2] = response_size >> 8;
+ request[3] = response_size >> 0;
+ memcpy (request+4, response_data, response_size);
+ es_free (response_data);
+ valid_response = 1;
+ }
+
+ leave:
+ if (send_err)
+ {
+ request[0] = 0;
+ request[1] = 0;
+ request[2] = 0;
+ request[3] = 1;
+ request[4] = SSH_RESPONSE_FAILURE;
+ valid_response = 1;
+ }
+
+ /* Reset the SCD in case it has been used. */
+ agent_reset_scd (ctrl);
+
+ return valid_response? 0 : -1;
+}
+#endif /*HAVE_W32_SYSTEM*/
diff --git a/agent/command.c b/agent/command.c
new file mode 100644
index 0000000..b682c55
--- /dev/null
+++ b/agent/command.c
@@ -0,0 +1,3648 @@
+/* command.c - gpg-agent command handler
+ * Copyright (C) 2001-2011 Free Software Foundation, Inc.
+ * Copyright (C) 2001-2013 Werner Koch
+ * Copyright (C) 2015 g10 Code GmbH.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* FIXME: we should not use the default assuan buffering but setup
+ some buffering in secure mempory to protect session keys etc. */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "agent.h"
+#include <assuan.h>
+#include "../common/i18n.h"
+#include "cvt-openpgp.h"
+#include "../common/ssh-utils.h"
+#include "../common/asshelp.h"
+#include "../common/server-help.h"
+
+
+/* Maximum allowed size of the inquired ciphertext. */
+#define MAXLEN_CIPHERTEXT 4096
+/* Maximum allowed size of the key parameters. */
+#define MAXLEN_KEYPARAM 1024
+/* Maximum allowed size of key data as used in inquiries (bytes). */
+#define MAXLEN_KEYDATA 8192
+/* The size of the import/export KEK key (in bytes). */
+#define KEYWRAP_KEYSIZE (128/8)
+
+/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
+ text string. */
+#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
+
+/* Check that the maximum digest length we support has at least the
+ length of the keygrip. */
+#if MAX_DIGEST_LEN < 20
+#error MAX_DIGEST_LEN shorter than keygrip
+#endif
+
+/* Data used to associate an Assuan context with local server data.
+ This is this modules local part of the server_control_s struct. */
+struct server_local_s
+{
+ /* Our Assuan context. */
+ assuan_context_t assuan_ctx;
+
+ /* If this flag is true, the passphrase cache is used for signing
+ operations. It defaults to true but may be set on a per
+ connection base. The global option opt.ignore_cache_for_signing
+ takes precedence over this flag. */
+ unsigned int use_cache_for_signing : 1;
+
+ /* Flag to suppress I/O logging during a command. */
+ unsigned int pause_io_logging : 1;
+
+ /* Flag indicating that the connection is from ourselves. */
+ unsigned int connect_from_self : 1;
+
+ /* Helper flag for io_monitor to allow suppressing of our own
+ * greeting in some cases. See io_monitor for details. */
+ unsigned int greeting_seen : 1;
+
+ /* If this flag is set to true the agent will be terminated after
+ the end of the current session. */
+ unsigned int stopme : 1;
+
+ /* Flag indicating whether pinentry notifications shall be done. */
+ unsigned int allow_pinentry_notify : 1;
+
+ /* An allocated description for the next key operation. This is
+ used if a pinnetry needs to be popped up. */
+ char *keydesc;
+
+ /* Malloced KEK (Key-Encryption-Key) for the import_key command. */
+ void *import_key;
+
+ /* Malloced KEK for the export_key command. */
+ void *export_key;
+
+ /* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */
+ int allow_fully_canceled;
+
+ /* Last CACHE_NONCE sent as status (malloced). */
+ char *last_cache_nonce;
+
+ /* Last PASSWD_NONCE sent as status (malloced). */
+ char *last_passwd_nonce;
+};
+
+
+/* An entry for the getval/putval commands. */
+struct putval_item_s
+{
+ struct putval_item_s *next;
+ size_t off; /* Offset to the value into DATA. */
+ size_t len; /* Length of the value. */
+ char d[1]; /* Key | Nul | value. */
+};
+
+
+/* A list of key value pairs fpr the getval/putval commands. */
+static struct putval_item_s *putval_list;
+
+
+
+/* To help polling clients, we keep track of the number of certain
+ events. This structure keeps those counters. The counters are
+ integers and there should be no problem if they are overflowing as
+ callers need to check only whether a counter changed. The actual
+ values are not meaningful. */
+struct
+{
+ /* Incremented if any of the other counters below changed. */
+ unsigned int any;
+
+ /* Incremented if a key is added or removed from the internal privat
+ key database. */
+ unsigned int key;
+
+ /* Incremented if a change of the card readers stati has been
+ detected. */
+ unsigned int card;
+
+} eventcounter;
+
+
+
+/* Local prototypes. */
+static int command_has_option (const char *cmd, const char *cmdopt);
+
+
+
+
+/* Release the memory buffer MB but first wipe out the used memory. */
+static void
+clear_outbuf (membuf_t *mb)
+{
+ void *p;
+ size_t n;
+
+ p = get_membuf (mb, &n);
+ if (p)
+ {
+ wipememory (p, n);
+ xfree (p);
+ }
+}
+
+
+/* Write the content of memory buffer MB as assuan data to CTX and
+ wipe the buffer out afterwards. */
+static gpg_error_t
+write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
+{
+ gpg_error_t ae;
+ void *p;
+ size_t n;
+
+ p = get_membuf (mb, &n);
+ if (!p)
+ return out_of_core ();
+ ae = assuan_send_data (ctx, p, n);
+ memset (p, 0, n);
+ xfree (p);
+ return ae;
+}
+
+
+/* Clear the nonces used to enable the passphrase cache for certain
+ multi-command command sequences. */
+static void
+clear_nonce_cache (ctrl_t ctrl)
+{
+ if (ctrl->server_local->last_cache_nonce)
+ {
+ agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce,
+ CACHE_MODE_NONCE, NULL, 0);
+ xfree (ctrl->server_local->last_cache_nonce);
+ ctrl->server_local->last_cache_nonce = NULL;
+ }
+ if (ctrl->server_local->last_passwd_nonce)
+ {
+ agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce,
+ CACHE_MODE_NONCE, NULL, 0);
+ xfree (ctrl->server_local->last_passwd_nonce);
+ ctrl->server_local->last_passwd_nonce = NULL;
+ }
+}
+
+
+/* This function is called by Libassuan whenever the client sends a
+ reset. It has been registered similar to the other Assuan
+ commands. */
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void) line;
+
+ memset (ctrl->keygrip, 0, 20);
+ ctrl->have_keygrip = 0;
+ ctrl->digest.valuelen = 0;
+
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+
+ clear_nonce_cache (ctrl);
+
+ return 0;
+}
+
+
+/* Replace all '+' by a blank in the string S. */
+static void
+plus_to_blank (char *s)
+{
+ for (; *s; s++)
+ {
+ if (*s == '+')
+ *s = ' ';
+ }
+}
+
+
+/* Parse a hex string. Return an Assuan error code or 0 on success and the
+ length of the parsed string in LEN. */
+static int
+parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
+{
+ const char *p;
+ size_t n;
+
+ /* parse the hash value */
+ for (p=string, n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p != ' ' && *p != '\t' && *p)
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
+ if ((n&1))
+ return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
+ *len = n;
+ return 0;
+}
+
+
+/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
+ provide space for 20 bytes. BUF is not changed if the function
+ returns an error. */
+static int
+parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
+{
+ int rc;
+ size_t n = 0;
+
+ rc = parse_hexstring (ctx, string, &n);
+ if (rc)
+ return rc;
+ n /= 2;
+ if (n != 20)
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip");
+
+ if (hex2bin (string, buf, 20) < 0)
+ return set_error (GPG_ERR_BUG, "hex2bin");
+
+ return 0;
+}
+
+
+/* Write an Assuan status line. KEYWORD is the first item on the
+ * status line. The following arguments are all separated by a space
+ * in the output. The last argument must be a NULL. Linefeeds and
+ * carriage returns characters (which are not allowed in an Assuan
+ * status line) are silently quoted in C-style. */
+gpg_error_t
+agent_write_status (ctrl_t ctrl, const char *keyword, ...)
+{
+ gpg_error_t err;
+ va_list arg_ptr;
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ va_start (arg_ptr, keyword);
+ err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
+ va_end (arg_ptr);
+ return err;
+}
+
+
+/* This function is similar to print_assuan_status but takes a CTRL
+ arg instead of an assuan context as first argument. */
+gpg_error_t
+agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
+{
+ gpg_error_t err;
+ va_list arg_ptr;
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ va_start (arg_ptr, format);
+ err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
+ va_end (arg_ptr);
+ return err;
+}
+
+
+/* Helper to notify the client about a launched Pinentry. Because
+ that might disturb some older clients, this is only done if enabled
+ via an option. Returns an gpg error code. */
+gpg_error_t
+agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
+{
+ char line[256];
+
+ if (!ctrl || !ctrl->server_local
+ || !ctrl->server_local->allow_pinentry_notify)
+ return 0;
+ snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
+ pid, extra?" ":"", extra? extra:"");
+ return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
+}
+
+
+/* An agent progress callback for Libgcrypt. This has been registered
+ * to be called via the progress dispatcher mechanism from
+ * gpg-agent.c */
+static void
+progress_cb (ctrl_t ctrl, const char *what, int printchar,
+ int current, int total)
+{
+ if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
+ ;
+ else if (printchar == '\n' && what && !strcmp (what, "primegen"))
+ agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
+ else
+ agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
+ what, printchar=='\n'?'X':printchar, current, total);
+}
+
+
+/* Helper to print a message while leaving a command. Note that this
+ * function does not call assuan_set_error; the caller may do this
+ * prior to calling us. */
+static gpg_error_t
+leave_cmd (assuan_context_t ctx, gpg_error_t err)
+{
+ if (err)
+ {
+ const char *name = assuan_get_command_name (ctx);
+ if (!name)
+ name = "?";
+
+ /* Not all users of gpg-agent know about the fully canceled
+ error code; map it back if needed. */
+ if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
+ {
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ if (!ctrl->server_local->allow_fully_canceled)
+ err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
+ }
+
+ /* Most code from common/ does not know the error source, thus
+ we fix this here. */
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
+ err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
+
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
+ log_error ("command '%s' failed: %s\n", name,
+ gpg_strerror (err));
+ else
+ log_error ("command '%s' failed: %s <%s>\n", name,
+ gpg_strerror (err), gpg_strsource (err));
+ }
+ return err;
+}
+
+
+
+static const char hlp_geteventcounter[] =
+ "GETEVENTCOUNTER\n"
+ "\n"
+ "Return a status line named EVENTCOUNTER with the current values\n"
+ "of all event counters. The values are decimal numbers in the range\n"
+ "0 to UINT_MAX and wrapping around to 0. The actual values should\n"
+ "not be relied upon, they shall only be used to detect a change.\n"
+ "\n"
+ "The currently defined counters are:\n"
+ "\n"
+ "ANY - Incremented with any change of any of the other counters.\n"
+ "KEY - Incremented for added or removed private keys.\n"
+ "CARD - Incremented for changes of the card readers stati.";
+static gpg_error_t
+cmd_geteventcounter (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
+ eventcounter.any,
+ eventcounter.key,
+ eventcounter.card);
+}
+
+
+/* This function should be called once for all key removals or
+ additions. This function is assured not to do any context
+ switches. */
+void
+bump_key_eventcounter (void)
+{
+ eventcounter.key++;
+ eventcounter.any++;
+}
+
+
+/* This function should be called for all card reader status
+ changes. This function is assured not to do any context
+ switches. */
+void
+bump_card_eventcounter (void)
+{
+ eventcounter.card++;
+ eventcounter.any++;
+}
+
+
+
+
+static const char hlp_istrusted[] =
+ "ISTRUSTED <hexstring_with_fingerprint>\n"
+ "\n"
+ "Return OK when we have an entry with this fingerprint in our\n"
+ "trustlist";
+static gpg_error_t
+cmd_istrusted (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc, n, i;
+ char *p;
+ char fpr[41];
+
+ /* Parse the fingerprint value. */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p || !(n == 40 || n == 32))
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
+ i = 0;
+ if (n==32)
+ {
+ strcpy (fpr, "00000000");
+ i += 8;
+ }
+ for (p=line; i < 40; p++, i++)
+ fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
+ fpr[i] = 0;
+ rc = agent_istrusted (ctrl, fpr, NULL);
+ if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
+ return rc;
+ else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
+ return gpg_error (GPG_ERR_NOT_TRUSTED);
+ else
+ return leave_cmd (ctx, rc);
+}
+
+
+static const char hlp_listtrusted[] =
+ "LISTTRUSTED\n"
+ "\n"
+ "List all entries from the trustlist.";
+static gpg_error_t
+cmd_listtrusted (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ (void)line;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ rc = agent_listtrusted (ctx);
+ return leave_cmd (ctx, rc);
+}
+
+
+static const char hlp_martrusted[] =
+ "MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
+ "\n"
+ "Store a new key in into the trustlist.";
+static gpg_error_t
+cmd_marktrusted (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc, n, i;
+ char *p;
+ char fpr[41];
+ int flag;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ /* parse the fingerprint value */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (!spacep (p) || !(n == 40 || n == 32))
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
+ i = 0;
+ if (n==32)
+ {
+ strcpy (fpr, "00000000");
+ i += 8;
+ }
+ for (p=line; i < 40; p++, i++)
+ fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
+ fpr[i] = 0;
+
+ while (spacep (p))
+ p++;
+ flag = *p++;
+ if ( (flag != 'S' && flag != 'P') || !spacep (p) )
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S");
+ while (spacep (p))
+ p++;
+
+ rc = agent_marktrusted (ctrl, p, fpr, flag);
+ return leave_cmd (ctx, rc);
+}
+
+
+
+
+static const char hlp_havekey[] =
+ "HAVEKEY <hexstrings_with_keygrips>\n"
+ "\n"
+ "Return success if at least one of the secret keys with the given\n"
+ "keygrips is available.";
+static gpg_error_t
+cmd_havekey (assuan_context_t ctx, char *line)
+{
+ gpg_error_t err;
+ unsigned char buf[20];
+
+ do
+ {
+ err = parse_keygrip (ctx, line, buf);
+ if (err)
+ return err;
+
+ if (!agent_key_available (buf))
+ return 0; /* Found. */
+
+ while (*line && *line != ' ' && *line != '\t')
+ line++;
+ while (*line == ' ' || *line == '\t')
+ line++;
+ }
+ while (*line);
+
+ /* No leave_cmd() here because errors are expected and would clutter
+ the log. */
+ return gpg_error (GPG_ERR_NO_SECKEY);
+}
+
+
+static const char hlp_sigkey[] =
+ "SIGKEY <hexstring_with_keygrip>\n"
+ "SETKEY <hexstring_with_keygrip>\n"
+ "\n"
+ "Set the key used for a sign or decrypt operation.";
+static gpg_error_t
+cmd_sigkey (assuan_context_t ctx, char *line)
+{
+ int rc;
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ rc = parse_keygrip (ctx, line, ctrl->keygrip);
+ if (rc)
+ return rc;
+ ctrl->have_keygrip = 1;
+ return 0;
+}
+
+
+static const char hlp_setkeydesc[] =
+ "SETKEYDESC plus_percent_escaped_string\n"
+ "\n"
+ "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
+ "or EXPORT_KEY operation if this operation requires a passphrase. If\n"
+ "this command is not used a default text will be used. Note, that\n"
+ "this description implictly selects the label used for the entry\n"
+ "box; if the string contains the string PIN (which in general will\n"
+ "not be translated), \"PIN\" is used, otherwise the translation of\n"
+ "\"passphrase\" is used. The description string should not contain\n"
+ "blanks unless they are percent or '+' escaped.\n"
+ "\n"
+ "The description is only valid for the next PKSIGN, PKDECRYPT,\n"
+ "IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
+static gpg_error_t
+cmd_setkeydesc (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ char *desc, *p;
+
+ for (p=line; *p == ' '; p++)
+ ;
+ desc = p;
+ p = strchr (desc, ' ');
+ if (p)
+ *p = 0; /* We ignore any garbage; we might late use it for other args. */
+
+ if (!*desc)
+ return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
+
+ /* Note, that we only need to replace the + characters and should
+ leave the other escaping in place because the escaped string is
+ send verbatim to the pinentry which does the unescaping (but not
+ the + replacing) */
+ plus_to_blank (desc);
+
+ xfree (ctrl->server_local->keydesc);
+
+ if (ctrl->restricted)
+ {
+ ctrl->server_local->keydesc = strconcat
+ ((ctrl->restricted == 2
+ ? _("Note: Request from the web browser.")
+ : _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL);
+ }
+ else
+ ctrl->server_local->keydesc = xtrystrdup (desc);
+ if (!ctrl->server_local->keydesc)
+ return out_of_core ();
+ return 0;
+}
+
+
+static const char hlp_sethash[] =
+ "SETHASH (--hash=<name>)|(<algonumber>) <hexstring>\n"
+ "\n"
+ "The client can use this command to tell the server about the data\n"
+ "(which usually is a hash) to be signed.";
+static gpg_error_t
+cmd_sethash (assuan_context_t ctx, char *line)
+{
+ int rc;
+ size_t n;
+ char *p;
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ unsigned char *buf;
+ char *endp;
+ int algo;
+
+ /* Parse the alternative hash options which may be used instead of
+ the algo number. */
+ if (has_option_name (line, "--hash"))
+ {
+ if (has_option (line, "--hash=sha1"))
+ algo = GCRY_MD_SHA1;
+ else if (has_option (line, "--hash=sha224"))
+ algo = GCRY_MD_SHA224;
+ else if (has_option (line, "--hash=sha256"))
+ algo = GCRY_MD_SHA256;
+ else if (has_option (line, "--hash=sha384"))
+ algo = GCRY_MD_SHA384;
+ else if (has_option (line, "--hash=sha512"))
+ algo = GCRY_MD_SHA512;
+ else if (has_option (line, "--hash=rmd160"))
+ algo = GCRY_MD_RMD160;
+ else if (has_option (line, "--hash=md5"))
+ algo = GCRY_MD_MD5;
+ else if (has_option (line, "--hash=tls-md5sha1"))
+ algo = MD_USER_TLS_MD5SHA1;
+ else
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
+ }
+ else
+ algo = 0;
+
+ line = skip_options (line);
+
+ if (!algo)
+ {
+ /* No hash option has been given: require an algo number instead */
+ algo = (int)strtoul (line, &endp, 10);
+ for (line = endp; *line == ' ' || *line == '\t'; line++)
+ ;
+ if (!algo || gcry_md_test_algo (algo))
+ return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
+ }
+ ctrl->digest.algo = algo;
+ ctrl->digest.raw_value = 0;
+
+ /* Parse the hash value. */
+ n = 0;
+ rc = parse_hexstring (ctx, line, &n);
+ if (rc)
+ return rc;
+ n /= 2;
+ if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
+ ;
+ else if (n != 16 && n != 20 && n != 24
+ && n != 28 && n != 32 && n != 48 && n != 64)
+ return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
+
+ if (n > MAX_DIGEST_LEN)
+ return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long");
+
+ buf = ctrl->digest.value;
+ ctrl->digest.valuelen = n;
+ for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
+ buf[n] = xtoi_2 (p);
+ for (; n < ctrl->digest.valuelen; n++)
+ buf[n] = 0;
+ return 0;
+}
+
+
+static const char hlp_pksign[] =
+ "PKSIGN [<options>] [<cache_nonce>]\n"
+ "\n"
+ "Perform the actual sign operation. Neither input nor output are\n"
+ "sensitive to eavesdropping.";
+static gpg_error_t
+cmd_pksign (assuan_context_t ctx, char *line)
+{
+ gpg_error_t err;
+ cache_mode_t cache_mode = CACHE_MODE_NORMAL;
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ membuf_t outbuf;
+ char *cache_nonce = NULL;
+ char *p;
+
+ line = skip_options (line);
+
+ for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+ ;
+ *p = '\0';
+ if (*line)
+ cache_nonce = xtrystrdup (line);
+
+ if (opt.ignore_cache_for_signing)
+ cache_mode = CACHE_MODE_IGNORE;
+ else if (!ctrl->server_local->use_cache_for_signing)
+ cache_mode = CACHE_MODE_IGNORE;
+
+ init_membuf (&outbuf, 512);
+
+ err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
+ &outbuf, cache_mode);
+ if (err)
+ clear_outbuf (&outbuf);
+ else
+ err = write_and_clear_outbuf (ctx, &outbuf);
+
+ xfree (cache_nonce);
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_pkdecrypt[] =
+ "PKDECRYPT [<options>]\n"
+ "\n"
+ "Perform the actual decrypt operation. Input is not\n"
+ "sensitive to eavesdropping.";
+static gpg_error_t
+cmd_pkdecrypt (assuan_context_t ctx, char *line)
+{
+ int rc;
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ unsigned char *value;
+ size_t valuelen;
+ membuf_t outbuf;
+ int padding;
+
+ (void)line;
+
+ /* First inquire the data to decrypt */
+ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
+ if (!rc)
+ rc = assuan_inquire (ctx, "CIPHERTEXT",
+ &value, &valuelen, MAXLEN_CIPHERTEXT);
+ if (rc)
+ return rc;
+
+ init_membuf (&outbuf, 512);
+
+ rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
+ value, valuelen, &outbuf, &padding);
+ xfree (value);
+ if (rc)
+ clear_outbuf (&outbuf);
+ else
+ {
+ if (padding != -1)
+ rc = print_assuan_status (ctx, "PADDING", "%d", padding);
+ else
+ rc = 0;
+ if (!rc)
+ rc = write_and_clear_outbuf (ctx, &outbuf);
+ }
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+ return leave_cmd (ctx, rc);
+}
+
+
+static const char hlp_genkey[] =
+ "GENKEY [--no-protection] [--preset] [--timestamp=<isodate>]\n"
+ " [--inq-passwd] [--passwd-nonce=<s>] [<cache_nonce>]\n"
+ "\n"
+ "Generate a new key, store the secret part and return the public\n"
+ "part. Here is an example transaction:\n"
+ "\n"
+ " C: GENKEY\n"
+ " S: INQUIRE KEYPARAM\n"
+ " C: D (genkey (rsa (nbits 2048)))\n"
+ " C: END\n"
+ " S: D (public-key\n"
+ " S: D (rsa (n 326487324683264) (e 10001)))\n"
+ " S: OK key created\n"
+ "\n"
+ "If the --preset option is used the passphrase for the generated\n"
+ "key will be added to the cache. If --inq-passwd is used an inquire\n"
+ "with the keyword NEWPASSWD is used to request the passphrase for the\n"
+ "new key. If a --passwd-nonce is used, the corresponding cached\n"
+ "passphrase is used to protect the new key. If --timestamp is given\n"
+ "its value is recorded as the key's creation time; the value is\n"
+ "expected in ISO format (e.g. \"20030316T120000\").";
+static gpg_error_t
+cmd_genkey (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ int no_protection;
+ unsigned char *value = NULL;
+ size_t valuelen;
+ unsigned char *newpasswd = NULL;
+ membuf_t outbuf;
+ char *cache_nonce = NULL;
+ char *passwd_nonce = NULL;
+ int opt_preset;
+ int opt_inq_passwd;
+ size_t n;
+ char *p, *pend;
+ const char *s;
+ time_t opt_timestamp;
+ int c;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ no_protection = has_option (line, "--no-protection");
+ opt_preset = has_option (line, "--preset");
+ opt_inq_passwd = has_option (line, "--inq-passwd");
+ passwd_nonce = option_value (line, "--passwd-nonce");
+ if (passwd_nonce)
+ {
+ for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
+ ;
+ c = *pend;
+ *pend = '\0';
+ passwd_nonce = xtrystrdup (passwd_nonce);
+ *pend = c;
+ if (!passwd_nonce)
+ {
+ rc = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+ if ((s=has_option_name (line, "--timestamp")))
+ {
+ if (*s != '=')
+ {
+ rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
+ goto leave;
+ }
+ opt_timestamp = isotime2epoch (s+1);
+ if (opt_timestamp < 1)
+ {
+ rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
+ goto leave;
+ }
+ }
+ else
+ opt_timestamp = 0;
+ line = skip_options (line);
+
+ for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+ ;
+ *p = '\0';
+ if (*line)
+ cache_nonce = xtrystrdup (line);
+
+ /* First inquire the parameters */
+ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
+ if (!rc)
+ rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
+ if (rc)
+ return rc;
+
+ init_membuf (&outbuf, 512);
+
+ /* If requested, ask for the password to be used for the key. If
+ this is not used the regular Pinentry mechanism is used. */
+ if (opt_inq_passwd && !no_protection)
+ {
+ /* (N is used as a dummy) */
+ assuan_begin_confidential (ctx);
+ rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
+ assuan_end_confidential (ctx);
+ if (rc)
+ goto leave;
+ if (!*newpasswd)
+ {
+ /* Empty password given - switch to no-protection mode. */
+ xfree (newpasswd);
+ newpasswd = NULL;
+ no_protection = 1;
+ }
+
+ }
+ else if (passwd_nonce)
+ newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
+
+ rc = agent_genkey (ctrl, cache_nonce, opt_timestamp,
+ (char*)value, valuelen, no_protection,
+ newpasswd, opt_preset, &outbuf);
+
+ leave:
+ if (newpasswd)
+ {
+ /* Assuan_inquire does not allow us to read into secure memory
+ thus we need to wipe it ourself. */
+ wipememory (newpasswd, strlen (newpasswd));
+ xfree (newpasswd);
+ }
+ xfree (value);
+ if (rc)
+ clear_outbuf (&outbuf);
+ else
+ rc = write_and_clear_outbuf (ctx, &outbuf);
+ xfree (cache_nonce);
+ xfree (passwd_nonce);
+ return leave_cmd (ctx, rc);
+}
+
+
+
+
+static const char hlp_readkey[] =
+ "READKEY [--no-data] <hexstring_with_keygrip>\n"
+ " --card <keyid>\n"
+ "\n"
+ "Return the public key for the given keygrip or keyid.\n"
+ "With --card, private key file with card information will be created.";
+static gpg_error_t
+cmd_readkey (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char grip[20];
+ gcry_sexp_t s_pkey = NULL;
+ unsigned char *pkbuf = NULL;
+ char *serialno = NULL;
+ char *keyidbuf = NULL;
+ size_t pkbuflen;
+ int opt_card, opt_no_data;
+ char *dispserialno = NULL;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ opt_no_data = has_option (line, "--no-data");
+ opt_card = has_option (line, "--card");
+ line = skip_options (line);
+
+ if (opt_card)
+ {
+ const char *keyid = line;
+
+ rc = agent_card_getattr (ctrl, "SERIALNO", &serialno);
+ if (rc)
+ {
+ log_error (_("error getting serial number of card: %s\n"),
+ gpg_strerror (rc));
+ goto leave;
+ }
+
+ /* Hack to create the shadow key for the standard keys. */
+ if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID")
+ || !strcmp (keyid, "$AUTHKEYID"))
+ && !agent_card_getattr (ctrl, keyid, &keyidbuf))
+ keyid = keyidbuf;
+
+ rc = agent_card_readkey (ctrl, keyid, &pkbuf);
+ if (rc)
+ goto leave;
+ pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
+ rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
+ if (rc)
+ goto leave;
+
+ if (!gcry_pk_get_keygrip (s_pkey, grip))
+ {
+ rc = gcry_pk_testkey (s_pkey);
+ if (rc == 0)
+ rc = gpg_error (GPG_ERR_INTERNAL);
+
+ goto leave;
+ }
+
+ agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno);
+ if (agent_key_available (grip))
+ {
+ /* Shadow-key is not available in our key storage. */
+ rc = agent_write_shadow_key (0, grip, serialno, keyid, pkbuf, 0,
+ dispserialno);
+ }
+ else
+ {
+ /* Shadow-key is available in our key storage but ne check
+ * whether we need to update it with a new display-s/n or
+ * whatever. */
+ rc = agent_write_shadow_key (1, grip, serialno, keyid, pkbuf, 0,
+ dispserialno);
+ }
+ if (rc)
+ goto leave;
+
+ rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen);
+ }
+ else
+ {
+ rc = parse_keygrip (ctx, line, grip);
+ if (rc)
+ goto leave;
+
+ rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
+ if (!rc)
+ {
+ pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
+ log_assert (pkbuflen);
+ pkbuf = xtrymalloc (pkbuflen);
+ if (!pkbuf)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON,
+ pkbuf, pkbuflen);
+ rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen);
+ }
+ }
+ }
+
+ leave:
+ xfree (keyidbuf);
+ xfree (serialno);
+ xfree (pkbuf);
+ xfree (dispserialno);
+ gcry_sexp_release (s_pkey);
+ return leave_cmd (ctx, rc);
+}
+
+
+
+static const char hlp_keyinfo[] =
+ "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh] <keygrip>\n"
+ "\n"
+ "Return information about the key specified by the KEYGRIP. If the\n"
+ "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
+ "--list is given the keygrip is ignored and information about all\n"
+ "available keys are returned. If --ssh-list is given information\n"
+ "about all keys listed in the sshcontrol are returned. With --with-ssh\n"
+ "information from sshcontrol is always added to the info. Unless --data\n"
+ "is given, the information is returned as a status line using the format:\n"
+ "\n"
+ " KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
+ "\n"
+ "KEYGRIP is the keygrip.\n"
+ "\n"
+ "TYPE is describes the type of the key:\n"
+ " 'D' - Regular key stored on disk,\n"
+ " 'T' - Key is stored on a smartcard (token),\n"
+ " 'X' - Unknown type,\n"
+ " '-' - Key is missing.\n"
+ "\n"
+ "SERIALNO is an ASCII string with the serial number of the\n"
+ " smartcard. If the serial number is not known a single\n"
+ " dash '-' is used instead.\n"
+ "\n"
+ "IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
+ " is not known a dash is used instead.\n"
+ "\n"
+ "CACHED is 1 if the passphrase for the key was found in the key cache.\n"
+ " If not, a '-' is used instead.\n"
+ "\n"
+ "PROTECTION describes the key protection type:\n"
+ " 'P' - The key is protected with a passphrase,\n"
+ " 'C' - The key is not protected,\n"
+ " '-' - Unknown protection.\n"
+ "\n"
+ "FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
+ " printed if the option --ssh-fpr has been used. If ALGO is not given\n"
+ " to that option the default ssh fingerprint algo is used. Without the\n"
+ " option a '-' is printed.\n"
+ "\n"
+ "TTL is the TTL in seconds for that key or '-' if n/a.\n"
+ "\n"
+ "FLAGS is a word consisting of one-letter flags:\n"
+ " 'D' - The key has been disabled,\n"
+ " 'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
+ " 'c' - Use of the key needs to be confirmed,\n"
+ " '-' - No flags given.\n"
+ "\n"
+ "More information may be added in the future.";
+static gpg_error_t
+do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
+ int data, int with_ssh_fpr, int in_ssh,
+ int ttl, int disabled, int confirm)
+{
+ gpg_error_t err;
+ char hexgrip[40+1];
+ char *fpr = NULL;
+ int keytype;
+ unsigned char *shadow_info = NULL;
+ char *serialno = NULL;
+ char *idstr = NULL;
+ const char *keytypestr;
+ const char *cached;
+ const char *protectionstr;
+ char *pw;
+ int missing_key = 0;
+ char ttlbuf[20];
+ char flagsbuf[5];
+
+ err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
+ if (err)
+ {
+ if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+ missing_key = 1;
+ else
+ goto leave;
+ }
+
+ /* Reformat the grip so that we use uppercase as good style. */
+ bin2hex (grip, 20, hexgrip);
+
+ if (ttl > 0)
+ snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
+ else
+ strcpy (ttlbuf, "-");
+
+ *flagsbuf = 0;
+ if (disabled)
+ strcat (flagsbuf, "D");
+ if (in_ssh)
+ strcat (flagsbuf, "S");
+ if (confirm)
+ strcat (flagsbuf, "c");
+ if (!*flagsbuf)
+ strcpy (flagsbuf, "-");
+
+
+ if (missing_key)
+ {
+ protectionstr = "-"; keytypestr = "-";
+ }
+ else
+ {
+ switch (keytype)
+ {
+ case PRIVATE_KEY_CLEAR:
+ case PRIVATE_KEY_OPENPGP_NONE:
+ protectionstr = "C"; keytypestr = "D";
+ break;
+ case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
+ break;
+ case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
+ break;
+ default: protectionstr = "-"; keytypestr = "X";
+ break;
+ }
+ }
+
+ /* Compute the ssh fingerprint if requested. */
+ if (with_ssh_fpr)
+ {
+ gcry_sexp_t key;
+
+ if (!agent_raw_key_from_file (ctrl, grip, &key))
+ {
+ ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr);
+ gcry_sexp_release (key);
+ }
+ }
+
+ /* Here we have a little race by doing the cache check separately
+ from the retrieval function. Given that the cache flag is only a
+ hint, it should not really matter. */
+ pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL);
+ cached = pw ? "1" : "-";
+ xfree (pw);
+
+ if (shadow_info)
+ {
+ err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
+ if (err)
+ goto leave;
+ }
+
+ if (!data)
+ err = agent_write_status (ctrl, "KEYINFO",
+ hexgrip,
+ keytypestr,
+ serialno? serialno : "-",
+ idstr? idstr : "-",
+ cached,
+ protectionstr,
+ fpr? fpr : "-",
+ ttlbuf,
+ flagsbuf,
+ NULL);
+ else
+ {
+ char *string;
+
+ string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
+ hexgrip, keytypestr,
+ serialno? serialno : "-",
+ idstr? idstr : "-", cached, protectionstr,
+ fpr? fpr : "-",
+ ttlbuf,
+ flagsbuf);
+ if (!string)
+ err = gpg_error_from_syserror ();
+ else
+ err = assuan_send_data (ctx, string, strlen(string));
+ xfree (string);
+ }
+
+ leave:
+ xfree (fpr);
+ xfree (shadow_info);
+ xfree (serialno);
+ xfree (idstr);
+ return err;
+}
+
+
+/* Entry into the command KEYINFO. This function handles the
+ * command option processing. For details see hlp_keyinfo above. */
+static gpg_error_t
+cmd_keyinfo (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int err;
+ unsigned char grip[20];
+ gnupg_dir_t dir = NULL;
+ int list_mode;
+ int opt_data, opt_ssh_fpr, opt_with_ssh;
+ ssh_control_file_t cf = NULL;
+ char hexgrip[41];
+ int disabled, ttl, confirm, is_ssh;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ if (has_option (line, "--ssh-list"))
+ list_mode = 2;
+ else
+ list_mode = has_option (line, "--list");
+ opt_data = has_option (line, "--data");
+
+ if (has_option_name (line, "--ssh-fpr"))
+ {
+ if (has_option (line, "--ssh-fpr=md5"))
+ opt_ssh_fpr = GCRY_MD_MD5;
+ else if (has_option (line, "--ssh-fpr=sha1"))
+ opt_ssh_fpr = GCRY_MD_SHA1;
+ else if (has_option (line, "--ssh-fpr=sha256"))
+ opt_ssh_fpr = GCRY_MD_SHA256;
+ else
+ opt_ssh_fpr = opt.ssh_fingerprint_digest;
+ }
+ else
+ opt_ssh_fpr = 0;
+
+ opt_with_ssh = has_option (line, "--with-ssh");
+ line = skip_options (line);
+
+ if (opt_with_ssh || list_mode == 2)
+ cf = ssh_open_control_file ();
+
+ if (list_mode == 2)
+ {
+ if (cf)
+ {
+ while (!ssh_read_control_file (cf, hexgrip,
+ &disabled, &ttl, &confirm))
+ {
+ if (hex2bin (hexgrip, grip, 20) < 0 )
+ continue; /* Bad hex string. */
+ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
+ ttl, disabled, confirm);
+ if (err)
+ goto leave;
+ }
+ }
+ err = 0;
+ }
+ else if (list_mode)
+ {
+ char *dirname;
+ gnupg_dirent_t dir_entry;
+
+ dirname = make_filename_try (gnupg_homedir (),
+ GNUPG_PRIVATE_KEYS_DIR, NULL);
+ if (!dirname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ dir = gnupg_opendir (dirname);
+ if (!dir)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (dirname);
+ goto leave;
+ }
+ xfree (dirname);
+
+ while ( (dir_entry = gnupg_readdir (dir)) )
+ {
+ if (strlen (dir_entry->d_name) != 44
+ || strcmp (dir_entry->d_name + 40, ".key"))
+ continue;
+ strncpy (hexgrip, dir_entry->d_name, 40);
+ hexgrip[40] = 0;
+
+ if ( hex2bin (hexgrip, grip, 20) < 0 )
+ continue; /* Bad hex string. */
+
+ disabled = ttl = confirm = is_ssh = 0;
+ if (opt_with_ssh)
+ {
+ err = ssh_search_control_file (cf, hexgrip,
+ &disabled, &ttl, &confirm);
+ if (!err)
+ is_ssh = 1;
+ else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ goto leave;
+ }
+
+ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
+ ttl, disabled, confirm);
+ if (err)
+ goto leave;
+ }
+ err = 0;
+ }
+ else
+ {
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+ disabled = ttl = confirm = is_ssh = 0;
+ if (opt_with_ssh)
+ {
+ err = ssh_search_control_file (cf, line,
+ &disabled, &ttl, &confirm);
+ if (!err)
+ is_ssh = 1;
+ else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ goto leave;
+ }
+
+ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
+ ttl, disabled, confirm);
+ }
+
+ leave:
+ ssh_close_control_file (cf);
+ gnupg_closedir (dir);
+ if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ leave_cmd (ctx, err);
+ return err;
+}
+
+
+
+/* Helper for cmd_get_passphrase. */
+static int
+send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
+{
+ size_t n;
+ int rc;
+
+ assuan_begin_confidential (ctx);
+ n = strlen (pw);
+ if (via_data)
+ rc = assuan_send_data (ctx, pw, n);
+ else
+ {
+ char *p = xtrymalloc_secure (n*2+1);
+ if (!p)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ bin2hex (pw, n, p);
+ rc = assuan_set_okay_line (ctx, p);
+ xfree (p);
+ }
+ }
+ return rc;
+}
+
+
+/* Callback function to compare the first entered PIN with the one
+ currently being entered. */
+static gpg_error_t
+reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi)
+{
+ const char *pin1 = pi->check_cb_arg;
+
+ if (!strcmp (pin1, pi->pin))
+ return 0; /* okay */
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+}
+
+
+static const char hlp_get_passphrase[] =
+ "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
+ " [--qualitybar] [--newsymkey] <cache_id>\n"
+ " [<error_message> <prompt> <description>]\n"
+ "\n"
+ "This function is usually used to ask for a passphrase to be used\n"
+ "for conventional encryption, but may also be used by programs which\n"
+ "need specal handling of passphrases. This command uses a syntax\n"
+ "which helps clients to use the agent with minimum effort. The\n"
+ "agent either returns with an error or with a OK followed by the hex\n"
+ "encoded passphrase. Note that the length of the strings is\n"
+ "implicitly limited by the maximum length of a command.\n"
+ "\n"
+ "If the option \"--data\" is used the passphrase is returned by usual\n"
+ "data lines and not on the okay line.\n"
+ "\n"
+ "If the option \"--check\" is used the passphrase constraints checks as\n"
+ "implemented by gpg-agent are applied. A check is not done if the\n"
+ "passphrase has been found in the cache.\n"
+ "\n"
+ "If the option \"--no-ask\" is used and the passphrase is not in the\n"
+ "cache the user will not be asked to enter a passphrase but the error\n"
+ "code GPG_ERR_NO_DATA is returned. \n"
+ "\n"
+ "If the option\"--newsymkey\" is used the agent asks for a new passphrase\n"
+ "to be used in symmetric-only encryption. This must not be empty.\n"
+ "\n"
+ "If the option \"--qualitybar\" is used a visual indication of the\n"
+ "entered passphrase quality is shown. (Unless no minimum passphrase\n"
+ "length has been configured.)";
+static gpg_error_t
+cmd_get_passphrase (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *pw;
+ char *response = NULL;
+ char *response2 = NULL;
+ char *cacheid = NULL; /* May point into LINE. */
+ char *desc = NULL; /* Ditto */
+ char *prompt = NULL; /* Ditto */
+ char *errtext = NULL; /* Ditto */
+ const char *desc2 = _("Please re-enter this passphrase");
+ char *p;
+ int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey;
+ int opt_repeat = 0;
+ char *entry_errtext = NULL;
+ struct pin_entry_info_s *pi = NULL;
+ struct pin_entry_info_s *pi2 = NULL;
+ int is_generated;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ opt_data = has_option (line, "--data");
+ opt_check = has_option (line, "--check");
+ opt_no_ask = has_option (line, "--no-ask");
+ if (has_option_name (line, "--repeat"))
+ {
+ p = option_value (line, "--repeat");
+ if (p)
+ opt_repeat = atoi (p);
+ else
+ opt_repeat = 1;
+ }
+ opt_qualbar = has_option (line, "--qualitybar");
+ opt_newsymkey = has_option (line, "--newsymkey");
+ line = skip_options (line);
+
+ cacheid = line;
+ p = strchr (cacheid, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ errtext = p;
+ p = strchr (errtext, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ prompt = p;
+ p = strchr (prompt, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ desc = p;
+ p = strchr (desc, ' ');
+ if (p)
+ *p = 0; /* Ignore trailing garbage. */
+ }
+ }
+ }
+ if (!*cacheid || strlen (cacheid) > 50)
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
+ if (!desc)
+ return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
+
+ if (!strcmp (cacheid, "X"))
+ cacheid = NULL;
+ if (!strcmp (errtext, "X"))
+ errtext = NULL;
+ if (!strcmp (prompt, "X"))
+ prompt = NULL;
+ if (!strcmp (desc, "X"))
+ desc = NULL;
+
+ pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL;
+ if (pw)
+ {
+ rc = send_back_passphrase (ctx, opt_data, pw);
+ xfree (pw);
+ goto leave;
+ }
+ else if (opt_no_ask)
+ {
+ rc = gpg_error (GPG_ERR_NO_DATA);
+ goto leave;
+ }
+
+ /* Note, that we only need to replace the + characters and should
+ * leave the other escaping in place because the escaped string is
+ * send verbatim to the pinentry which does the unescaping (but not
+ * the + replacing) */
+ if (errtext)
+ plus_to_blank (errtext);
+ if (prompt)
+ plus_to_blank (prompt);
+ if (desc)
+ plus_to_blank (desc);
+
+ /* If opt_repeat is 2 or higher we can't use our pin_entry_info_s
+ * based method but fallback to the old simple method. It is
+ * anyway questionable whether this extra repeat count makes any
+ * real sense. */
+ if (opt_newsymkey && opt_repeat < 2)
+ {
+ /* We do not want to break any existing usage of this command
+ * and thus we introduced the option --newsymkey to make this
+ * command more useful to query the passphrase for symmetric
+ * encryption. */
+ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi)
+ {
+ rc = gpg_error_from_syserror ();
+ goto leave;
+ }
+ pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi2)
+ {
+ rc = gpg_error_from_syserror ();
+ goto leave;
+ }
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi->max_tries = 3;
+ pi->with_qualitybar = opt_qualbar;
+ pi->with_repeat = opt_repeat;
+ pi->constraints_flags = (CHECK_CONSTRAINTS_NOT_EMPTY
+ | CHECK_CONSTRAINTS_NEW_SYMKEY);
+ pi2->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi2->max_tries = 3;
+ pi2->check_cb = reenter_passphrase_cmp_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ for (;;) /* (degenerated for-loop) */
+ {
+ xfree (response);
+ response = NULL;
+ rc = agent_get_passphrase (ctrl, &response,
+ desc,
+ prompt,
+ entry_errtext? entry_errtext:errtext,
+ opt_qualbar, cacheid, CACHE_MODE_USER,
+ pi);
+ if (rc)
+ goto leave;
+ xfree (entry_errtext);
+ entry_errtext = NULL;
+ is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED);
+
+ /* We don't allow an empty passpharse in this mode. */
+ if (!is_generated
+ && check_passphrase_constraints (ctrl, pi->pin,
+ pi->constraints_flags,
+ &entry_errtext))
+ {
+ pi->failed_tries = 0;
+ pi2->failed_tries = 0;
+ continue;
+ }
+ if (*pi->pin && !pi->repeat_okay
+ && ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK
+ && opt_repeat)
+ {
+ /* The passphrase is empty and the pinentry did not
+ * already run the repetition check, do it here. This
+ * is only called when using an old and simple pinentry.
+ * It is neither called in loopback mode because the
+ * caller does any passphrase repetition by herself nor if
+ * no repetition was requested. */
+ xfree (response);
+ response = NULL;
+ rc = agent_get_passphrase (ctrl, &response,
+ L_("Please re-enter this passphrase"),
+ prompt,
+ entry_errtext? entry_errtext:errtext,
+ opt_qualbar, cacheid, CACHE_MODE_USER,
+ pi2);
+ if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
+ { /* The re-entered passphrase one did not match and
+ * the user did not hit cancel. */
+ entry_errtext = xtrystrdup (L_("does not match - try again"));
+ if (!entry_errtext)
+ {
+ rc = gpg_error_from_syserror ();
+ goto leave;
+ }
+ continue;
+ }
+ }
+ break;
+ }
+ if (!rc && *pi->pin)
+ {
+ /* Return the passphrase. */
+ if (cacheid)
+ agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0);
+ rc = send_back_passphrase (ctx, opt_data, pi->pin);
+ }
+ }
+ else
+ {
+ next_try:
+ xfree (response);
+ response = NULL;
+ rc = agent_get_passphrase (ctrl, &response, desc, prompt,
+ entry_errtext? entry_errtext:errtext,
+ opt_qualbar, cacheid, CACHE_MODE_USER, NULL);
+ xfree (entry_errtext);
+ entry_errtext = NULL;
+ is_generated = 0;
+
+ if (!rc)
+ {
+ int i;
+
+ if (opt_check
+ && !is_generated
+ && check_passphrase_constraints
+ (ctrl, response,
+ (opt_newsymkey? CHECK_CONSTRAINTS_NEW_SYMKEY:0),
+ &entry_errtext))
+ {
+ goto next_try;
+ }
+ for (i = 0; i < opt_repeat; i++)
+ {
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ break;
+
+ xfree (response2);
+ response2 = NULL;
+ rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
+ errtext, 0,
+ cacheid, CACHE_MODE_USER, NULL);
+ if (rc)
+ break;
+ if (strcmp (response2, response))
+ {
+ entry_errtext = try_percent_escape
+ (_("does not match - try again"), NULL);
+ if (!entry_errtext)
+ {
+ rc = gpg_error_from_syserror ();
+ break;
+ }
+ goto next_try;
+ }
+ }
+ if (!rc)
+ {
+ if (cacheid)
+ agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0);
+ rc = send_back_passphrase (ctx, opt_data, response);
+ }
+ }
+ }
+
+ leave:
+ xfree (response);
+ xfree (response2);
+ xfree (entry_errtext);
+ xfree (pi2);
+ xfree (pi);
+ return leave_cmd (ctx, rc);
+}
+
+
+static const char hlp_clear_passphrase[] =
+ "CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
+ "\n"
+ "may be used to invalidate the cache entry for a passphrase. The\n"
+ "function returns with OK even when there is no cached passphrase.\n"
+ "The --mode=normal option is used to clear an entry for a cacheid\n"
+ "added by the agent. The --mode=ssh option is used for a cacheid\n"
+ "added for ssh.\n";
+static gpg_error_t
+cmd_clear_passphrase (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ char *cacheid = NULL;
+ char *p;
+ cache_mode_t cache_mode = CACHE_MODE_USER;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ if (has_option (line, "--mode=normal"))
+ cache_mode = CACHE_MODE_NORMAL;
+ else if (has_option (line, "--mode=ssh"))
+ cache_mode = CACHE_MODE_SSH;
+
+ line = skip_options (line);
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ cacheid = p;
+ p = strchr (cacheid, ' ');
+ if (p)
+ *p = 0; /* ignore garbage */
+ if (!*cacheid || strlen (cacheid) > 50)
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
+
+ agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0);
+
+ agent_clear_passphrase (ctrl, cacheid, cache_mode);
+
+ return 0;
+}
+
+
+static const char hlp_get_confirmation[] =
+ "GET_CONFIRMATION <description>\n"
+ "\n"
+ "This command may be used to ask for a simple confirmation.\n"
+ "DESCRIPTION is displayed along with a Okay and Cancel button. This\n"
+ "command uses a syntax which helps clients to use the agent with\n"
+ "minimum effort. The agent either returns with an error or with a\n"
+ "OK. Note, that the length of DESCRIPTION is implicitly limited by\n"
+ "the maximum length of a command. DESCRIPTION should not contain\n"
+ "any spaces, those must be encoded either percent escaped or simply\n"
+ "as '+'.";
+static gpg_error_t
+cmd_get_confirmation (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *desc = NULL;
+ char *p;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ desc = p;
+ p = strchr (desc, ' ');
+ if (p)
+ *p = 0; /* We ignore any garbage -may be later used for other args. */
+
+ if (!*desc)
+ return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
+
+ if (!strcmp (desc, "X"))
+ desc = NULL;
+
+ /* Note, that we only need to replace the + characters and should
+ leave the other escaping in place because the escaped string is
+ send verbatim to the pinentry which does the unescaping (but not
+ the + replacing) */
+ if (desc)
+ plus_to_blank (desc);
+
+ rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+ return leave_cmd (ctx, rc);
+}
+
+
+
+static const char hlp_learn[] =
+ "LEARN [--send] [--sendinfo] [--force]\n"
+ "\n"
+ "Learn something about the currently inserted smartcard. With\n"
+ "--sendinfo information about the card is returned; with --send\n"
+ "the available certificates are returned as D lines; with --force\n"
+ "private key storage will be updated by the result.";
+static gpg_error_t
+cmd_learn (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ int send, sendinfo, force;
+
+ send = has_option (line, "--send");
+ sendinfo = send? 1 : has_option (line, "--sendinfo");
+ force = has_option (line, "--force");
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_passwd[] =
+ "PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
+ " [--verify] <hexkeygrip>\n"
+ "\n"
+ "Change the passphrase/PIN for the key identified by keygrip in LINE. If\n"
+ "--preset is used then the new passphrase will be added to the cache.\n"
+ "If --verify is used the command asks for the passphrase and verifies\n"
+ "that the passphrase valid.\n";
+static gpg_error_t
+cmd_passwd (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ int c;
+ char *cache_nonce = NULL;
+ char *passwd_nonce = NULL;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *shadow_info = NULL;
+ char *passphrase = NULL;
+ char *pend;
+ int opt_preset, opt_verify;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ opt_preset = has_option (line, "--preset");
+ cache_nonce = option_value (line, "--cache-nonce");
+ opt_verify = has_option (line, "--verify");
+ if (cache_nonce)
+ {
+ for (pend = cache_nonce; *pend && !spacep (pend); pend++)
+ ;
+ c = *pend;
+ *pend = '\0';
+ cache_nonce = xtrystrdup (cache_nonce);
+ *pend = c;
+ if (!cache_nonce)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ passwd_nonce = option_value (line, "--passwd-nonce");
+ if (passwd_nonce)
+ {
+ for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
+ ;
+ c = *pend;
+ *pend = '\0';
+ passwd_nonce = xtrystrdup (passwd_nonce);
+ *pend = c;
+ if (!passwd_nonce)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ line = skip_options (line);
+
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+
+ ctrl->in_passwd++;
+ err = agent_key_from_file (ctrl,
+ opt_verify? NULL : cache_nonce,
+ ctrl->server_local->keydesc,
+ grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
+ &s_skey, &passphrase);
+ if (err)
+ ;
+ else if (shadow_info)
+ {
+ log_error ("changing a smartcard PIN is not yet supported\n");
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ }
+ else if (opt_verify)
+ {
+ /* All done. */
+ if (passphrase)
+ {
+ if (!passwd_nonce)
+ {
+ char buf[12];
+ gcry_create_nonce (buf, 12);
+ passwd_nonce = bin2hex (buf, 12, NULL);
+ }
+ if (passwd_nonce
+ && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
+ passphrase, CACHE_TTL_NONCE))
+ {
+ assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
+ xfree (ctrl->server_local->last_passwd_nonce);
+ ctrl->server_local->last_passwd_nonce = passwd_nonce;
+ passwd_nonce = NULL;
+ }
+ }
+ }
+ else
+ {
+ char *newpass = NULL;
+
+ if (passwd_nonce)
+ newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
+ err = agent_protect_and_store (ctrl, s_skey, &newpass);
+ if (!err && passphrase)
+ {
+ /* A passphrase existed on the old key and the change was
+ successful. Return a nonce for that old passphrase to
+ let the caller try to unprotect the other subkeys with
+ the same key. */
+ if (!cache_nonce)
+ {
+ char buf[12];
+ gcry_create_nonce (buf, 12);
+ cache_nonce = bin2hex (buf, 12, NULL);
+ }
+ if (cache_nonce
+ && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
+ passphrase, CACHE_TTL_NONCE))
+ {
+ assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
+ xfree (ctrl->server_local->last_cache_nonce);
+ ctrl->server_local->last_cache_nonce = cache_nonce;
+ cache_nonce = NULL;
+ }
+ if (newpass)
+ {
+ /* If we have a new passphrase (which might be empty) we
+ store it under a passwd nonce so that the caller may
+ send that nonce again to use it for another key. */
+ if (!passwd_nonce)
+ {
+ char buf[12];
+ gcry_create_nonce (buf, 12);
+ passwd_nonce = bin2hex (buf, 12, NULL);
+ }
+ if (passwd_nonce
+ && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
+ newpass, CACHE_TTL_NONCE))
+ {
+ assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
+ xfree (ctrl->server_local->last_passwd_nonce);
+ ctrl->server_local->last_passwd_nonce = passwd_nonce;
+ passwd_nonce = NULL;
+ }
+ }
+ }
+ if (!err && opt_preset)
+ {
+ char hexgrip[40+1];
+ bin2hex(grip, 20, hexgrip);
+ err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass,
+ ctrl->cache_ttl_opt_preset);
+ }
+ xfree (newpass);
+ }
+ ctrl->in_passwd--;
+
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+
+ leave:
+ xfree (passphrase);
+ gcry_sexp_release (s_skey);
+ xfree (shadow_info);
+ xfree (cache_nonce);
+ xfree (passwd_nonce);
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_preset_passphrase[] =
+ "PRESET_PASSPHRASE [--inquire] <string_or_keygrip> <timeout> [<hexstring>]\n"
+ "\n"
+ "Set the cached passphrase/PIN for the key identified by the keygrip\n"
+ "to passwd for the given time, where -1 means infinite and 0 means\n"
+ "the default (currently only a timeout of -1 is allowed, which means\n"
+ "to never expire it). If passwd is not provided, ask for it via the\n"
+ "pinentry module unless --inquire is passed in which case the passphrase\n"
+ "is retrieved from the client via a server inquire.\n";
+static gpg_error_t
+cmd_preset_passphrase (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *grip_clear = NULL;
+ unsigned char *passphrase = NULL;
+ int ttl;
+ size_t len;
+ int opt_inquire;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ if (!opt.allow_preset_passphrase)
+ return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
+
+ opt_inquire = has_option (line, "--inquire");
+ line = skip_options (line);
+ grip_clear = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (!*line)
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+ *line = '\0';
+ line++;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+
+ /* Currently, only infinite timeouts are allowed. */
+ ttl = -1;
+ if (line[0] != '-' || line[1] != '1')
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ line++;
+ line++;
+ while (!(*line != ' ' && *line != '\t'))
+ line++;
+
+ /* Syntax check the hexstring. */
+ len = 0;
+ rc = parse_hexstring (ctx, line, &len);
+ if (rc)
+ return rc;
+ line[len] = '\0';
+
+ /* If there is a passphrase, use it. Currently, a passphrase is
+ required. */
+ if (*line)
+ {
+ if (opt_inquire)
+ {
+ rc = set_error (GPG_ERR_ASS_PARAMETER,
+ "both --inquire and passphrase specified");
+ goto leave;
+ }
+
+ /* Do in-place conversion. */
+ passphrase = line;
+ if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
+ rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
+ }
+ else if (opt_inquire)
+ {
+ /* Note that the passphrase will be truncated at any null byte and the
+ * limit is 480 characters. */
+ size_t maxlen = 480;
+
+ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
+ if (!rc)
+ {
+ assuan_begin_confidential (ctx);
+ rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
+ assuan_end_confidential (ctx);
+ }
+ }
+ else
+ rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
+
+ if (!rc)
+ {
+ rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl);
+ if (opt_inquire)
+ {
+ wipememory (passphrase, len);
+ xfree (passphrase);
+ }
+ }
+
+leave:
+ return leave_cmd (ctx, rc);
+}
+
+
+
+static const char hlp_scd[] =
+ "SCD <commands to pass to the scdaemon>\n"
+ " \n"
+ "This is a general quote command to redirect everything to the\n"
+ "SCdaemon.";
+static gpg_error_t
+cmd_scd (assuan_context_t ctx, char *line)
+{
+ int rc;
+#ifdef BUILD_WITH_SCDAEMON
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ rc = divert_generic_cmd (ctrl, line, ctx);
+#else
+ (void)ctx; (void)line;
+ rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
+#endif
+ return rc;
+}
+
+
+
+static const char hlp_keywrap_key[] =
+ "KEYWRAP_KEY [--clear] <mode>\n"
+ "\n"
+ "Return a key to wrap another key. For now the key is returned\n"
+ "verbatim and thus makes not much sense because an eavesdropper on\n"
+ "the gpg-agent connection will see the key as well as the wrapped key.\n"
+ "However, this function may either be equipped with a public key\n"
+ "mechanism or not used at all if the key is a pre-shared key. In any\n"
+ "case wrapping the import and export of keys is a requirement for\n"
+ "certain cryptographic validations and thus useful. The key persists\n"
+ "until a RESET command but may be cleared using the option --clear.\n"
+ "\n"
+ "Supported modes are:\n"
+ " --import - Return a key to import a key into gpg-agent\n"
+ " --export - Return a key to export a key from gpg-agent";
+static gpg_error_t
+cmd_keywrap_key (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ int clearopt = has_option (line, "--clear");
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ assuan_begin_confidential (ctx);
+ if (has_option (line, "--import"))
+ {
+ xfree (ctrl->server_local->import_key);
+ if (clearopt)
+ ctrl->server_local->import_key = NULL;
+ else if (!(ctrl->server_local->import_key =
+ gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
+ err = gpg_error_from_syserror ();
+ else
+ err = assuan_send_data (ctx, ctrl->server_local->import_key,
+ KEYWRAP_KEYSIZE);
+ }
+ else if (has_option (line, "--export"))
+ {
+ xfree (ctrl->server_local->export_key);
+ if (clearopt)
+ ctrl->server_local->export_key = NULL;
+ else if (!(ctrl->server_local->export_key =
+ gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
+ err = gpg_error_from_syserror ();
+ else
+ err = assuan_send_data (ctx, ctrl->server_local->export_key,
+ KEYWRAP_KEYSIZE);
+ }
+ else
+ err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
+ assuan_end_confidential (ctx);
+
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_import_key[] =
+ "IMPORT_KEY [--unattended] [--force] [--timestamp=<isodate>]\n"
+ " [<cache_nonce>]\n"
+ "\n"
+ "Import a secret key into the key store. The key is expected to be\n"
+ "encrypted using the current session's key wrapping key (cf. command\n"
+ "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
+ "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
+ "key data. The unwrapped key must be a canonical S-expression. The\n"
+ "option --unattended tries to import the key as-is without any\n"
+ "re-encryption. An existing key can be overwritten with --force.\n"
+ "If --timestamp is given its value is recorded as the key's creation\n"
+ "time; the value is expected in ISO format (e.g. \"20030316T120000\").";
+static gpg_error_t
+cmd_import_key (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ int opt_unattended;
+ time_t opt_timestamp;
+ int force;
+ unsigned char *wrappedkey = NULL;
+ size_t wrappedkeylen;
+ gcry_cipher_hd_t cipherhd = NULL;
+ unsigned char *key = NULL;
+ size_t keylen, realkeylen;
+ char *passphrase = NULL;
+ unsigned char *finalkey = NULL;
+ size_t finalkeylen;
+ unsigned char grip[20];
+ gcry_sexp_t openpgp_sexp = NULL;
+ char *cache_nonce = NULL;
+ char *p;
+ const char *s;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ if (!ctrl->server_local->import_key)
+ {
+ err = gpg_error (GPG_ERR_MISSING_KEY);
+ goto leave;
+ }
+
+ opt_unattended = has_option (line, "--unattended");
+ force = has_option (line, "--force");
+ if ((s=has_option_name (line, "--timestamp")))
+ {
+ if (*s != '=')
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
+ goto leave;
+ }
+ opt_timestamp = isotime2epoch (s+1);
+ if (opt_timestamp < 1)
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
+ goto leave;
+ }
+ }
+ else
+ opt_timestamp = 0;
+ line = skip_options (line);
+
+ for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+ ;
+ *p = '\0';
+ if (*line)
+ cache_nonce = xtrystrdup (line);
+
+ assuan_begin_confidential (ctx);
+ err = assuan_inquire (ctx, "KEYDATA",
+ &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
+ assuan_end_confidential (ctx);
+ if (err)
+ goto leave;
+ if (wrappedkeylen < 24)
+ {
+ err = gpg_error (GPG_ERR_INV_LENGTH);
+ goto leave;
+ }
+ keylen = wrappedkeylen - 8;
+ key = xtrymalloc_secure (keylen);
+ if (!key)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
+ GCRY_CIPHER_MODE_AESWRAP, 0);
+ if (err)
+ goto leave;
+ err = gcry_cipher_setkey (cipherhd,
+ ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
+ if (err)
+ goto leave;
+ err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
+ if (err)
+ goto leave;
+ gcry_cipher_close (cipherhd);
+ cipherhd = NULL;
+ xfree (wrappedkey);
+ wrappedkey = NULL;
+
+ realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
+ if (!realkeylen)
+ goto leave; /* Invalid canonical encoded S-expression. */
+
+ err = keygrip_from_canon_sexp (key, realkeylen, grip);
+ if (err)
+ {
+ /* This might be due to an unsupported S-expression format.
+ Check whether this is openpgp-private-key and trigger that
+ import code. */
+ if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
+ {
+ const char *tag;
+ size_t taglen;
+
+ tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
+ if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
+ ;
+ else
+ {
+ gcry_sexp_release (openpgp_sexp);
+ openpgp_sexp = NULL;
+ }
+ }
+ if (!openpgp_sexp)
+ goto leave; /* Note that ERR is still set. */
+ }
+
+ if (openpgp_sexp)
+ {
+ /* In most cases the key is encrypted and thus the conversion
+ function from the OpenPGP format to our internal format will
+ ask for a passphrase. That passphrase will be returned and
+ used to protect the key using the same code as for regular
+ key import. */
+
+ xfree (key);
+ key = NULL;
+ err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
+ ctrl->server_local->keydesc, cache_nonce,
+ &key, opt_unattended? NULL : &passphrase);
+ if (err)
+ goto leave;
+ realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
+ if (!realkeylen)
+ goto leave; /* Invalid canonical encoded S-expression. */
+ if (passphrase)
+ {
+ assert (!opt_unattended);
+ if (!cache_nonce)
+ {
+ char buf[12];
+ gcry_create_nonce (buf, 12);
+ cache_nonce = bin2hex (buf, 12, NULL);
+ }
+ if (cache_nonce
+ && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
+ passphrase, CACHE_TTL_NONCE))
+ assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
+ }
+ }
+ else if (opt_unattended)
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER,
+ "\"--unattended\" may only be used with OpenPGP keys");
+ goto leave;
+ }
+ else
+ {
+ if (!force && !agent_key_available (grip))
+ err = gpg_error (GPG_ERR_EEXIST);
+ else
+ {
+ char *prompt = xtryasprintf
+ (_("Please enter the passphrase to protect the "
+ "imported object within the %s system."), GNUPG_NAME);
+ if (!prompt)
+ err = gpg_error_from_syserror ();
+ else
+ err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
+ xfree (prompt);
+ }
+ if (err)
+ goto leave;
+ }
+
+ if (passphrase)
+ {
+ err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
+ ctrl->s2k_count, -1);
+ if (!err)
+ err = agent_write_private_key (grip, finalkey, finalkeylen, force,
+ opt_timestamp, NULL, NULL, NULL);
+ }
+ else
+ err = agent_write_private_key (grip, key, realkeylen, force,
+ opt_timestamp, NULL, NULL, NULL);
+
+ leave:
+ gcry_sexp_release (openpgp_sexp);
+ xfree (finalkey);
+ xfree (passphrase);
+ xfree (key);
+ gcry_cipher_close (cipherhd);
+ xfree (wrappedkey);
+ xfree (cache_nonce);
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_export_key[] =
+ "EXPORT_KEY [--cache-nonce=<nonce>] [--openpgp] <hexstring_with_keygrip>\n"
+ "\n"
+ "Export a secret key from the key store. The key will be encrypted\n"
+ "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
+ "using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n"
+ "prior to using this command. The function takes the keygrip as argument.\n"
+ "\n"
+ "If --openpgp is used, the secret key material will be exported in RFC 4880\n"
+ "compatible passphrase-protected form. Without --openpgp, the secret key\n"
+ "material will be exported in the clear (after prompting the user to unlock\n"
+ "it, if needed).\n";
+static gpg_error_t
+cmd_export_key (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *key = NULL;
+ size_t keylen;
+ gcry_cipher_hd_t cipherhd = NULL;
+ unsigned char *wrappedkey = NULL;
+ size_t wrappedkeylen;
+ int openpgp;
+ char *cache_nonce;
+ char *passphrase = NULL;
+ unsigned char *shadow_info = NULL;
+ char *pend;
+ int c;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ openpgp = has_option (line, "--openpgp");
+ cache_nonce = option_value (line, "--cache-nonce");
+ if (cache_nonce)
+ {
+ for (pend = cache_nonce; *pend && !spacep (pend); pend++)
+ ;
+ c = *pend;
+ *pend = '\0';
+ cache_nonce = xtrystrdup (cache_nonce);
+ *pend = c;
+ if (!cache_nonce)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+ line = skip_options (line);
+
+ if (!ctrl->server_local->export_key)
+ {
+ err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
+ goto leave;
+ }
+
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+
+ if (agent_key_available (grip))
+ {
+ err = gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+
+ /* Get the key from the file. With the openpgp flag we also ask for
+ the passphrase so that we can use it to re-encrypt it. */
+ err = agent_key_from_file (ctrl, cache_nonce,
+ ctrl->server_local->keydesc, grip,
+ &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
+ openpgp ? &passphrase : NULL);
+ if (err)
+ goto leave;
+ if (shadow_info)
+ {
+ /* Key is on a smartcard. */
+ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ goto leave;
+ }
+
+ if (openpgp)
+ {
+ /* The openpgp option changes the key format into the OpenPGP
+ key transfer format. The result is already a padded
+ canonical S-expression. */
+ if (!passphrase)
+ {
+ err = agent_ask_new_passphrase
+ (ctrl, _("This key (or subkey) is not protected with a passphrase."
+ " Please enter a new passphrase to export it."),
+ &passphrase);
+ if (err)
+ goto leave;
+ }
+ err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
+ if (!err && passphrase)
+ {
+ if (!cache_nonce)
+ {
+ char buf[12];
+ gcry_create_nonce (buf, 12);
+ cache_nonce = bin2hex (buf, 12, NULL);
+ }
+ if (cache_nonce
+ && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
+ passphrase, CACHE_TTL_NONCE))
+ {
+ assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
+ xfree (ctrl->server_local->last_cache_nonce);
+ ctrl->server_local->last_cache_nonce = cache_nonce;
+ cache_nonce = NULL;
+ }
+ }
+ }
+ else
+ {
+ /* Convert into a canonical S-expression and wrap that. */
+ err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
+ }
+ if (err)
+ goto leave;
+ gcry_sexp_release (s_skey);
+ s_skey = NULL;
+
+ err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
+ GCRY_CIPHER_MODE_AESWRAP, 0);
+ if (err)
+ goto leave;
+ err = gcry_cipher_setkey (cipherhd,
+ ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
+ if (err)
+ goto leave;
+
+ wrappedkeylen = keylen + 8;
+ wrappedkey = xtrymalloc (wrappedkeylen);
+ if (!wrappedkey)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
+ if (err)
+ goto leave;
+ xfree (key);
+ key = NULL;
+ gcry_cipher_close (cipherhd);
+ cipherhd = NULL;
+
+ assuan_begin_confidential (ctx);
+ err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
+ assuan_end_confidential (ctx);
+
+
+ leave:
+ xfree (cache_nonce);
+ xfree (passphrase);
+ xfree (wrappedkey);
+ gcry_cipher_close (cipherhd);
+ xfree (key);
+ gcry_sexp_release (s_skey);
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+ xfree (shadow_info);
+
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_delete_key[] =
+ "DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
+ "\n"
+ "Delete a secret key from the key store. If --force is used\n"
+ "and a loopback pinentry is allowed, the agent will not ask\n"
+ "the user for confirmation. If --stub-only is used the key will\n"
+ "only be deleted if it is a reference to a token.";
+static gpg_error_t
+cmd_delete_key (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ int force, stub_only;
+ unsigned char grip[20];
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ force = has_option (line, "--force");
+ stub_only = has_option (line, "--stub-only");
+ line = skip_options (line);
+
+ /* If the use of a loopback pinentry has been disabled, we assume
+ * that a silent deletion of keys shall also not be allowed. */
+ if (!opt.allow_loopback_pinentry)
+ force = 0;
+
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+
+ err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
+ force, stub_only);
+ if (err)
+ goto leave;
+
+ leave:
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+
+ return leave_cmd (ctx, err);
+}
+
+
+
+#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
+#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
+#else
+#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
+#endif
+
+static const char hlp_keytocard[] =
+ "KEYTOCARD [--force] <hexstring_with_keygrip> <serialno> <id> <timestamp>\n"
+ "\n";
+static gpg_error_t
+cmd_keytocard (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int force;
+ gpg_error_t err = 0;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *keydata;
+ size_t keydatalen;
+ const char *serialno, *timestamp_str, *id;
+ unsigned char *shadow_info = NULL;
+ time_t timestamp;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ force = has_option (line, "--force");
+ line = skip_options (line);
+
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+
+ if (agent_key_available (grip))
+ {
+ err =gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+
+ /* Fixme: Replace the parsing code by split_fields(). */
+ line += 40;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+ serialno = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (!*line)
+ {
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ goto leave;
+ }
+ *line = '\0';
+ line++;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+ id = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (!*line)
+ {
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ goto leave;
+ }
+ *line = '\0';
+ line++;
+ while (*line && (*line == ' ' || *line == '\t'))
+ line++;
+ timestamp_str = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (*line)
+ *line = '\0';
+
+ if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1))
+ {
+ err = gpg_error (GPG_ERR_INV_TIME);
+ goto leave;
+ }
+
+ err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
+ &shadow_info, CACHE_MODE_IGNORE, NULL,
+ &s_skey, NULL);
+ if (err)
+ {
+ xfree (shadow_info);
+ goto leave;
+ }
+ if (shadow_info)
+ {
+ /* Key is on a smartcard already. */
+ xfree (shadow_info);
+ gcry_sexp_release (s_skey);
+ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ goto leave;
+ }
+
+ keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
+ keydata = xtrymalloc_secure (keydatalen + 30);
+ if (keydata == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_sexp_release (s_skey);
+ goto leave;
+ }
+
+ gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
+ gcry_sexp_release (s_skey);
+ keydatalen--; /* Decrement for last '\0'. */
+ /* Add timestamp "created-at" in the private key */
+ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp);
+ keydatalen += 10 + 19 - 1;
+ err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen);
+ xfree (keydata);
+
+ leave:
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_getval[] =
+ "GETVAL <key>\n"
+ "\n"
+ "Return the value for KEY from the special environment as created by\n"
+ "PUTVAL.";
+static gpg_error_t
+cmd_getval (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc = 0;
+ char *key = NULL;
+ char *p;
+ struct putval_item_s *vl;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ for (p=line; *p == ' '; p++)
+ ;
+ key = p;
+ p = strchr (key, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ for (; *p == ' '; p++)
+ ;
+ if (*p)
+ return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
+ }
+ if (!*key)
+ return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
+
+
+ for (vl=putval_list; vl; vl = vl->next)
+ if ( !strcmp (vl->d, key) )
+ break;
+
+ if (vl) /* Got an entry. */
+ rc = assuan_send_data (ctx, vl->d+vl->off, vl->len);
+ else
+ return gpg_error (GPG_ERR_NO_DATA);
+
+ return leave_cmd (ctx, rc);
+}
+
+
+static const char hlp_putval[] =
+ "PUTVAL <key> [<percent_escaped_value>]\n"
+ "\n"
+ "The gpg-agent maintains a kind of environment which may be used to\n"
+ "store key/value pairs in it, so that they can be retrieved later.\n"
+ "This may be used by helper daemons to daemonize themself on\n"
+ "invocation and register them with gpg-agent. Callers of the\n"
+ "daemon's service may now first try connect to get the information\n"
+ "for that service from gpg-agent through the GETVAL command and then\n"
+ "try to connect to that daemon. Only if that fails they may start\n"
+ "an own instance of the service daemon. \n"
+ "\n"
+ "KEY is an arbitrary symbol with the same syntax rules as keys\n"
+ "for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
+ "corresponding value; they should be similar to the values of\n"
+ "envronment variables but gpg-agent does not enforce any\n"
+ "restrictions. If that value is not given any value under that KEY\n"
+ "is removed from this special environment.";
+static gpg_error_t
+cmd_putval (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc = 0;
+ char *key = NULL;
+ char *value = NULL;
+ size_t valuelen = 0;
+ char *p;
+ struct putval_item_s *vl, *vlprev;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ for (p=line; *p == ' '; p++)
+ ;
+ key = p;
+ p = strchr (key, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ for (; *p == ' '; p++)
+ ;
+ if (*p)
+ {
+ value = p;
+ p = strchr (value, ' ');
+ if (p)
+ *p = 0;
+ valuelen = percent_plus_unescape_inplace (value, 0);
+ }
+ }
+ if (!*key)
+ return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
+
+
+ for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next)
+ if ( !strcmp (vl->d, key) )
+ break;
+
+ if (vl) /* Delete old entry. */
+ {
+ if (vlprev)
+ vlprev->next = vl->next;
+ else
+ putval_list = vl->next;
+ xfree (vl);
+ }
+
+ if (valuelen) /* Add entry. */
+ {
+ vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
+ if (!vl)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ vl->len = valuelen;
+ vl->off = strlen (key) + 1;
+ strcpy (vl->d, key);
+ memcpy (vl->d + vl->off, value, valuelen);
+ vl->next = putval_list;
+ putval_list = vl;
+ }
+ }
+
+ return leave_cmd (ctx, rc);
+}
+
+
+
+
+static const char hlp_updatestartuptty[] =
+ "UPDATESTARTUPTTY\n"
+ "\n"
+ "Set startup TTY and X11 DISPLAY variables to the values of this\n"
+ "session. This command is useful to pull future pinentries to\n"
+ "another screen. It is only required because there is no way in the\n"
+ "ssh-agent protocol to convey this information.";
+static gpg_error_t
+cmd_updatestartuptty (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ session_env_t se;
+ char *lc_ctype = NULL;
+ char *lc_messages = NULL;
+ int iterator;
+ const char *name;
+
+ (void)line;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ se = session_env_new ();
+ if (!se)
+ err = gpg_error_from_syserror ();
+
+ iterator = 0;
+ while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
+ {
+ const char *value = session_env_getenv (ctrl->session_env, name);
+ if (value)
+ err = session_env_setenv (se, name, value);
+ }
+
+ if (!err && ctrl->lc_ctype)
+ if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
+ err = gpg_error_from_syserror ();
+
+ if (!err && ctrl->lc_messages)
+ if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
+ err = gpg_error_from_syserror ();
+
+ if (err)
+ {
+ session_env_release (se);
+ xfree (lc_ctype);
+ xfree (lc_messages);
+ }
+ else
+ {
+ session_env_release (opt.startup_env);
+ opt.startup_env = se;
+ xfree (opt.startup_lc_ctype);
+ opt.startup_lc_ctype = lc_ctype;
+ xfree (opt.startup_lc_messages);
+ opt.startup_lc_messages = lc_messages;
+ }
+
+ return err;
+}
+
+
+
+static const char hlp_killagent[] =
+ "KILLAGENT\n"
+ "\n"
+ "Stop the agent.";
+static gpg_error_t
+cmd_killagent (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ ctrl->server_local->stopme = 1;
+ assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
+ return 0;
+}
+
+
+static const char hlp_reloadagent[] =
+ "RELOADAGENT\n"
+ "\n"
+ "This command is an alternative to SIGHUP\n"
+ "to reload the configuration.";
+static gpg_error_t
+cmd_reloadagent (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+
+ if (ctrl->restricted)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+ agent_sighup_action ();
+ return 0;
+}
+
+
+
+static const char hlp_getinfo[] =
+ "GETINFO <what>\n"
+ "\n"
+ "Multipurpose function to return a variety of information.\n"
+ "Supported values for WHAT are:\n"
+ "\n"
+ " version - Return the version of the program.\n"
+ " pid - Return the process id of the server.\n"
+ " socket_name - Return the name of the socket.\n"
+ " ssh_socket_name - Return the name of the ssh socket.\n"
+ " scd_running - Return OK if the SCdaemon is already running.\n"
+ " s2k_time - Return the time in milliseconds required for S2K.\n"
+ " s2k_count - Return the standard S2K count.\n"
+ " s2k_count_cal - Return the calibrated S2K count.\n"
+ " std_env_names - List the names of the standard environment.\n"
+ " std_session_env - List the standard session environment.\n"
+ " std_startup_env - List the standard startup environment.\n"
+ " getenv NAME - Return value of envvar NAME.\n"
+ " connections - Return number of active connections.\n"
+ " jent_active - Returns OK if Libgcrypt's JENT is active.\n"
+ " restricted - Returns OK if the connection is in restricted mode.\n"
+ " cmd_has_option CMD OPT\n"
+ " - Returns OK if command CMD has option OPT.\n";
+static gpg_error_t
+cmd_getinfo (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc = 0;
+
+ if (!strcmp (line, "version"))
+ {
+ const char *s = VERSION;
+ rc = assuan_send_data (ctx, s, strlen (s));
+ }
+ else if (!strncmp (line, "cmd_has_option", 14)
+ && (line[14] == ' ' || line[14] == '\t' || !line[14]))
+ {
+ char *cmd, *cmdopt;
+ line += 14;
+ while (*line == ' ' || *line == '\t')
+ line++;
+ if (!*line)
+ rc = gpg_error (GPG_ERR_MISSING_VALUE);
+ else
+ {
+ cmd = line;
+ while (*line && (*line != ' ' && *line != '\t'))
+ line++;
+ if (!*line)
+ rc = gpg_error (GPG_ERR_MISSING_VALUE);
+ else
+ {
+ *line++ = 0;
+ while (*line == ' ' || *line == '\t')
+ line++;
+ if (!*line)
+ rc = gpg_error (GPG_ERR_MISSING_VALUE);
+ else
+ {
+ cmdopt = line;
+ if (!command_has_option (cmd, cmdopt))
+ rc = gpg_error (GPG_ERR_FALSE);
+ }
+ }
+ }
+ }
+ else if (!strcmp (line, "s2k_count"))
+ {
+ char numbuf[50];
+
+ snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strcmp (line, "restricted"))
+ {
+ rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE);
+ }
+ else if (ctrl->restricted)
+ {
+ rc = gpg_error (GPG_ERR_FORBIDDEN);
+ }
+ /* All sub-commands below are not allowed in restricted mode. */
+ else if (!strcmp (line, "pid"))
+ {
+ char numbuf[50];
+
+ snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strcmp (line, "socket_name"))
+ {
+ const char *s = get_agent_socket_name ();
+
+ if (s)
+ rc = assuan_send_data (ctx, s, strlen (s));
+ else
+ rc = gpg_error (GPG_ERR_NO_DATA);
+ }
+ else if (!strcmp (line, "ssh_socket_name"))
+ {
+ const char *s = get_agent_ssh_socket_name ();
+
+ if (s)
+ rc = assuan_send_data (ctx, s, strlen (s));
+ else
+ rc = gpg_error (GPG_ERR_NO_DATA);
+ }
+ else if (!strcmp (line, "scd_running"))
+ {
+ rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_FALSE);
+ }
+ else if (!strcmp (line, "std_env_names"))
+ {
+ int iterator;
+ const char *name;
+
+ iterator = 0;
+ while ((name = session_env_list_stdenvnames (&iterator, NULL)))
+ {
+ rc = assuan_send_data (ctx, name, strlen (name)+1);
+ if (!rc)
+ rc = assuan_send_data (ctx, NULL, 0);
+ if (rc)
+ break;
+ }
+ }
+ else if (!strcmp (line, "std_session_env")
+ || !strcmp (line, "std_startup_env"))
+ {
+ int iterator;
+ const char *name, *value;
+ char *string;
+
+ iterator = 0;
+ while ((name = session_env_list_stdenvnames (&iterator, NULL)))
+ {
+ value = session_env_getenv_or_default
+ (line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
+ if (value)
+ {
+ string = xtryasprintf ("%s=%s", name, value);
+ if (!string)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ rc = assuan_send_data (ctx, string, strlen (string)+1);
+ if (!rc)
+ rc = assuan_send_data (ctx, NULL, 0);
+ }
+ if (rc)
+ break;
+ }
+ }
+ }
+ else if (!strncmp (line, "getenv", 6)
+ && (line[6] == ' ' || line[6] == '\t' || !line[6]))
+ {
+ line += 6;
+ while (*line == ' ' || *line == '\t')
+ line++;
+ if (!*line)
+ rc = gpg_error (GPG_ERR_MISSING_VALUE);
+ else
+ {
+ const char *s = getenv (line);
+ if (!s)
+ rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
+ else
+ rc = assuan_send_data (ctx, s, strlen (s));
+ }
+ }
+ else if (!strcmp (line, "connections"))
+ {
+ char numbuf[20];
+
+ snprintf (numbuf, sizeof numbuf, "%d",
+ get_agent_active_connection_count ());
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strcmp (line, "jent_active"))
+ {
+ char *buf;
+ char *fields[5];
+
+ buf = gcry_get_config (0, "rng-type");
+ if (buf
+ && split_fields_colon (buf, fields, DIM (fields)) >= 5
+ && atoi (fields[4]) > 0)
+ rc = 0;
+ else
+ rc = gpg_error (GPG_ERR_FALSE);
+ gcry_free (buf);
+ }
+ else if (!strcmp (line, "s2k_count_cal"))
+ {
+ char numbuf[50];
+
+ snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ());
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strcmp (line, "s2k_time"))
+ {
+ char numbuf[50];
+
+ snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ());
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else
+ rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
+ return rc;
+}
+
+
+
+/* This function is called by Libassuan to parse the OPTION command.
+ It has been registered similar to the other Assuan commands. */
+static gpg_error_t
+option_handler (assuan_context_t ctx, const char *key, const char *value)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+
+ if (!strcmp (key, "agent-awareness"))
+ {
+ /* The value is a version string telling us of which agent
+ version the caller is aware of. */
+ ctrl->server_local->allow_fully_canceled =
+ gnupg_compare_version (value, "2.1.0");
+ }
+ else if (ctrl->restricted)
+ {
+ err = gpg_error (GPG_ERR_FORBIDDEN);
+ }
+ /* All options below are not allowed in restricted mode. */
+ else if (!strcmp (key, "putenv"))
+ {
+ /* Change the session's environment to be used for the
+ Pinentry. Valid values are:
+ <NAME> Delete envvar NAME
+ <KEY>= Set envvar NAME to the empty string
+ <KEY>=<VALUE> Set envvar NAME to VALUE
+ */
+ err = session_env_putenv (ctrl->session_env, value);
+ }
+ else if (!strcmp (key, "display"))
+ {
+ err = session_env_setenv (ctrl->session_env, "DISPLAY", value);
+ }
+ else if (!strcmp (key, "ttyname"))
+ {
+ if (!opt.keep_tty)
+ err = session_env_setenv (ctrl->session_env, "GPG_TTY", value);
+ }
+ else if (!strcmp (key, "ttytype"))
+ {
+ if (!opt.keep_tty)
+ err = session_env_setenv (ctrl->session_env, "TERM", value);
+ }
+ else if (!strcmp (key, "lc-ctype"))
+ {
+ if (ctrl->lc_ctype)
+ xfree (ctrl->lc_ctype);
+ ctrl->lc_ctype = xtrystrdup (value);
+ if (!ctrl->lc_ctype)
+ return out_of_core ();
+ }
+ else if (!strcmp (key, "lc-messages"))
+ {
+ if (ctrl->lc_messages)
+ xfree (ctrl->lc_messages);
+ ctrl->lc_messages = xtrystrdup (value);
+ if (!ctrl->lc_messages)
+ return out_of_core ();
+ }
+ else if (!strcmp (key, "xauthority"))
+ {
+ err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value);
+ }
+ else if (!strcmp (key, "pinentry-user-data"))
+ {
+ err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
+ }
+ else if (!strcmp (key, "use-cache-for-signing"))
+ ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
+ else if (!strcmp (key, "allow-pinentry-notify"))
+ ctrl->server_local->allow_pinentry_notify = 1;
+ else if (!strcmp (key, "pinentry-mode"))
+ {
+ int tmp = parse_pinentry_mode (value);
+ if (tmp == -1)
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ else
+ ctrl->pinentry_mode = tmp;
+ }
+ else if (!strcmp (key, "cache-ttl-opt-preset"))
+ {
+ ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
+ }
+ else if (!strcmp (key, "s2k-count"))
+ {
+ ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
+ if (ctrl->s2k_count && ctrl->s2k_count < 65536)
+ {
+ ctrl->s2k_count = 0;
+ }
+ }
+ else if (!strcmp (key, "pretend-request-origin"))
+ {
+ log_assert (!ctrl->restricted);
+ switch (parse_request_origin (value))
+ {
+ case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break;
+ case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break;
+ case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break;
+ default:
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ /* Better pretend to be remote in case of a bad value. */
+ ctrl->restricted = 1;
+ break;
+ }
+ }
+ else
+ err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
+
+ return err;
+}
+
+
+
+
+/* Called by libassuan after all commands. ERR is the error from the
+ last assuan operation and not the one returned from the command. */
+static void
+post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)err;
+
+ /* Switch off any I/O monitor controlled logging pausing. */
+ ctrl->server_local->pause_io_logging = 0;
+}
+
+
+/* This function is called by libassuan for all I/O. We use it here
+ to disable logging for the GETEVENTCOUNTER commands. This is so
+ that the debug output won't get cluttered by this primitive
+ command. */
+static unsigned int
+io_monitor (assuan_context_t ctx, void *hook, int direction,
+ const char *line, size_t linelen)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void) hook;
+
+ /* We want to suppress all Assuan log messages for connections from
+ * self. However, assuan_get_pid works only after
+ * assuan_accept. Now, assuan_accept already logs a line ending with
+ * the process id. We use this hack here to get the peers pid so
+ * that we can compare it to our pid. We should add an assuan
+ * function to return the pid for a file descriptor and use that to
+ * detect connections to self. */
+ if (ctx && !ctrl->server_local->greeting_seen
+ && direction == ASSUAN_IO_TO_PEER)
+ {
+ ctrl->server_local->greeting_seen = 1;
+ if (linelen > 32
+ && !strncmp (line, "OK Pleased to meet you, process ", 32)
+ && strtoul (line+32, NULL, 10) == getpid ())
+ return ASSUAN_IO_MONITOR_NOLOG;
+ }
+
+
+ /* Do not log self-connections. This makes the log cleaner because
+ * we won't see the check-our-own-socket calls. */
+ if (ctx && ctrl->server_local->connect_from_self)
+ return ASSUAN_IO_MONITOR_NOLOG;
+
+ /* Note that we only check for the uppercase name. This allows the user to
+ see the logging for debugging if using a non-upercase command
+ name. */
+ if (ctx && direction == ASSUAN_IO_FROM_PEER
+ && linelen >= 15
+ && !strncmp (line, "GETEVENTCOUNTER", 15)
+ && (linelen == 15 || spacep (line+15)))
+ {
+ ctrl->server_local->pause_io_logging = 1;
+ }
+
+ return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0;
+}
+
+
+/* Return true if the command CMD implements the option OPT. */
+static int
+command_has_option (const char *cmd, const char *cmdopt)
+{
+ if (!strcmp (cmd, "GET_PASSPHRASE"))
+ {
+ if (!strcmp (cmdopt, "repeat"))
+ return 1;
+ if (!strcmp (cmdopt, "newsymkey"))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* Tell Libassuan about our commands. Also register the other Assuan
+ handlers. */
+static int
+register_commands (assuan_context_t ctx)
+{
+ static struct {
+ const char *name;
+ assuan_handler_t handler;
+ const char * const help;
+ } table[] = {
+ { "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter },
+ { "ISTRUSTED", cmd_istrusted, hlp_istrusted },
+ { "HAVEKEY", cmd_havekey, hlp_havekey },
+ { "KEYINFO", cmd_keyinfo, hlp_keyinfo },
+ { "SIGKEY", cmd_sigkey, hlp_sigkey },
+ { "SETKEY", cmd_sigkey, hlp_sigkey },
+ { "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc },
+ { "SETHASH", cmd_sethash, hlp_sethash },
+ { "PKSIGN", cmd_pksign, hlp_pksign },
+ { "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
+ { "GENKEY", cmd_genkey, hlp_genkey },
+ { "READKEY", cmd_readkey, hlp_readkey },
+ { "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase },
+ { "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase },
+ { "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase },
+ { "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation },
+ { "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted },
+ { "MARKTRUSTED", cmd_marktrusted, hlp_martrusted },
+ { "LEARN", cmd_learn, hlp_learn },
+ { "PASSWD", cmd_passwd, hlp_passwd },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { "SCD", cmd_scd, hlp_scd },
+ { "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key },
+ { "IMPORT_KEY", cmd_import_key, hlp_import_key },
+ { "EXPORT_KEY", cmd_export_key, hlp_export_key },
+ { "DELETE_KEY", cmd_delete_key, hlp_delete_key },
+ { "GETVAL", cmd_getval, hlp_getval },
+ { "PUTVAL", cmd_putval, hlp_putval },
+ { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },
+ { "KILLAGENT", cmd_killagent, hlp_killagent },
+ { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
+ { "GETINFO", cmd_getinfo, hlp_getinfo },
+ { "KEYTOCARD", cmd_keytocard, hlp_keytocard },
+ { NULL }
+ };
+ int i, rc;
+
+ for (i=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler,
+ table[i].help);
+ if (rc)
+ return rc;
+ }
+ assuan_register_post_cmd_notify (ctx, post_cmd_notify);
+ assuan_register_reset_notify (ctx, reset_notify);
+ assuan_register_option_handler (ctx, option_handler);
+ return 0;
+}
+
+
+/* Startup the server. If LISTEN_FD and FD is given as -1, this is a
+ simple piper server, otherwise it is a regular server. CTRL is the
+ control structure for this connection; it has only the basic
+ initialization. */
+void
+start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
+{
+ int rc;
+ assuan_context_t ctx = NULL;
+
+ if (ctrl->restricted)
+ {
+ if (agent_copy_startup_env (ctrl))
+ return;
+ }
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc));
+ agent_exit (2);
+ }
+
+ if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
+ {
+ assuan_fd_t filedes[2];
+
+ filedes[0] = assuan_fdopen (0);
+ filedes[1] = assuan_fdopen (1);
+ rc = assuan_init_pipe_server (ctx, filedes);
+ }
+ else if (listen_fd != GNUPG_INVALID_FD)
+ {
+ rc = assuan_init_socket_server (ctx, listen_fd, 0);
+ /* FIXME: Need to call assuan_sock_set_nonce for Windows. But
+ this branch is currently not used. */
+ }
+ else
+ {
+ rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
+ }
+ if (rc)
+ {
+ log_error ("failed to initialize the server: %s\n",
+ gpg_strerror(rc));
+ agent_exit (2);
+ }
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error ("failed to register commands with Assuan: %s\n",
+ gpg_strerror(rc));
+ agent_exit (2);
+ }
+
+ assuan_set_pointer (ctx, ctrl);
+ ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
+ ctrl->server_local->assuan_ctx = ctx;
+ ctrl->server_local->use_cache_for_signing = 1;
+
+ ctrl->digest.raw_value = 0;
+
+ assuan_set_io_monitor (ctx, io_monitor, NULL);
+ agent_set_progress_cb (progress_cb, ctrl);
+
+ for (;;)
+ {
+ pid_t client_pid;
+
+ rc = assuan_accept (ctx);
+ if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
+ {
+ break;
+ }
+ else if (rc)
+ {
+ log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
+ break;
+ }
+
+ client_pid = assuan_get_pid (ctx);
+ ctrl->server_local->connect_from_self = (client_pid == getpid ());
+ if (client_pid != ASSUAN_INVALID_PID)
+ ctrl->client_pid = (unsigned long)client_pid;
+ else
+ ctrl->client_pid = 0;
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
+ continue;
+ }
+ }
+
+ /* Reset the nonce caches. */
+ clear_nonce_cache (ctrl);
+
+ /* Reset the SCD if needed. */
+ agent_reset_scd (ctrl);
+
+ /* Reset the pinentry (in case of popup messages). */
+ agent_reset_query (ctrl);
+
+ /* Cleanup. */
+ assuan_release (ctx);
+ xfree (ctrl->server_local->keydesc);
+ xfree (ctrl->server_local->import_key);
+ xfree (ctrl->server_local->export_key);
+ if (ctrl->server_local->stopme)
+ agent_exit (0);
+ xfree (ctrl->server_local);
+ ctrl->server_local = NULL;
+}
+
+
+/* Helper for the pinentry loopback mode. It merely passes the
+ parameters on to the client. */
+gpg_error_t
+pinentry_loopback(ctrl_t ctrl, const char *keyword,
+ unsigned char **buffer, size_t *size,
+ size_t max_length)
+{
+ gpg_error_t rc;
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
+ if (rc)
+ return rc;
+
+ assuan_begin_confidential (ctx);
+ rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
+ assuan_end_confidential (ctx);
+ return rc;
+}
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
new file mode 100644
index 0000000..78f2989
--- /dev/null
+++ b/agent/cvt-openpgp.c
@@ -0,0 +1,1413 @@
+/* cvt-openpgp.c - Convert an OpenPGP key to our internal format.
+ * Copyright (C) 1998-2002, 2006, 2009, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2013, 2014 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "agent.h"
+#include "../common/i18n.h"
+#include "cvt-openpgp.h"
+#include "../common/host2net.h"
+
+
+/* Helper to pass data via the callback to do_unprotect. */
+struct try_do_unprotect_arg_s
+{
+ int is_v4;
+ int is_protected;
+ int pubkey_algo;
+ const char *curve;
+ int protect_algo;
+ char *iv;
+ int ivlen;
+ int s2k_mode;
+ int s2k_algo;
+ byte *s2k_salt;
+ u32 s2k_count;
+ u16 desired_csum;
+ gcry_mpi_t *skey;
+ size_t skeysize;
+ int skeyidx;
+ gcry_sexp_t *r_key;
+};
+
+
+
+/* Compute the keygrip from the public key and store it at GRIP. */
+static gpg_error_t
+get_keygrip (int pubkey_algo, const char *curve, gcry_mpi_t *pkey,
+ unsigned char *grip)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_pkey = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
+ pkey[0], pkey[1], pkey[2], pkey[3]);
+ break;
+
+ case GCRY_PK_ELG:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(elg(p%m)(g%m)(y%m)))",
+ pkey[0], pkey[1], pkey[2]);
+ break;
+
+ case GCRY_PK_RSA:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
+ break;
+
+ case GCRY_PK_ECC:
+ if (!curve)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ const char *format;
+
+ if (!strcmp (curve, "Ed25519"))
+ format = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))";
+ else if (!strcmp (curve, "Curve25519"))
+ format = "(public-key(ecc(curve %s)(flags djb-tweak)(q%m)))";
+ else
+ format = "(public-key(ecc(curve %s)(q%m)))";
+
+ err = gcry_sexp_build (&s_pkey, NULL, format, curve, pkey[0]);
+ }
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
+ err = gpg_error (GPG_ERR_INTERNAL);
+
+ gcry_sexp_release (s_pkey);
+ return err;
+}
+
+
+/* Convert a secret key given as algorithm id and an array of key
+ parameters into our s-expression based format. Note that
+ PUBKEY_ALGO has an gcrypt algorithm number. */
+static gpg_error_t
+convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
+ const char *curve)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))",
+ skey[0], skey[1], skey[2], skey[3], skey[4]);
+ break;
+
+ case GCRY_PK_ELG:
+ case GCRY_PK_ELG_E:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(elg(p%m)(g%m)(y%m)(x%m)))",
+ skey[0], skey[1], skey[2], skey[3]);
+ break;
+
+
+ case GCRY_PK_RSA:
+ case GCRY_PK_RSA_E:
+ case GCRY_PK_RSA_S:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+ skey[0], skey[1], skey[2], skey[3], skey[4],
+ skey[5]);
+ break;
+
+ case GCRY_PK_ECC:
+ if (!curve)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ const char *format;
+
+ if (!strcmp (curve, "Ed25519"))
+ /* Do not store the OID as name but the real name and the
+ EdDSA flag. */
+ format = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%M)))";
+ else if (!strcmp (curve, "Curve25519"))
+ format = "(private-key(ecc(curve %s)(flags djb-tweak)(q%m)(d%m)))";
+ else
+ format = "(private-key(ecc(curve %s)(q%m)(d%m)))";
+
+ err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], skey[1]);
+ }
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err)
+ *r_key = s_skey;
+ return err;
+}
+
+
+/* Convert a secret key given as algorithm id, an array of key
+ parameters, and an S-expression of the original OpenPGP transfer
+ key into our s-expression based format. This is a variant of
+ convert_secret_key which is used for the openpgp-native protection
+ mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */
+static gpg_error_t
+convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
+ const char *curve, gcry_sexp_t transfer_key)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build
+ (&s_skey, NULL,
+ "(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)"
+ "(protected openpgp-native%S)))",
+ skey[0], skey[1], skey[2], skey[3], transfer_key);
+ break;
+
+ case GCRY_PK_ELG:
+ err = gcry_sexp_build
+ (&s_skey, NULL,
+ "(protected-private-key(elg(p%m)(g%m)(y%m)"
+ "(protected openpgp-native%S)))",
+ skey[0], skey[1], skey[2], transfer_key);
+ break;
+
+
+ case GCRY_PK_RSA:
+ err = gcry_sexp_build
+ (&s_skey, NULL,
+ "(protected-private-key(rsa(n%m)(e%m)"
+ "(protected openpgp-native%S)))",
+ skey[0], skey[1], transfer_key );
+ break;
+
+ case GCRY_PK_ECC:
+ if (!curve)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ const char *format;
+
+ if (!strcmp (curve, "Ed25519"))
+ /* Do not store the OID as name but the real name and the
+ EdDSA flag. */
+ format = "(protected-private-key(ecc(curve %s)(flags eddsa)(q%m)"
+ "(protected openpgp-native%S)))";
+ else if (!strcmp (curve, "Curve25519"))
+ format = "(protected-private-key(ecc(curve %s)(flags djb-tweak)(q%m)"
+ "(protected openpgp-native%S)))";
+ else
+ format = "(protected-private-key(ecc(curve %s)(q%m)"
+ "(protected openpgp-native%S)))";
+
+ err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], transfer_key);
+ }
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err)
+ *r_key = s_skey;
+ return err;
+}
+
+
+/* Hash the passphrase and set the key. */
+static gpg_error_t
+hash_passphrase_and_set_key (const char *passphrase,
+ gcry_cipher_hd_t hd, int protect_algo,
+ int s2k_mode, int s2k_algo,
+ byte *s2k_salt, u32 s2k_count)
+{
+ gpg_error_t err;
+ unsigned char *key;
+ size_t keylen;
+
+ keylen = gcry_cipher_get_algo_keylen (protect_algo);
+ if (!keylen)
+ return gpg_error (GPG_ERR_INTERNAL);
+
+ key = xtrymalloc_secure (keylen);
+ if (!key)
+ return gpg_error_from_syserror ();
+
+ err = s2k_hash_passphrase (passphrase,
+ s2k_algo, s2k_mode, s2k_salt, s2k_count,
+ key, keylen);
+ if (!err)
+ err = gcry_cipher_setkey (hd, key, keylen);
+
+ xfree (key);
+ return err;
+}
+
+
+static u16
+checksum (const unsigned char *p, unsigned int n)
+{
+ u16 a;
+
+ for (a=0; n; n-- )
+ a += *p++;
+ return a;
+}
+
+
+/* Return the number of expected key parameters. */
+static void
+get_npkey_nskey (int pubkey_algo, size_t *npkey, size_t *nskey)
+{
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_RSA: *npkey = 2; *nskey = 6; break;
+ case GCRY_PK_ELG: *npkey = 3; *nskey = 4; break;
+ case GCRY_PK_ELG_E: *npkey = 3; *nskey = 4; break;
+ case GCRY_PK_DSA: *npkey = 4; *nskey = 5; break;
+ case GCRY_PK_ECC: *npkey = 1; *nskey = 2; break;
+ default: *npkey = 0; *nskey = 0; break;
+ }
+}
+
+
+/* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number.
+ On success R_NPKEY and R_NSKEY receive the number or parameters for
+ the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of
+ SKEY. */
+static int
+prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize,
+ int s2k_mode,
+ unsigned int *r_npkey, unsigned int *r_nskey,
+ unsigned int *r_skeylen)
+{
+ size_t npkey, nskey, skeylen;
+ int i;
+
+ /* Count the actual number of MPIs is in the array and set the
+ remainder to NULL for easier processing later on. */
+ for (skeylen = 0; skey[skeylen]; skeylen++)
+ ;
+ for (i=skeylen; i < skeysize; i++)
+ skey[i] = NULL;
+
+ /* Check some args. */
+ if (s2k_mode == 1001)
+ {
+ /* Stub key. */
+ log_info (_("secret key parts are not available\n"));
+ return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ }
+
+ if (gcry_pk_test_algo (pubkey_algo))
+ {
+ log_info (_("public key algorithm %d (%s) is not supported\n"),
+ pubkey_algo, gcry_pk_algo_name (pubkey_algo));
+ return gpg_error (GPG_ERR_PUBKEY_ALGO);
+ }
+
+ /* Get properties of the public key algorithm and do some
+ consistency checks. Note that we need at least NPKEY+1 elements
+ in the SKEY array. */
+ get_npkey_nskey (pubkey_algo, &npkey, &nskey);
+ if (!npkey || !nskey || npkey >= nskey)
+ return gpg_error (GPG_ERR_INTERNAL);
+ if (skeylen <= npkey)
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+ if (nskey+1 >= skeysize)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+
+ /* Check that the public key parameters are all available and not
+ encrypted. */
+ for (i=0; i < npkey; i++)
+ {
+ if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ if (r_npkey)
+ *r_npkey = npkey;
+ if (r_nskey)
+ *r_nskey = nskey;
+ if (r_skeylen)
+ *r_skeylen = skeylen;
+ return 0;
+}
+
+
+/* Note that this function modifies SKEY. SKEYSIZE is the allocated
+ size of the array including the NULL item; this is used for a
+ bounds check. On success a converted key is stored at R_KEY. */
+static int
+do_unprotect (const char *passphrase,
+ int pkt_version, int pubkey_algo, int is_protected,
+ const char *curve, gcry_mpi_t *skey, size_t skeysize,
+ int protect_algo, void *protect_iv, size_t protect_ivlen,
+ int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
+ u16 desired_csum, gcry_sexp_t *r_key)
+{
+ gpg_error_t err;
+ unsigned int npkey, nskey, skeylen;
+ gcry_cipher_hd_t cipher_hd = NULL;
+ u16 actual_csum;
+ size_t nbytes;
+ int i;
+ gcry_mpi_t tmpmpi;
+
+ *r_key = NULL;
+
+ err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode,
+ &npkey, &nskey, &skeylen);
+ if (err)
+ return err;
+
+ /* Check whether SKEY is at all protected. If it is not protected
+ merely verify the checksum. */
+ if (!is_protected)
+ {
+ actual_csum = 0;
+ for (i=npkey; i < nskey; i++)
+ {
+ if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+
+ if (gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
+ {
+ unsigned int nbits;
+ const unsigned char *buffer;
+ buffer = gcry_mpi_get_opaque (skey[i], &nbits);
+ nbytes = (nbits+7)/8;
+ actual_csum += checksum (buffer, nbytes);
+ }
+ else
+ {
+ unsigned char *buffer;
+
+ err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes,
+ skey[i]);
+ if (!err)
+ actual_csum += checksum (buffer, nbytes);
+ xfree (buffer);
+ }
+ if (err)
+ return err;
+ }
+
+ if (actual_csum != desired_csum)
+ return gpg_error (GPG_ERR_CHECKSUM);
+
+ goto do_convert;
+ }
+
+
+ if (gcry_cipher_test_algo (protect_algo))
+ {
+ /* The algorithm numbers are Libgcrypt numbers but fortunately
+ the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
+ numbers. */
+ log_info (_("protection algorithm %d (%s) is not supported\n"),
+ protect_algo, gnupg_cipher_algo_name (protect_algo));
+ return gpg_error (GPG_ERR_CIPHER_ALGO);
+ }
+
+ if (gcry_md_test_algo (s2k_algo))
+ {
+ log_info (_("protection hash algorithm %d (%s) is not supported\n"),
+ s2k_algo, gcry_md_algo_name (s2k_algo));
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ err = gcry_cipher_open (&cipher_hd, protect_algo,
+ GCRY_CIPHER_MODE_CFB,
+ (GCRY_CIPHER_SECURE
+ | (protect_algo >= 100 ?
+ 0 : GCRY_CIPHER_ENABLE_SYNC)));
+ if (err)
+ {
+ log_error ("failed to open cipher_algo %d: %s\n",
+ protect_algo, gpg_strerror (err));
+ return err;
+ }
+
+ err = hash_passphrase_and_set_key (passphrase, cipher_hd, protect_algo,
+ s2k_mode, s2k_algo, s2k_salt, s2k_count);
+ if (err)
+ {
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+
+ gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen);
+
+ actual_csum = 0;
+ if (pkt_version >= 4)
+ {
+ int ndata;
+ unsigned int ndatabits;
+ const unsigned char *p;
+ unsigned char *data;
+ u16 csum_pgp7 = 0;
+
+ if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE ))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ p = gcry_mpi_get_opaque (skey[npkey], &ndatabits);
+ ndata = (ndatabits+7)/8;
+
+ if (ndata > 1)
+ csum_pgp7 = buf16_to_u16 (p+ndata-2);
+ data = xtrymalloc_secure (ndata);
+ if (!data)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+ gcry_cipher_decrypt (cipher_hd, data, ndata, p, ndata);
+
+ p = data;
+ if (is_protected == 2)
+ {
+ /* This is the new SHA1 checksum method to detect tampering
+ with the key as used by the Klima/Rosa attack. */
+ desired_csum = 0;
+ actual_csum = 1; /* Default to bad checksum. */
+
+ if (ndata < 20)
+ log_error ("not enough bytes for SHA-1 checksum\n");
+ else
+ {
+ gcry_md_hd_t h;
+
+ if (gcry_md_open (&h, GCRY_MD_SHA1, 1))
+ BUG(); /* Algo not available. */
+ gcry_md_write (h, data, ndata - 20);
+ gcry_md_final (h);
+ if (!memcmp (gcry_md_read (h, GCRY_MD_SHA1), data+ndata-20, 20))
+ actual_csum = 0; /* Digest does match. */
+ gcry_md_close (h);
+ }
+ }
+ else
+ {
+ /* Old 16 bit checksum method. */
+ if (ndata < 2)
+ {
+ log_error ("not enough bytes for checksum\n");
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ }
+ else
+ {
+ desired_csum = buf16_to_u16 (data+ndata-2);
+ actual_csum = checksum (data, ndata-2);
+ if (desired_csum != actual_csum)
+ {
+ /* This is a PGP 7.0.0 workaround */
+ desired_csum = csum_pgp7; /* Take the encrypted one. */
+ }
+ }
+ }
+
+ /* Better check it here. Otherwise the gcry_mpi_scan would fail
+ because the length may have an arbitrary value. */
+ if (desired_csum == actual_csum)
+ {
+ for (i=npkey; i < nskey; i++ )
+ {
+ if (gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, p, ndata, &nbytes))
+ {
+ /* Checksum was okay, but not correctly decrypted. */
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ break;
+ }
+ gcry_mpi_release (skey[i]);
+ skey[i] = tmpmpi;
+ ndata -= nbytes;
+ p += nbytes;
+ }
+ skey[i] = NULL;
+ skeylen = i;
+ assert (skeylen <= skeysize);
+
+ /* Note: at this point NDATA should be 2 for a simple
+ checksum or 20 for the sha1 digest. */
+ }
+ xfree(data);
+ }
+ else /* Packet version <= 3. */
+ {
+ unsigned char *buffer;
+
+ for (i = npkey; i < nskey; i++)
+ {
+ const unsigned char *p;
+ size_t ndata;
+ unsigned int ndatabits;
+
+ if (!skey[i] || !gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ p = gcry_mpi_get_opaque (skey[i], &ndatabits);
+ ndata = (ndatabits+7)/8;
+
+ if (!(ndata >= 2) || !(ndata == (buf16_to_ushort (p) + 7)/8 + 2))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ buffer = xtrymalloc_secure (ndata);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+
+ gcry_cipher_sync (cipher_hd);
+ buffer[0] = p[0];
+ buffer[1] = p[1];
+ gcry_cipher_decrypt (cipher_hd, buffer+2, ndata-2, p+2, ndata-2);
+ actual_csum += checksum (buffer, ndata);
+ err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, buffer, ndata, &ndata);
+ xfree (buffer);
+ if (err)
+ {
+ /* Checksum was okay, but not correctly decrypted. */
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ break;
+ }
+ gcry_mpi_release (skey[i]);
+ skey[i] = tmpmpi;
+ }
+ }
+ gcry_cipher_close (cipher_hd);
+
+ /* Now let's see whether we have used the correct passphrase. */
+ if (actual_csum != desired_csum)
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+
+ do_convert:
+ if (nskey != skeylen)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ err = convert_secret_key (r_key, pubkey_algo, skey, curve);
+ if (err)
+ return err;
+
+ /* The checksum may fail, thus we also check the key itself. */
+ err = gcry_pk_testkey (*r_key);
+ if (err)
+ {
+ gcry_sexp_release (*r_key);
+ *r_key = NULL;
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+
+ return 0;
+}
+
+
+/* Callback function to try the unprotection from the passphrase query
+ code. */
+static gpg_error_t
+try_do_unprotect_cb (struct pin_entry_info_s *pi)
+{
+ gpg_error_t err;
+ struct try_do_unprotect_arg_s *arg = pi->check_cb_arg;
+
+ err = do_unprotect (pi->pin,
+ arg->is_v4? 4:3,
+ arg->pubkey_algo, arg->is_protected,
+ arg->curve,
+ arg->skey, arg->skeysize,
+ arg->protect_algo, arg->iv, arg->ivlen,
+ arg->s2k_mode, arg->s2k_algo,
+ arg->s2k_salt, arg->s2k_count,
+ arg->desired_csum, arg->r_key);
+ /* SKEY may be modified now, thus we need to re-compute SKEYIDX. */
+ for (arg->skeyidx = 0; (arg->skeyidx < arg->skeysize
+ && arg->skey[arg->skeyidx]); arg->skeyidx++)
+ ;
+ return err;
+}
+
+
+/* See convert_from_openpgp for the core of the description. This
+ function adds an optional PASSPHRASE argument and uses this to
+ silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be
+ NULL in this mode. */
+static gpg_error_t
+convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
+ unsigned char *grip, const char *prompt,
+ const char *cache_nonce, const char *passphrase,
+ unsigned char **r_key, char **r_passphrase)
+{
+ gpg_error_t err;
+ int unattended;
+ int from_native;
+ gcry_sexp_t top_list;
+ gcry_sexp_t list = NULL;
+ const char *value;
+ size_t valuelen;
+ char *string;
+ int idx;
+ int is_v4, is_protected;
+ int pubkey_algo;
+ int protect_algo = 0;
+ char iv[16];
+ int ivlen = 0;
+ int s2k_mode = 0;
+ int s2k_algo = 0;
+ byte s2k_salt[8];
+ u32 s2k_count = 0;
+ size_t npkey, nskey;
+ gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
+ char *curve = NULL;
+ u16 desired_csum;
+ int skeyidx = 0;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+ if (r_passphrase)
+ *r_passphrase = NULL;
+ unattended = !r_passphrase;
+ from_native = (!cache_nonce && passphrase && !r_passphrase);
+
+ top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
+ if (!top_list)
+ goto bad_seckey;
+
+ list = gcry_sexp_find_token (top_list, "version", 0);
+ if (!list)
+ goto bad_seckey;
+ value = gcry_sexp_nth_data (list, 1, &valuelen);
+ if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
+ goto bad_seckey;
+ is_v4 = (value[0] == '4');
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "protection", 0);
+ if (!list)
+ goto bad_seckey;
+ value = gcry_sexp_nth_data (list, 1, &valuelen);
+ if (!value)
+ goto bad_seckey;
+ if (valuelen == 4 && !memcmp (value, "sha1", 4))
+ is_protected = 2;
+ else if (valuelen == 3 && !memcmp (value, "sum", 3))
+ is_protected = 1;
+ else if (valuelen == 4 && !memcmp (value, "none", 4))
+ is_protected = 0;
+ else
+ goto bad_seckey;
+
+ if (is_protected)
+ {
+ string = gcry_sexp_nth_string (list, 2);
+ if (!string)
+ goto bad_seckey;
+ protect_algo = gcry_cipher_map_name (string);
+ xfree (string);
+
+ value = gcry_sexp_nth_data (list, 3, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof iv)
+ goto bad_seckey;
+ memcpy (iv, value, valuelen);
+ ivlen = valuelen;
+
+ string = gcry_sexp_nth_string (list, 4);
+ if (!string)
+ goto bad_seckey;
+ s2k_mode = strtol (string, NULL, 10);
+ xfree (string);
+
+ string = gcry_sexp_nth_string (list, 5);
+ if (!string)
+ goto bad_seckey;
+ s2k_algo = gcry_md_map_name (string);
+ xfree (string);
+
+ value = gcry_sexp_nth_data (list, 6, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof s2k_salt)
+ goto bad_seckey;
+ memcpy (s2k_salt, value, valuelen);
+
+ string = gcry_sexp_nth_string (list, 7);
+ if (!string)
+ goto bad_seckey;
+ s2k_count = strtoul (string, NULL, 10);
+ xfree (string);
+ }
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "algo", 0);
+ if (!list)
+ goto bad_seckey;
+ string = gcry_sexp_nth_string (list, 1);
+ if (!string)
+ goto bad_seckey;
+ pubkey_algo = gcry_pk_map_name (string);
+ xfree (string);
+
+ get_npkey_nskey (pubkey_algo, &npkey, &nskey);
+ if (!npkey || !nskey || npkey >= nskey)
+ goto bad_seckey;
+
+ if (npkey == 1) /* This is ECC */
+ {
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "curve", 0);
+ if (!list)
+ goto bad_seckey;
+ curve = gcry_sexp_nth_string (list, 1);
+ if (!curve)
+ goto bad_seckey;
+ }
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "skey", 0);
+ if (!list)
+ goto bad_seckey;
+ for (idx=0;;)
+ {
+ int is_enc;
+
+ value = gcry_sexp_nth_data (list, ++idx, &valuelen);
+ if (!value && skeyidx >= npkey)
+ break; /* Ready. */
+
+ /* Check for too many parameters. Note that depending on the
+ protection mode and version number we may see less than NSKEY
+ (but at least NPKEY+1) parameters. */
+ if (idx >= 2*nskey)
+ goto bad_seckey;
+ if (skeyidx >= DIM (skey)-1)
+ goto bad_seckey;
+
+ if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
+ goto bad_seckey;
+ is_enc = (value[0] == 'e');
+ value = gcry_sexp_nth_data (list, ++idx, &valuelen);
+ if (!value || !valuelen)
+ goto bad_seckey;
+ if (is_enc)
+ {
+ /* Encrypted parameters need to be stored as opaque. */
+ skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8);
+ if (!skey[skeyidx])
+ goto outofmem;
+ gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1);
+ }
+ else
+ {
+ if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
+ value, valuelen, NULL))
+ goto bad_seckey;
+ }
+ skeyidx++;
+ }
+ skey[skeyidx++] = NULL;
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "csum", 0);
+ if (list)
+ {
+ string = gcry_sexp_nth_string (list, 1);
+ if (!string)
+ goto bad_seckey;
+ desired_csum = strtoul (string, NULL, 10);
+ xfree (string);
+ }
+ else
+ desired_csum = 0;
+
+
+ gcry_sexp_release (list); list = NULL;
+ gcry_sexp_release (top_list); top_list = NULL;
+
+#if 0
+ log_debug ("XXX is_v4=%d\n", is_v4);
+ log_debug ("XXX pubkey_algo=%d\n", pubkey_algo);
+ log_debug ("XXX is_protected=%d\n", is_protected);
+ log_debug ("XXX protect_algo=%d\n", protect_algo);
+ log_printhex (iv, ivlen, "XXX iv");
+ log_debug ("XXX ivlen=%d\n", ivlen);
+ log_debug ("XXX s2k_mode=%d\n", s2k_mode);
+ log_debug ("XXX s2k_algo=%d\n", s2k_algo);
+ log_printhex (s2k_salt, sizeof s2k_salt, "XXX s2k_salt");
+ log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count);
+ log_debug ("XXX curve='%s'\n", curve);
+ for (idx=0; skey[idx]; idx++)
+ gcry_log_debugmpi (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_USER1)
+ ? "skey(e)" : "skey(_)", skey[idx]);
+#endif /*0*/
+
+ err = get_keygrip (pubkey_algo, curve, skey, grip);
+ if (err)
+ goto leave;
+
+ if (!dontcare_exist && !from_native && !agent_key_available (grip))
+ {
+ err = gpg_error (GPG_ERR_EEXIST);
+ goto leave;
+ }
+
+ if (unattended && !from_native)
+ {
+ err = prepare_unprotect (pubkey_algo, skey, DIM(skey), s2k_mode,
+ NULL, NULL, NULL);
+ if (err)
+ goto leave;
+
+ err = convert_transfer_key (&s_skey, pubkey_algo, skey, curve, s_pgp);
+ if (err)
+ goto leave;
+ }
+ else
+ {
+ struct pin_entry_info_s *pi;
+ struct try_do_unprotect_arg_s pi_arg;
+
+ pi = xtrycalloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi->min_digits = 0; /* We want a real passphrase. */
+ pi->max_digits = 16;
+ pi->max_tries = 3;
+ pi->check_cb = try_do_unprotect_cb;
+ pi->check_cb_arg = &pi_arg;
+ pi_arg.is_v4 = is_v4;
+ pi_arg.is_protected = is_protected;
+ pi_arg.pubkey_algo = pubkey_algo;
+ pi_arg.curve = curve;
+ pi_arg.protect_algo = protect_algo;
+ pi_arg.iv = iv;
+ pi_arg.ivlen = ivlen;
+ pi_arg.s2k_mode = s2k_mode;
+ pi_arg.s2k_algo = s2k_algo;
+ pi_arg.s2k_salt = s2k_salt;
+ pi_arg.s2k_count = s2k_count;
+ pi_arg.desired_csum = desired_csum;
+ pi_arg.skey = skey;
+ pi_arg.skeysize = DIM (skey);
+ pi_arg.skeyidx = skeyidx;
+ pi_arg.r_key = &s_skey;
+
+ err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ if (!is_protected)
+ {
+ err = try_do_unprotect_cb (pi);
+ if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ else if (cache_nonce)
+ {
+ char *cache_value;
+
+ cache_value = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
+ if (cache_value)
+ {
+ if (strlen (cache_value) < pi->max_length)
+ strcpy (pi->pin, cache_value);
+ xfree (cache_value);
+ }
+ if (*pi->pin)
+ err = try_do_unprotect_cb (pi);
+ }
+ else if (from_native)
+ {
+ if (strlen (passphrase) < pi->max_length)
+ strcpy (pi->pin, passphrase);
+ err = try_do_unprotect_cb (pi);
+ }
+ if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native)
+ err = agent_askpin (ctrl, prompt, NULL, NULL, pi, NULL, 0);
+ skeyidx = pi_arg.skeyidx;
+ if (!err && r_passphrase && is_protected)
+ {
+ *r_passphrase = xtrystrdup (pi->pin);
+ if (!*r_passphrase)
+ err = gpg_error_from_syserror ();
+ }
+ xfree (pi);
+ if (err)
+ goto leave;
+ }
+
+ /* Save some memory and get rid of the SKEY array now. */
+ for (idx=0; idx < skeyidx; idx++)
+ gcry_mpi_release (skey[idx]);
+ skeyidx = 0;
+
+ /* Note that the padding is not required - we use it only because
+ that function allows us to create the result in secure memory. */
+ err = make_canon_sexp_pad (s_skey, 1, r_key, NULL);
+
+ leave:
+ xfree (curve);
+ gcry_sexp_release (s_skey);
+ gcry_sexp_release (list);
+ gcry_sexp_release (top_list);
+ for (idx=0; idx < skeyidx; idx++)
+ gcry_mpi_release (skey[idx]);
+ if (err && r_passphrase)
+ {
+ xfree (*r_passphrase);
+ *r_passphrase = NULL;
+ }
+ return err;
+
+ bad_seckey:
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ goto leave;
+
+ outofmem:
+ err = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+
+}
+
+
+/* Convert an OpenPGP transfer key into our internal format. Before
+ asking for a passphrase we check whether the key already exists in
+ our key storage. S_PGP is the OpenPGP key in transfer format. If
+ CACHE_NONCE is given the passphrase will be looked up in the cache.
+ On success R_KEY will receive a canonical encoded S-expression with
+ the unprotected key in our internal format; the caller needs to
+ release that memory. The passphrase used to decrypt the OpenPGP
+ key will be returned at R_PASSPHRASE; the caller must release this
+ passphrase. If R_PASSPHRASE is NULL the unattended conversion mode
+ will be used which uses the openpgp-native protection format for
+ the key. The keygrip will be stored at the 20 byte buffer pointed
+ to by GRIP. On error NULL is stored at all return arguments. */
+gpg_error_t
+convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
+ unsigned char *grip, const char *prompt,
+ const char *cache_nonce,
+ unsigned char **r_key, char **r_passphrase)
+{
+ return convert_from_openpgp_main (ctrl, s_pgp, dontcare_exist, grip, prompt,
+ cache_nonce, NULL,
+ r_key, r_passphrase);
+}
+
+/* This function is called by agent_unprotect to re-protect an
+ openpgp-native protected private-key into the standard private-key
+ protection format. */
+gpg_error_t
+convert_from_openpgp_native (ctrl_t ctrl,
+ gcry_sexp_t s_pgp, const char *passphrase,
+ unsigned char **r_key)
+{
+ gpg_error_t err;
+ unsigned char grip[20];
+
+ if (!passphrase)
+ return gpg_error (GPG_ERR_INTERNAL);
+
+ err = convert_from_openpgp_main (ctrl, s_pgp, 0, grip, NULL,
+ NULL, passphrase,
+ r_key, NULL);
+
+ /* On success try to re-write the key. */
+ if (!err)
+ {
+ if (*passphrase)
+ {
+ unsigned char *protectedkey = NULL;
+ size_t protectedkeylen;
+
+ if (!agent_protect (*r_key, passphrase,
+ &protectedkey, &protectedkeylen,
+ ctrl->s2k_count, -1))
+ agent_write_private_key (grip, protectedkey, protectedkeylen,
+ 1, 0, NULL, NULL, NULL);
+ xfree (protectedkey);
+ }
+ else
+ {
+ /* Empty passphrase: write key without protection. */
+ agent_write_private_key (grip,
+ *r_key,
+ gcry_sexp_canon_len (*r_key, 0, NULL,NULL),
+ 1, 0, NULL, NULL, NULL);
+ }
+ }
+
+ return err;
+}
+
+
+/* Given an ARRAY of mpis with the key parameters, protect the secret
+ parameters in that array and replace them by one opaque encoded
+ mpi. NPKEY is the number of public key parameters and NSKEY is
+ the number of secret key parameters (including the public ones).
+ On success the array will have NPKEY+1 elements. */
+static gpg_error_t
+apply_protection (gcry_mpi_t *array, int npkey, int nskey,
+ const char *passphrase,
+ int protect_algo, void *protect_iv, size_t protect_ivlen,
+ int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count)
+{
+ gpg_error_t err;
+ int i, j;
+ gcry_cipher_hd_t cipherhd;
+ unsigned char *bufarr[10];
+ size_t narr[10];
+ unsigned int nbits[10];
+ int ndata;
+ unsigned char *p, *data;
+
+ assert (npkey < nskey);
+ assert (nskey < DIM (bufarr));
+
+ /* Collect only the secret key parameters into BUFARR et al and
+ compute the required size of the data buffer. */
+ ndata = 20; /* Space for the SHA-1 checksum. */
+ for (i = npkey, j = 0; i < nskey; i++, j++ )
+ {
+ err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]);
+ if (err)
+ {
+ for (i = 0; i < j; i++)
+ xfree (bufarr[i]);
+ return err;
+ }
+ nbits[j] = gcry_mpi_get_nbits (array[i]);
+ ndata += 2 + narr[j];
+ }
+
+ /* Allocate data buffer and stuff it with the secret key parameters. */
+ data = xtrymalloc_secure (ndata);
+ if (!data)
+ {
+ err = gpg_error_from_syserror ();
+ for (i = 0; i < (nskey-npkey); i++ )
+ xfree (bufarr[i]);
+ return err;
+ }
+ p = data;
+ for (i = 0; i < (nskey-npkey); i++ )
+ {
+ *p++ = nbits[i] >> 8 ;
+ *p++ = nbits[i];
+ memcpy (p, bufarr[i], narr[i]);
+ p += narr[i];
+ xfree (bufarr[i]);
+ bufarr[i] = NULL;
+ }
+ assert (p == data + ndata - 20);
+
+ /* Append a hash of the secret key parameters. */
+ gcry_md_hash_buffer (GCRY_MD_SHA1, p, data, ndata - 20);
+
+ /* Encrypt it. */
+ err = gcry_cipher_open (&cipherhd, protect_algo,
+ GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
+ if (!err)
+ err = hash_passphrase_and_set_key (passphrase, cipherhd, protect_algo,
+ s2k_mode, s2k_algo, s2k_salt, s2k_count);
+ if (!err)
+ err = gcry_cipher_setiv (cipherhd, protect_iv, protect_ivlen);
+ if (!err)
+ err = gcry_cipher_encrypt (cipherhd, data, ndata, NULL, 0);
+ gcry_cipher_close (cipherhd);
+ if (err)
+ {
+ xfree (data);
+ return err;
+ }
+
+ /* Replace the secret key parameters in the array by one opaque value. */
+ for (i = npkey; i < nskey; i++ )
+ {
+ gcry_mpi_release (array[i]);
+ array[i] = NULL;
+ }
+ array[npkey] = gcry_mpi_set_opaque (NULL, data, ndata*8);
+ return 0;
+}
+
+
+/*
+ * Examining S_KEY in S-Expression and extract data.
+ * When REQ_PRIVATE_KEY_DATA == 1, S_KEY's CAR should be 'private-key',
+ * but it also allows shadowed or protected versions.
+ * On success, it returns 0, otherwise error number.
+ * R_ALGONAME is static string which is no need to free by caller.
+ * R_NPKEY is pointer to number of public key data.
+ * R_NSKEY is pointer to number of private key data.
+ * R_ELEMS is static string which is no need to free by caller.
+ * ARRAY contains public and private key data.
+ * ARRAYSIZE is the allocated size of the array for cross-checking.
+ * R_CURVE is pointer to S-Expression of the curve (can be NULL).
+ * R_FLAGS is pointer to S-Expression of the flags (can be NULL).
+ */
+gpg_error_t
+extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
+ const char **r_algoname, int *r_npkey, int *r_nskey,
+ const char **r_elems,
+ gcry_mpi_t *array, int arraysize,
+ gcry_sexp_t *r_curve, gcry_sexp_t *r_flags)
+{
+ gpg_error_t err;
+ gcry_sexp_t list, l2;
+ char *name;
+ const char *algoname, *format;
+ int npkey, nskey;
+ gcry_sexp_t curve = NULL;
+ gcry_sexp_t flags = NULL;
+
+ *r_curve = NULL;
+ *r_flags = NULL;
+
+ if (!req_private_key_data)
+ {
+ list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_key, "private-key", 0 );
+ }
+ else
+ list = gcry_sexp_find_token (s_key, "private-key", 0);
+
+ if (!list)
+ {
+ log_error ("invalid private key format\n");
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ l2 = gcry_sexp_cadr (list);
+ gcry_sexp_release (list);
+ list = l2;
+ name = gcry_sexp_nth_string (list, 0);
+ if (!name)
+ {
+ gcry_sexp_release (list);
+ return gpg_error (GPG_ERR_INV_OBJ); /* Invalid structure of object. */
+ }
+
+ if (arraysize < 7)
+ BUG ();
+
+ /* Map NAME to a name as used by Libgcrypt. We do not use the
+ Libgcrypt function here because we need a lowercase name and
+ require special treatment for some algorithms. */
+ strlwr (name);
+ if (!strcmp (name, "rsa"))
+ {
+ algoname = "rsa";
+ format = "ned?p?q?u?";
+ npkey = 2;
+ nskey = 6;
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, array+2, array+3,
+ array+4, array+5, NULL);
+ }
+ else if (!strcmp (name, "elg"))
+ {
+ algoname = "elg";
+ format = "pgyx?";
+ npkey = 3;
+ nskey = 4;
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, array+2, array+3,
+ NULL);
+ }
+ else if (!strcmp (name, "dsa"))
+ {
+ algoname = "dsa";
+ format = "pqgyx?";
+ npkey = 4;
+ nskey = 5;
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, array+2, array+3,
+ array+4, NULL);
+ }
+ else if (!strcmp (name, "ecc") || !strcmp (name, "ecdsa"))
+ {
+ algoname = "ecc";
+ format = "qd?";
+ npkey = 1;
+ nskey = 2;
+ curve = gcry_sexp_find_token (list, "curve", 0);
+ flags = gcry_sexp_find_token (list, "flags", 0);
+ err = gcry_sexp_extract_param (list, NULL, format,
+ array+0, array+1, NULL);
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ }
+ xfree (name);
+ gcry_sexp_release (list);
+ if (err)
+ {
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+ return err;
+ }
+ else
+ {
+ *r_algoname = algoname;
+ if (r_elems)
+ *r_elems = format;
+ *r_npkey = npkey;
+ if (r_nskey)
+ *r_nskey = nskey;
+ *r_curve = curve;
+ *r_flags = flags;
+
+ return 0;
+ }
+}
+
+/* Convert our key S_KEY into an OpenPGP key transfer format. On
+ success a canonical encoded S-expression is stored at R_TRANSFERKEY
+ and its length at R_TRANSFERKEYLEN; this S-expression is also
+ padded to a multiple of 64 bits. */
+gpg_error_t
+convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
+ unsigned char **r_transferkey, size_t *r_transferkeylen)
+{
+ gpg_error_t err;
+ const char *algoname;
+ int npkey, nskey;
+ gcry_mpi_t array[10];
+ gcry_sexp_t curve = NULL;
+ gcry_sexp_t flags = NULL;
+ char protect_iv[16];
+ char salt[8];
+ unsigned long s2k_count;
+ int i, j;
+
+ (void)ctrl;
+
+ *r_transferkey = NULL;
+
+ for (i=0; i < DIM (array); i++)
+ array[i] = NULL;
+
+ err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL,
+ array, DIM (array), &curve, &flags);
+ if (err)
+ return err;
+
+ gcry_create_nonce (protect_iv, sizeof protect_iv);
+ gcry_create_nonce (salt, sizeof salt);
+ /* We need to use the encoded S2k count. It is not possible to
+ encode it after it has been used because the encoding procedure
+ may round the value up. */
+ s2k_count = get_standard_s2k_count_rfc4880 ();
+ err = apply_protection (array, npkey, nskey, passphrase,
+ GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
+ 3, GCRY_MD_SHA1, salt, s2k_count);
+ /* Turn it into the transfer key S-expression. Note that we always
+ return a protected key. */
+ if (!err)
+ {
+ char countbuf[35];
+ membuf_t mbuf;
+ void *format_args[10+2];
+ gcry_sexp_t tmpkey;
+ gcry_sexp_t tmpsexp = NULL;
+
+ snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
+
+ init_membuf (&mbuf, 50);
+ put_membuf_str (&mbuf, "(skey");
+ for (i=j=0; i < npkey; i++)
+ {
+ put_membuf_str (&mbuf, " _ %m");
+ format_args[j++] = array + i;
+ }
+ put_membuf_str (&mbuf, " e %m");
+ format_args[j++] = array + npkey;
+ put_membuf_str (&mbuf, ")\n");
+ put_membuf (&mbuf, "", 1);
+
+ tmpkey = NULL;
+ {
+ char *format = get_membuf (&mbuf, NULL);
+ if (!format)
+ err = gpg_error_from_syserror ();
+ else
+ err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args);
+ xfree (format);
+ }
+ if (!err)
+ err = gcry_sexp_build (&tmpsexp, NULL,
+ "(openpgp-private-key\n"
+ " (version 1:4)\n"
+ " (algo %s)\n"
+ " %S%S\n"
+ " (protection sha1 aes %b 1:3 sha1 %b %s))\n",
+ algoname,
+ curve,
+ tmpkey,
+ (int)sizeof protect_iv, protect_iv,
+ (int)sizeof salt, salt,
+ countbuf);
+ gcry_sexp_release (tmpkey);
+ if (!err)
+ err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen);
+ gcry_sexp_release (tmpsexp);
+ }
+
+ for (i=0; i < DIM (array); i++)
+ gcry_mpi_release (array[i]);
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+
+ return err;
+}
diff --git a/agent/cvt-openpgp.h b/agent/cvt-openpgp.h
new file mode 100644
index 0000000..23092f6
--- /dev/null
+++ b/agent/cvt-openpgp.h
@@ -0,0 +1,37 @@
+/* cvt-openpgp.h - Convert an OpenPGP key to our internal format.
+ * Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+#ifndef GNUPG_AGENT_CVT_OPENPGP_H
+#define GNUPG_AGENT_CVT_OPENPGP_H
+
+gpg_error_t convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
+ int dontcare_exist,
+ unsigned char *grip, const char *prompt,
+ const char *cache_nonce,
+ unsigned char **r_key, char **r_passphrase);
+gpg_error_t convert_from_openpgp_native (ctrl_t ctrl,
+ gcry_sexp_t s_pgp,
+ const char *passphrase,
+ unsigned char **r_key);
+
+gpg_error_t convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key,
+ const char *passphrase,
+ unsigned char **r_transferkey,
+ size_t *r_transferkeylen);
+
+#endif /*GNUPG_AGENT_CVT_OPENPGP_H*/
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
new file mode 100644
index 0000000..b9ea7e7
--- /dev/null
+++ b/agent/divert-scd.c
@@ -0,0 +1,734 @@
+/* divert-scd.c - divert operations to the scdaemon
+ * Copyright (C) 2002, 2003, 2009 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+#include "../common/i18n.h"
+#include "../common/sexp-parse.h"
+
+/* Replace all linefeeds in STRING by "%0A" and return a new malloced
+ * string. May return NULL on memory error. */
+static char *
+linefeed_to_percent0A (const char *string)
+{
+ const char *s;
+ size_t n;
+ char *buf, *p;
+
+ for (n=0, s=string; *s; s++)
+ if (*s == '\n')
+ n += 3;
+ else
+ n++;
+ p = buf = xtrymalloc (n+1);
+ if (!buf)
+ return NULL;
+ for (s=string; *s; s++)
+ if (*s == '\n')
+ {
+ memcpy (p, "%0A", 3);
+ p += 3;
+ }
+ else
+ *p++ = *s;
+ *p = 0;
+ return buf;
+}
+
+
+/* Ask for the card using SHADOW_INFO. If GRIP is not NULL, the
+ * function also tries to find additional information from the shadow
+ * key file. */
+static int
+ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info,
+ const unsigned char *grip, char **r_kid)
+{
+ int rc, i;
+ char *serialno;
+ int no_card = 0;
+ char *desc;
+ char *want_sn, *want_kid, *want_sn_disp;
+ int got_sn_disp_from_meta = 0;
+ int len;
+ char *comment = NULL;
+
+ *r_kid = NULL;
+
+ rc = parse_shadow_info (shadow_info, &want_sn, &want_kid, NULL);
+ if (rc)
+ return rc;
+ want_sn_disp = xtrystrdup (want_sn);
+ if (!want_sn_disp)
+ {
+ rc = gpg_error_from_syserror ();
+ xfree (want_sn);
+ xfree (want_kid);
+ xfree (comment);
+ return rc;
+ }
+
+ if (grip)
+ {
+ nvc_t keymeta;
+ const char *s;
+ size_t snlen;
+ nve_t item;
+ char **tokenfields = NULL;
+
+ rc = agent_keymeta_from_file (ctrl, grip, &keymeta);
+ if (!rc)
+ {
+ snlen = strlen (want_sn);
+ s = NULL;
+ for (item = nvc_lookup (keymeta, "Token:");
+ item;
+ item = nve_next_value (item, "Token:"))
+ if ((s = nve_value (item)) && !strncmp (s, want_sn, snlen))
+ break;
+ if (s && (tokenfields = strtokenize (s, " \t\n")))
+ {
+ if (tokenfields[0] && tokenfields[1] && tokenfields[2]
+ && tokenfields[3] && strlen (tokenfields[3]) > 1)
+ {
+ xfree (want_sn_disp);
+ want_sn_disp = percent_plus_unescape (tokenfields[3], 0xff);
+ if (!want_sn_disp)
+ {
+ rc = gpg_error_from_syserror ();
+ xfree (tokenfields);
+ nvc_release (keymeta);
+ xfree (want_sn);
+ xfree (want_kid);
+ xfree (comment);
+ return rc;
+ }
+ got_sn_disp_from_meta = 1;
+ }
+
+ xfree (tokenfields);
+ }
+
+ if ((s = nvc_get_string (keymeta, "Label:")))
+ comment = linefeed_to_percent0A (s);
+
+ nvc_release (keymeta);
+ }
+ }
+
+ len = strlen (want_sn_disp);
+ if (got_sn_disp_from_meta)
+ ; /* We got the the display S/N from the key file. */
+ else if (len == 32 && !strncmp (want_sn_disp, "D27600012401", 12))
+ {
+ /* This is an OpenPGP card - reformat */
+ memmove (want_sn_disp, want_sn_disp+16, 4);
+ want_sn_disp[4] = ' ';
+ memmove (want_sn_disp+5, want_sn_disp+20, 8);
+ want_sn_disp[13] = 0;
+ }
+ else if (len == 20 && want_sn_disp[19] == '0')
+ {
+ /* We assume that a 20 byte serial number is a standard one
+ * which has the property to have a zero in the last nibble (Due
+ * to BCD representation). We don't display this '0' because it
+ * may confuse the user. */
+ want_sn_disp[19] = 0;
+ }
+
+ for (;;)
+ {
+ rc = agent_card_serialno (ctrl, &serialno, want_sn);
+ if (!rc)
+ {
+ log_info ("detected card with S/N %s\n", serialno);
+ i = strcmp (serialno, want_sn);
+ xfree (serialno);
+ serialno = NULL;
+ if (!i)
+ {
+ xfree (want_sn_disp);
+ xfree (want_sn);
+ xfree (comment);
+ *r_kid = want_kid;
+ return 0; /* yes, we have the correct card */
+ }
+ }
+ else if (gpg_err_code (rc) == GPG_ERR_ENODEV)
+ {
+ log_info ("no device present\n");
+ rc = 0;
+ no_card = 1;
+ }
+ else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT)
+ {
+ log_info ("no card present\n");
+ rc = 0;
+ no_card = 2;
+ }
+ else
+ {
+ log_error ("error accessing card: %s\n", gpg_strerror (rc));
+ }
+
+ if (!rc)
+ {
+ if (asprintf (&desc,
+ "%s:%%0A%%0A"
+ " %s%%0A"
+ " %s",
+ no_card
+ ? L_("Please insert the card with serial number")
+ : L_("Please remove the current card and "
+ "insert the one with serial number"),
+ want_sn_disp, comment? comment:"") < 0)
+ {
+ rc = out_of_core ();
+ }
+ else
+ {
+ rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
+ gpg_err_code (rc) == GPG_ERR_NO_PIN_ENTRY)
+ rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+
+ xfree (desc);
+ }
+ }
+ if (rc)
+ {
+ xfree (want_sn_disp);
+ xfree (want_sn);
+ xfree (want_kid);
+ xfree (comment);
+ return rc;
+ }
+ }
+}
+
+
+/* Put the DIGEST into an DER encoded container and return it in R_VAL. */
+static int
+encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo,
+ unsigned char **r_val, size_t *r_len)
+{
+ unsigned char *frame;
+ unsigned char asn[100];
+ size_t asnlen;
+
+ *r_val = NULL;
+ *r_len = 0;
+
+ asnlen = DIM(asn);
+ if (!algo || gcry_md_test_algo (algo))
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen))
+ {
+ log_error ("no object identifier for algo %d\n", algo);
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+
+ frame = xtrymalloc (asnlen + digestlen);
+ if (!frame)
+ return out_of_core ();
+ memcpy (frame, asn, asnlen);
+ memcpy (frame+asnlen, digest, digestlen);
+ if (DBG_CRYPTO)
+ log_printhex (frame, asnlen+digestlen, "encoded hash:");
+
+ *r_val = frame;
+ *r_len = asnlen+digestlen;
+ return 0;
+}
+
+
+/* Return true if STRING ends in "%0A". */
+static int
+has_percent0A_suffix (const char *string)
+{
+ size_t n;
+
+ return (string
+ && (n = strlen (string)) >= 3
+ && !strcmp (string + n - 3, "%0A"));
+}
+
+
+/* Callback used to ask for the PIN which should be set into BUF. The
+ buf has been allocated by the caller and is of size MAXBUF which
+ includes the terminating null. The function should return an UTF-8
+ string with the passphrase, the buffer may optionally be padded
+ with arbitrary characters.
+
+ If DESC_TEXT is not NULL it can be used as further informtion shown
+ atop of the INFO message.
+
+ INFO gets displayed as part of a generic string. However if the
+ first character of INFO is a vertical bar all up to the next
+ verical bar are considered flags and only everything after the
+ second vertical bar gets displayed as the full prompt.
+
+ Flags:
+
+ 'N' = New PIN, this requests a second prompt to repeat the
+ PIN. If the PIN is not correctly repeated it starts from
+ all over.
+ 'A' = The PIN is an Admin PIN, SO-PIN or alike.
+ 'P' = The PIN is a PUK (Personal Unblocking Key).
+ 'R' = The PIN is a Reset Code.
+
+ Example:
+
+ "|AN|Please enter the new security officer's PIN"
+
+ The text "Please ..." will get displayed and the flags 'A' and 'N'
+ are considered.
+ */
+static int
+getpin_cb (void *opaque, const char *desc_text, const char *info,
+ char *buf, size_t maxbuf)
+{
+ struct pin_entry_info_s *pi;
+ int rc;
+ ctrl_t ctrl = opaque;
+ const char *ends, *s;
+ int any_flags = 0;
+ int newpin = 0;
+ int resetcode = 0;
+ int is_puk = 0;
+ const char *again_text = NULL;
+ const char *prompt = "PIN";
+
+ if (buf && maxbuf < 2)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Parse the flags. */
+ if (info && *info =='|' && (ends=strchr (info+1, '|')))
+ {
+ for (s=info+1; s < ends; s++)
+ {
+ if (*s == 'A')
+ prompt = L_("Admin PIN");
+ else if (*s == 'P')
+ {
+ /* TRANSLATORS: A PUK is the Personal Unblocking Code
+ used to unblock a PIN. */
+ prompt = L_("PUK");
+ is_puk = 1;
+ }
+ else if (*s == 'N')
+ newpin = 1;
+ else if (*s == 'R')
+ {
+ prompt = L_("Reset Code");
+ resetcode = 1;
+ }
+ }
+ info = ends+1;
+ any_flags = 1;
+ }
+ else if (info && *info == '|')
+ log_debug ("pin_cb called without proper PIN info hack\n");
+
+ /* If BUF has been passed as NULL, we are in pinpad mode: The
+ callback opens the popup and immediately returns. */
+ if (!buf)
+ {
+ if (maxbuf == 0) /* Close the pinentry. */
+ {
+ agent_popup_message_stop (ctrl);
+ rc = 0;
+ }
+ else if (maxbuf == 1) /* Open the pinentry. */
+ {
+ if (info)
+ {
+ char *desc;
+ const char *desc2;
+
+ if (!strcmp (info, "--ack"))
+ {
+ desc2 = L_("Push ACK button on card/token.");
+
+ if (desc_text)
+ {
+ desc = strconcat (desc_text,
+ has_percent0A_suffix (desc_text)
+ ? "%0A" : "%0A%0A",
+ desc2, NULL);
+ desc2 = NULL;
+ }
+ else
+ desc = NULL;
+ }
+ else
+ {
+ desc2 = NULL;
+
+ if (desc_text)
+ desc = strconcat (desc_text,
+ has_percent0A_suffix (desc_text)
+ ? "%0A" : "%0A%0A",
+ info, "%0A%0A",
+ L_("Use the reader's pinpad for input."),
+ NULL);
+ else
+ desc = strconcat (info, "%0A%0A",
+ L_("Use the reader's pinpad for input."),
+ NULL);
+ }
+
+ if (!desc2 && !desc)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ rc = agent_popup_message_start (ctrl,
+ desc2? desc2:desc, NULL);
+ xfree (desc);
+ }
+ }
+ else
+ rc = agent_popup_message_start (ctrl, desc_text, NULL);
+ }
+ else
+ rc = gpg_error (GPG_ERR_INV_VALUE);
+ return rc;
+ }
+
+ /* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole
+ mess because we should call the card's verify function from the
+ pinentry check pin CB. */
+ again:
+ pi = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi->max_length = maxbuf-1;
+ pi->min_digits = 0; /* we want a real passphrase */
+ pi->max_digits = 16;
+ pi->max_tries = 3;
+
+ if (any_flags)
+ {
+ {
+ char *desc2;
+
+ if (desc_text)
+ desc2 = strconcat (desc_text,
+ has_percent0A_suffix (desc_text)
+ ? "%0A" : "%0A%0A",
+ info, NULL);
+ else
+ desc2 = NULL;
+ rc = agent_askpin (ctrl, desc2? desc2 : info,
+ prompt, again_text, pi, NULL, 0);
+ xfree (desc2);
+ }
+ again_text = NULL;
+ if (!rc && newpin)
+ {
+ struct pin_entry_info_s *pi2;
+ pi2 = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10);
+ if (!pi2)
+ {
+ rc = gpg_error_from_syserror ();
+ xfree (pi);
+ return rc;
+ }
+ pi2->max_length = maxbuf-1;
+ pi2->min_digits = 0;
+ pi2->max_digits = 16;
+ pi2->max_tries = 1;
+ rc = agent_askpin (ctrl,
+ (resetcode?
+ L_("Repeat this Reset Code"):
+ is_puk?
+ L_("Repeat this PUK"):
+ L_("Repeat this PIN")),
+ prompt, NULL, pi2, NULL, 0);
+ if (!rc && strcmp (pi->pin, pi2->pin))
+ {
+ again_text = (resetcode?
+ L_("Reset Code not correctly repeated; try again"):
+ is_puk?
+ L_("PUK not correctly repeated; try again"):
+ L_("PIN not correctly repeated; try again"));
+ xfree (pi2);
+ xfree (pi);
+ goto again;
+ }
+ xfree (pi2);
+ }
+ }
+ else
+ {
+ char *desc, *desc2;
+
+ if ( asprintf (&desc,
+ L_("Please enter the PIN%s%s%s to unlock the card"),
+ info? " (":"",
+ info? info:"",
+ info? ")":"") < 0)
+ desc = NULL;
+ if (desc_text)
+ desc2 = strconcat (desc_text,
+ has_percent0A_suffix (desc_text)
+ ? "%0A" : "%0A%0A",
+ desc, NULL);
+ else
+ desc2 = NULL;
+ rc = agent_askpin (ctrl, desc2? desc2 : desc? desc : info,
+ prompt, NULL, pi, NULL, 0);
+ xfree (desc2);
+ xfree (desc);
+ }
+
+ if (!rc)
+ {
+ strncpy (buf, pi->pin, maxbuf-1);
+ buf[maxbuf-1] = 0;
+ }
+ xfree (pi);
+ return rc;
+}
+
+
+
+/* This function is used when a sign operation has been diverted to a
+ * smartcard. DESC_TEXT is the original text for a prompt has send by
+ * gpg to gpg-agent.
+ *
+ * FIXME: Explain the other args. */
+int
+divert_pksign (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *digest, size_t digestlen, int algo,
+ const unsigned char *grip,
+ const unsigned char *shadow_info, unsigned char **r_sig,
+ size_t *r_siglen)
+{
+ int rc;
+ char *kid;
+ size_t siglen;
+ unsigned char *sigval = NULL;
+
+ (void)desc_text;
+
+ rc = ask_for_card (ctrl, shadow_info, grip, &kid);
+ if (rc)
+ return rc;
+
+ /* For OpenPGP cards we better use the keygrip as key reference.
+ * This has the advantage that app-openpgp can check that the stored
+ * key matches our expectation. This is important in case new keys
+ * have been created on the same card but the sub file has not been
+ * updated. In that case we would get a error from our final
+ * signature checking code or, if the pubkey algo is different,
+ * weird errors from the card (Conditions of use not satisfied). */
+ if (kid && grip && !strncmp (kid, "OPENPGP.", 8))
+ {
+ xfree (kid);
+ kid = bin2hex (grip, KEYGRIP_LEN, NULL);
+ if (!kid)
+ return gpg_error_from_syserror ();
+ }
+
+
+ if (algo == MD_USER_TLS_MD5SHA1)
+ {
+ int save = ctrl->use_auth_call;
+ ctrl->use_auth_call = 1;
+ rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, NULL,
+ algo, digest, digestlen, &sigval, &siglen);
+ ctrl->use_auth_call = save;
+ }
+ else
+ {
+ unsigned char *data;
+ size_t ndata;
+
+ rc = encode_md_for_card (digest, digestlen, algo, &data, &ndata);
+ if (!rc)
+ {
+ rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, NULL,
+ algo, data, ndata, &sigval, &siglen);
+ xfree (data);
+ }
+ }
+
+ if (!rc)
+ {
+ *r_sig = sigval;
+ *r_siglen = siglen;
+ }
+
+ xfree (kid);
+
+ return rc;
+}
+
+
+/* Decrypt the value given asn an S-expression in CIPHER using the
+ key identified by SHADOW_INFO and return the plaintext in an
+ allocated buffer in R_BUF. The padding information is stored at
+ R_PADDING with -1 for not known. */
+int
+divert_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *cipher,
+ const unsigned char *grip,
+ const unsigned char *shadow_info,
+ char **r_buf, size_t *r_len, int *r_padding)
+{
+ int rc;
+ char *kid;
+ const unsigned char *s;
+ size_t n;
+ int depth;
+ const unsigned char *ciphertext;
+ size_t ciphertextlen;
+ char *plaintext;
+ size_t plaintextlen;
+
+ (void)desc_text;
+
+ *r_padding = -1;
+ s = cipher;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "enc-val"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ /* First check whether we have a flags parameter and skip it. */
+ if (smatch (&s, n, "flags"))
+ {
+ depth = 1;
+ if (sskip (&s, &depth) || depth)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ }
+
+ if (smatch (&s, n, "rsa"))
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "a"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ n = snext (&s);
+ }
+ else if (smatch (&s, n, "ecdh"))
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "s"))
+ {
+ n = snext (&s);
+ s += n;
+ if (*s++ != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (*s++ != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ }
+ if (!smatch (&s, n, "e"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ n = snext (&s);
+ }
+ else
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+ if (!n)
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ ciphertext = s;
+ ciphertextlen = n;
+
+ rc = ask_for_card (ctrl, shadow_info, grip, &kid);
+ if (rc)
+ return rc;
+
+ /* For OpenPGP cards we better use the keygrip as key reference.
+ * This has the advantage that app-openpgp can check that the stored
+ * key matches our expectation. This is important in case new keys
+ * have been created on the same card but the sub file has not been
+ * updated. In that case we would get a error from our final
+ * signature checking code or, if the pubkey algo is different,
+ * weird errors from the card (Conditions of use not satisfied). */
+ if (kid && grip && !strncmp (kid, "OPENPGP.", 8))
+ {
+ xfree (kid);
+ kid = bin2hex (grip, KEYGRIP_LEN, NULL);
+ if (!kid)
+ return gpg_error_from_syserror ();
+ }
+
+ rc = agent_card_pkdecrypt (ctrl, kid, getpin_cb, ctrl, NULL,
+ ciphertext, ciphertextlen,
+ &plaintext, &plaintextlen, r_padding);
+ if (!rc)
+ {
+ *r_buf = plaintext;
+ *r_len = plaintextlen;
+ }
+ xfree (kid);
+ return rc;
+}
+
+int
+divert_writekey (ctrl_t ctrl, int force, const char *serialno,
+ const char *id, const char *keydata, size_t keydatalen)
+{
+ return agent_card_writekey (ctrl, force, serialno, id, keydata, keydatalen,
+ getpin_cb, ctrl);
+}
+
+int
+divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context)
+{
+ return agent_card_scd (ctrl, cmdline, getpin_cb, ctrl, assuan_context);
+}
diff --git a/agent/findkey.c b/agent/findkey.c
new file mode 100644
index 0000000..dadcc3c
--- /dev/null
+++ b/agent/findkey.c
@@ -0,0 +1,1827 @@
+/* findkey.c - Locate the secret key
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
+ * 2010, 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2014 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <npth.h> /* (we use pth_sleep) */
+
+#include "agent.h"
+#include "../common/i18n.h"
+#include "../common/ssh-utils.h"
+#include "../common/name-value.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+/* Helper to pass data to the check callback of the unprotect function. */
+struct try_unprotect_arg_s
+{
+ ctrl_t ctrl;
+ const unsigned char *protected_key;
+ unsigned char *unprotected_key;
+ int change_required; /* Set by the callback to indicate that the
+ user should change the passphrase. */
+};
+
+
+/* Return the file name for the 20 byte keygrip GRIP. Return NULL on
+ * error. */
+static char *
+fname_from_keygrip (const unsigned char *grip)
+{
+ char hexgrip[40+4+1];
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+
+ return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+ hexgrip, NULL);
+}
+
+
+/* Note: Ownership of FNAME and FP are moved to this function.
+ * OLD_FORMAT is true if the file exists but is still in the
+ * non-extended mode format. If MAYBE_UPDATE is set the function
+ * assumes that the file exists but writes it only if it figures that
+ * an update is required. */
+static gpg_error_t
+write_extended_private_key (int maybe_update,
+ char *fname, estream_t fp,
+ int old_format, int newkey,
+ const void *buf, size_t len, time_t timestamp,
+ const char *serialno, const char *keyref,
+ const char *dispserialno)
+{
+ gpg_error_t err;
+ nvc_t pk = NULL;
+ gcry_sexp_t key = NULL;
+ int remove = 0;
+ char *token0 = NULL;
+ char *token = NULL;
+ char *dispserialno_buffer = NULL;
+ char **tokenfields = NULL;
+
+ if (old_format || newkey)
+ {
+ /* We must create a new NVC if the key is still in the old
+ * format and of course if it is a new key. */
+ pk = nvc_new_private_key ();
+ if (!pk)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ maybe_update = 0; /* Always write. */
+ }
+ else
+ { /* Parse the existing NVC. */
+ int lineno = 0;
+
+ err = nvc_parse_private_key (&pk, &lineno, fp);
+ if (err)
+ {
+ log_error ("error parsing '%s' line %d: %s\n",
+ fname, lineno, gpg_strerror (err));
+ goto leave;
+ }
+ }
+ es_clearerr (fp);
+
+ err = gcry_sexp_sscan (&key, NULL, buf, len);
+ if (err)
+ goto leave;
+
+ err = nvc_set_private_key (pk, key);
+ if (err)
+ goto leave;
+
+ /* If a timestamp has been supplied and the key is new write a
+ * creation timestamp. Note that we can't add this item if we are
+ * still in the old format. We also add an extra check that there
+ * is no Created item yet. */
+ if (timestamp && newkey && !nvc_lookup (pk, "Created:"))
+ {
+ gnupg_isotime_t timebuf;
+
+ epoch2isotime (timebuf, timestamp);
+ err = nvc_add (pk, "Created:", timebuf);
+ if (err)
+ goto leave;
+ }
+
+ /* If requested write a Token line. */
+ if (serialno && keyref)
+ {
+ nve_t item;
+ const char *s;
+ size_t token0len;
+
+ if (dispserialno)
+ {
+ /* Escape the DISPSERIALNO. */
+ dispserialno_buffer = percent_plus_escape (dispserialno);
+ if (!dispserialno_buffer)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ dispserialno = dispserialno_buffer;
+ }
+
+ token0 = strconcat (serialno, " ", keyref, NULL);
+ if (token0)
+ token = strconcat (token0, " - ", dispserialno? dispserialno:"-", NULL);
+ if (!token0 || !token)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ token0len = strlen (token0);
+ for (item = nvc_lookup (pk, "Token:");
+ item;
+ item = nve_next_value (item, "Token:"))
+ if ((s = nve_value (item)) && !strncmp (s, token0, token0len))
+ break;
+ if (!item)
+ {
+ /* No token or no token with that value exists. Add a new
+ * one so that keys which have been stored on several cards
+ * are well supported. */
+ err = nvc_add (pk, "Token:", token);
+ if (err)
+ goto leave;
+ maybe_update = 0; /* Force write. */
+ }
+ else
+ {
+ /* Token exists: Update the display s/n. It may have
+ * changed due to changes in a newer software version. */
+ if (maybe_update && s && (tokenfields = strtokenize (s, " \t\n"))
+ && tokenfields[0] && tokenfields[1] && tokenfields[2]
+ && tokenfields[3]
+ && !strcmp (tokenfields[3], dispserialno))
+ ; /* No need to update Token entry. */
+ else
+ {
+ err = nve_set (item, token);
+ if (err)
+ goto leave;
+ maybe_update = 0; /* Force write. */
+ }
+ }
+ }
+
+ err = es_fseek (fp, 0, SEEK_SET);
+ if (err)
+ goto leave;
+
+ if (!maybe_update)
+ {
+ err = nvc_write (pk, fp);
+ if (!err)
+ err = es_fflush (fp);
+ if (err)
+ {
+ log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
+ remove = 1;
+ goto leave;
+ }
+
+ if (ftruncate (es_fileno (fp), es_ftello (fp)))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err));
+ remove = 1;
+ goto leave;
+ }
+ }
+
+ if (es_fclose (fp))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
+ remove = 1;
+ goto leave;
+ }
+ else
+ fp = NULL;
+
+ if (!maybe_update)
+ bump_key_eventcounter ();
+
+ leave:
+ es_fclose (fp);
+ if (remove)
+ gnupg_remove (fname);
+ xfree (fname);
+ xfree (token);
+ xfree (token0);
+ xfree (dispserialno_buffer);
+ xfree (tokenfields);
+ gcry_sexp_release (key);
+ nvc_release (pk);
+ return err;
+}
+
+/* Write an S-expression formatted key to our key storage. With FORCE
+ * passed as true an existing key with the given GRIP will get
+ * overwritten. If TIMESTAMP is not zero and the key does not yet
+ * exists it will be recorded as creation date. If SERIALNO, KEYREF,
+ * of DISPSERIALNO are not NULL they will be recorded as well. */
+int
+agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length,
+ int force, time_t timestamp,
+ const char *serialno, const char *keyref,
+ const char *dispserialno)
+{
+ char *fname;
+ estream_t fp;
+
+ fname = fname_from_keygrip (grip);
+ if (!fname)
+ return gpg_error_from_syserror ();
+
+ /* FIXME: Write to a temp file first so that write failures during
+ key updates won't lead to a key loss. */
+
+ if (!force && !gnupg_access (fname, F_OK))
+ {
+ log_error ("secret key file '%s' already exists\n", fname);
+ xfree (fname);
+ return gpg_error (GPG_ERR_EEXIST);
+ }
+
+ fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw");
+ if (!fp)
+ {
+ gpg_error_t tmperr = gpg_error_from_syserror ();
+
+ if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT)
+ {
+ fp = es_fopen (fname, "wbx,mode=-rw");
+ if (!fp)
+ tmperr = gpg_error_from_syserror ();
+ }
+ if (!fp)
+ {
+ log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
+ xfree (fname);
+ return tmperr;
+ }
+ }
+ else if (force)
+ {
+ gpg_error_t rc;
+ char first;
+
+ /* See if an existing key is in extended format. */
+ if (es_fread (&first, 1, 1, fp) != 1)
+ {
+ rc = gpg_error_from_syserror ();
+ log_error ("error reading first byte from '%s': %s\n",
+ fname, strerror (errno));
+ xfree (fname);
+ es_fclose (fp);
+ return rc;
+ }
+
+ rc = es_fseek (fp, 0, SEEK_SET);
+ if (rc)
+ {
+ log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ es_fclose (fp);
+ return rc;
+ }
+
+ if (first != '(')
+ {
+ /* Key is already in the extended format. */
+ return write_extended_private_key (0, fname, fp, 0, 0,
+ buffer, length,
+ timestamp, serialno, keyref,
+ dispserialno);
+ }
+ if (first == '(' && opt.enable_extended_key_format)
+ {
+ /* Key is in the old format - but we want the extended format. */
+ return write_extended_private_key (0, fname, fp, 1, 0,
+ buffer, length,
+ timestamp, serialno, keyref,
+ dispserialno);
+ }
+ }
+
+ if (opt.enable_extended_key_format)
+ return write_extended_private_key (0, fname, fp, 0, 1,
+ buffer, length,
+ timestamp, serialno, keyref,
+ dispserialno);
+
+ if (es_fwrite (buffer, length, 1, fp) != 1)
+ {
+ gpg_error_t tmperr = gpg_error_from_syserror ();
+ log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr));
+ es_fclose (fp);
+ gnupg_remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+
+ /* When force is given, the file might have to be truncated. */
+ if (force && ftruncate (es_fileno (fp), es_ftello (fp)))
+ {
+ gpg_error_t tmperr = gpg_error_from_syserror ();
+ log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr));
+ es_fclose (fp);
+ gnupg_remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+
+ if (es_fclose (fp))
+ {
+ gpg_error_t tmperr = gpg_error_from_syserror ();
+ log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr));
+ gnupg_remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+ bump_key_eventcounter ();
+ xfree (fname);
+ return 0;
+}
+
+
+/* Callback function to try the unprotection from the passphrase query
+ code. */
+static gpg_error_t
+try_unprotect_cb (struct pin_entry_info_s *pi)
+{
+ struct try_unprotect_arg_s *arg = pi->check_cb_arg;
+ ctrl_t ctrl = arg->ctrl;
+ size_t dummy;
+ gpg_error_t err;
+ gnupg_isotime_t now, protected_at, tmptime;
+ char *desc = NULL;
+
+ assert (!arg->unprotected_key);
+
+ arg->change_required = 0;
+ err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
+ &arg->unprotected_key, &dummy);
+ if (err)
+ return err;
+ if (!opt.max_passphrase_days || ctrl->in_passwd)
+ return 0; /* No regular passphrase change required. */
+
+ if (!*protected_at)
+ {
+ /* No protection date known - must force passphrase change. */
+ desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
+ "Please change it now."));
+ if (!desc)
+ return gpg_error_from_syserror ();
+ }
+ else
+ {
+ gnupg_get_isotime (now);
+ gnupg_copy_time (tmptime, protected_at);
+ err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
+ if (err)
+ return err;
+ if (strcmp (now, tmptime) > 0 )
+ {
+ /* Passphrase "expired". */
+ desc = xtryasprintf
+ (L_("This passphrase has not been changed%%0A"
+ "since %.4s-%.2s-%.2s. Please change it now."),
+ protected_at, protected_at+4, protected_at+6);
+ if (!desc)
+ return gpg_error_from_syserror ();
+ }
+ }
+
+ if (desc)
+ {
+ /* Change required. */
+ if (opt.enforce_passphrase_constraints)
+ {
+ err = agent_get_confirmation (ctrl, desc,
+ L_("Change passphrase"), NULL, 0);
+ if (!err)
+ arg->change_required = 1;
+ }
+ else
+ {
+ err = agent_get_confirmation (ctrl, desc,
+ L_("Change passphrase"),
+ L_("I'll change it later"), 0);
+ if (!err)
+ arg->change_required = 1;
+ else if (gpg_err_code (err) == GPG_ERR_CANCELED
+ || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
+ err = 0;
+ }
+ xfree (desc);
+ }
+
+ return err;
+}
+
+
+/* Modify a Key description, replacing certain special format
+ characters. List of currently supported replacements:
+
+ %% - Replaced by a single %
+ %c - Replaced by the content of COMMENT.
+ %C - Same as %c but put into parentheses.
+ %F - Replaced by an ssh style fingerprint computed from KEY.
+
+ The functions returns 0 on success or an error code. On success a
+ newly allocated string is stored at the address of RESULT.
+ */
+gpg_error_t
+agent_modify_description (const char *in, const char *comment,
+ const gcry_sexp_t key, char **result)
+{
+ size_t comment_length;
+ size_t in_len;
+ size_t out_len;
+ char *out;
+ size_t i;
+ int special, pass;
+ char *ssh_fpr = NULL;
+ char *p;
+
+ *result = NULL;
+
+ if (!comment)
+ comment = "";
+
+ comment_length = strlen (comment);
+ in_len = strlen (in);
+
+ /* First pass calculates the length, second pass does the actual
+ copying. */
+ /* FIXME: This can be simplified by using es_fopenmem. */
+ out = NULL;
+ out_len = 0;
+ for (pass=0; pass < 2; pass++)
+ {
+ special = 0;
+ for (i = 0; i < in_len; i++)
+ {
+ if (special)
+ {
+ special = 0;
+ switch (in[i])
+ {
+ case '%':
+ if (out)
+ *out++ = '%';
+ else
+ out_len++;
+ break;
+
+ case 'c': /* Comment. */
+ if (out)
+ {
+ memcpy (out, comment, comment_length);
+ out += comment_length;
+ }
+ else
+ out_len += comment_length;
+ break;
+
+ case 'C': /* Comment. */
+ if (!comment_length)
+ ;
+ else if (out)
+ {
+ *out++ = '(';
+ memcpy (out, comment, comment_length);
+ out += comment_length;
+ *out++ = ')';
+ }
+ else
+ out_len += comment_length + 2;
+ break;
+
+ case 'F': /* SSH style fingerprint. */
+ if (!ssh_fpr && key)
+ ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
+ &ssh_fpr);
+ if (ssh_fpr)
+ {
+ if (out)
+ out = stpcpy (out, ssh_fpr);
+ else
+ out_len += strlen (ssh_fpr);
+ }
+ break;
+
+ default: /* Invalid special sequences are kept as they are. */
+ if (out)
+ {
+ *out++ = '%';
+ *out++ = in[i];
+ }
+ else
+ out_len+=2;
+ break;
+ }
+ }
+ else if (in[i] == '%')
+ special = 1;
+ else
+ {
+ if (out)
+ *out++ = in[i];
+ else
+ out_len++;
+ }
+ }
+
+ if (!pass)
+ {
+ *result = out = xtrymalloc (out_len + 1);
+ if (!out)
+ {
+ xfree (ssh_fpr);
+ return gpg_error_from_syserror ();
+ }
+ }
+ }
+
+ *out = 0;
+ log_assert (*result + out_len == out);
+ xfree (ssh_fpr);
+
+ /* The ssh prompt may sometimes end in
+ * "...%0A ()"
+ * The empty parentheses doesn't look very good. We use this hack
+ * here to remove them as well as the indentation spaces. */
+ p = *result;
+ i = strlen (p);
+ if (i > 2 && !strcmp (p + i - 2, "()"))
+ {
+ p += i - 2;
+ *p-- = 0;
+ while (p > *result && spacep (p))
+ *p-- = 0;
+ }
+
+ return 0;
+}
+
+
+
+/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
+ should be the hex encoded keygrip of that key to be used with the
+ caching mechanism. DESC_TEXT may be set to override the default
+ description used for the pinentry. If LOOKUP_TTL is given this
+ function is used to lookup the default ttl. If R_PASSPHRASE is not
+ NULL, the function succeeded and the key was protected the used
+ passphrase (entered or from the cache) is stored there; if not NULL
+ will be stored. The caller needs to free the returned
+ passphrase. */
+static gpg_error_t
+unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
+ unsigned char **keybuf, const unsigned char *grip,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
+ char **r_passphrase)
+{
+ struct pin_entry_info_s *pi;
+ struct try_unprotect_arg_s arg;
+ int rc;
+ unsigned char *result;
+ size_t resultlen;
+ char hexgrip[40+1];
+
+ if (r_passphrase)
+ *r_passphrase = NULL;
+
+ bin2hex (grip, 20, hexgrip);
+
+ /* Initially try to get it using a cache nonce. */
+ if (cache_nonce)
+ {
+ char *pw;
+
+ pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
+ if (pw)
+ {
+ rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
+ if (!rc)
+ {
+ if (r_passphrase)
+ *r_passphrase = pw;
+ else
+ xfree (pw);
+ xfree (*keybuf);
+ *keybuf = result;
+ return 0;
+ }
+ xfree (pw);
+ }
+ }
+
+ /* First try to get it from the cache - if there is none or we can't
+ unprotect it, we fall back to ask the user */
+ if (cache_mode != CACHE_MODE_IGNORE)
+ {
+ char *pw;
+
+ retry:
+ pw = agent_get_cache (ctrl, hexgrip, cache_mode);
+ if (pw)
+ {
+ rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
+ if (!rc)
+ {
+ if (cache_mode == CACHE_MODE_NORMAL)
+ agent_store_cache_hit (hexgrip);
+ if (r_passphrase)
+ *r_passphrase = pw;
+ else
+ xfree (pw);
+ xfree (*keybuf);
+ *keybuf = result;
+ return 0;
+ }
+ xfree (pw);
+ }
+ else if (cache_mode == CACHE_MODE_NORMAL)
+ {
+ /* The standard use of GPG keys is to have a signing and an
+ encryption subkey. Commonly both use the same
+ passphrase. We try to help the user to enter the
+ passphrase only once by silently trying the last
+ correctly entered passphrase. Checking one additional
+ passphrase should be acceptable; despite the S2K
+ introduced delays. The assumed workflow is:
+
+ 1. Read encrypted message in a MUA and thus enter a
+ passphrase for the encryption subkey.
+
+ 2. Reply to that mail with an encrypted and signed
+ mail, thus entering the passphrase for the signing
+ subkey.
+
+ We can often avoid the passphrase entry in the second
+ step. We do this only in normal mode, so not to
+ interfere with unrelated cache entries. */
+ pw = agent_get_cache (ctrl, NULL, cache_mode);
+ if (pw)
+ {
+ rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
+ &result, &resultlen);
+ if (!rc)
+ {
+ if (r_passphrase)
+ *r_passphrase = pw;
+ else
+ xfree (pw);
+ xfree (*keybuf);
+ *keybuf = result;
+ return 0;
+ }
+ xfree (pw);
+ }
+ }
+
+ /* If the pinentry is currently in use, we wait up to 60 seconds
+ for it to close and check the cache again. This solves a common
+ situation where several requests for unprotecting a key have
+ been made but the user is still entering the passphrase for
+ the first request. Because all requests to agent_askpin are
+ serialized they would then pop up one after the other to
+ request the passphrase - despite that the user has already
+ entered it and is then available in the cache. This
+ implementation is not race free but in the worst case the
+ user has to enter the passphrase only once more. */
+ if (pinentry_active_p (ctrl, 0))
+ {
+ /* Active - wait */
+ if (!pinentry_active_p (ctrl, 60))
+ {
+ /* We need to give the other thread a chance to actually put
+ it into the cache. */
+ npth_sleep (1);
+ goto retry;
+ }
+ /* Timeout - better call pinentry now the plain way. */
+ }
+ }
+
+ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi->min_digits = 0; /* we want a real passphrase */
+ pi->max_digits = 16;
+ pi->max_tries = 3;
+ pi->check_cb = try_unprotect_cb;
+ arg.ctrl = ctrl;
+ arg.protected_key = *keybuf;
+ arg.unprotected_key = NULL;
+ arg.change_required = 0;
+ pi->check_cb_arg = &arg;
+
+ rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
+ if (rc)
+ {
+ if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+ {
+ log_error ("Clearing pinentry cache which caused error %s\n",
+ gpg_strerror (rc));
+
+ agent_clear_passphrase (ctrl, hexgrip, cache_mode);
+ }
+ }
+ else
+ {
+ assert (arg.unprotected_key);
+ if (arg.change_required)
+ {
+ /* The callback told as that the user should change their
+ passphrase. Present the dialog to do. */
+ size_t canlen, erroff;
+ gcry_sexp_t s_skey;
+
+ assert (arg.unprotected_key);
+ canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
+ rc = gcry_sexp_sscan (&s_skey, &erroff,
+ (char*)arg.unprotected_key, canlen);
+ if (rc)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ wipememory (arg.unprotected_key, canlen);
+ xfree (arg.unprotected_key);
+ xfree (pi);
+ return rc;
+ }
+ rc = agent_protect_and_store (ctrl, s_skey, NULL);
+ gcry_sexp_release (s_skey);
+ if (rc)
+ {
+ log_error ("changing the passphrase failed: %s\n",
+ gpg_strerror (rc));
+ wipememory (arg.unprotected_key, canlen);
+ xfree (arg.unprotected_key);
+ xfree (pi);
+ return rc;
+ }
+ }
+ else
+ {
+ /* Passphrase is fine. */
+ agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
+ lookup_ttl? lookup_ttl (hexgrip) : 0);
+ agent_store_cache_hit (hexgrip);
+ if (r_passphrase && *pi->pin)
+ *r_passphrase = xtrystrdup (pi->pin);
+ }
+ xfree (*keybuf);
+ *keybuf = arg.unprotected_key;
+ }
+ xfree (pi);
+ return rc;
+}
+
+
+/* Read the key identified by GRIP from the private key directory and
+ * return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
+ * is not NULl and the extended key format is used, the meta data
+ * items are stored there. However the "Key:" item is removed from
+ * it. On failure returns an error code and stores NULL at RESULT. */
+static gpg_error_t
+read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
+{
+ gpg_error_t err;
+ char *fname;
+ estream_t fp;
+ struct stat st;
+ unsigned char *buf;
+ size_t buflen, erroff;
+ gcry_sexp_t s_skey;
+ char hexgrip[40+4+1];
+ char first;
+
+ *result = NULL;
+ if (r_keymeta)
+ *r_keymeta = NULL;
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+ hexgrip, NULL);
+ fp = es_fopen (fname, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_ENOENT)
+ log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
+ xfree (fname);
+ return err;
+ }
+
+ if (es_fread (&first, 1, 1, fp) != 1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading first byte from '%s': %s\n",
+ fname, gpg_strerror (err));
+ xfree (fname);
+ es_fclose (fp);
+ return err;
+ }
+
+ if (es_fseek (fp, 0, SEEK_SET))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
+ xfree (fname);
+ es_fclose (fp);
+ return err;
+ }
+
+ if (first != '(')
+ {
+ /* Key is in extended format. */
+ nvc_t pk;
+ int line;
+
+ err = nvc_parse_private_key (&pk, &line, fp);
+ es_fclose (fp);
+
+ if (err)
+ log_error ("error parsing '%s' line %d: %s\n",
+ fname, line, gpg_strerror (err));
+ else
+ {
+ err = nvc_get_private_key (pk, result);
+ if (err)
+ log_error ("error getting private key from '%s': %s\n",
+ fname, gpg_strerror (err));
+ else
+ nvc_delete_named (pk, "Key:");
+ }
+
+ if (!err && r_keymeta)
+ *r_keymeta = pk;
+ else
+ nvc_release (pk);
+ xfree (fname);
+ return err;
+ }
+
+ if (fstat (es_fileno (fp), &st))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
+ xfree (fname);
+ es_fclose (fp);
+ return err;
+ }
+
+ buflen = st.st_size;
+ buf = xtrymalloc (buflen+1);
+ if (!buf)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error allocating %zu bytes for '%s': %s\n",
+ buflen, fname, gpg_strerror (err));
+ xfree (fname);
+ es_fclose (fp);
+ xfree (buf);
+ return err;
+
+ }
+
+ if (es_fread (buf, buflen, 1, fp) != 1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading %zu bytes from '%s': %s\n",
+ buflen, fname, gpg_strerror (err));
+ xfree (fname);
+ es_fclose (fp);
+ xfree (buf);
+ return err;
+ }
+
+ /* Convert the file into a gcrypt S-expression object. */
+ err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
+ xfree (fname);
+ es_fclose (fp);
+ xfree (buf);
+ if (err)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (err));
+ return err;
+ }
+ *result = s_skey;
+ return 0;
+}
+
+
+/* Remove the key identified by GRIP from the private key directory. */
+static gpg_error_t
+remove_key_file (const unsigned char *grip)
+{
+ gpg_error_t err = 0;
+ char *fname;
+ char hexgrip[40+4+1];
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+ fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+ hexgrip, NULL);
+ if (gnupg_remove (fname))
+ err = gpg_error_from_syserror ();
+ xfree (fname);
+ return err;
+}
+
+
+/* Return the secret key as an S-Exp in RESULT after locating it using
+ the GRIP. If the operation shall be diverted to a token, an
+ allocated S-expression with the shadow_info part from the file is
+ stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
+ CACHE_MODE defines now the cache shall be used. DESC_TEXT may be
+ set to present a custom description for the pinentry. LOOKUP_TTL
+ is an optional function to convey a TTL to the cache manager; we do
+ not simply pass the TTL value because the value is only needed if
+ an unprotect action was needed and looking up the TTL may have some
+ overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is
+ given that cache item is first tried to get a passphrase. If
+ R_PASSPHRASE is not NULL, the function succeeded and the key was
+ protected the used passphrase (entered or from the cache) is stored
+ there; if not NULL will be stored. The caller needs to free the
+ returned passphrase. */
+gpg_error_t
+agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
+ const char *desc_text,
+ const unsigned char *grip, unsigned char **shadow_info,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
+ gcry_sexp_t *result, char **r_passphrase)
+{
+ gpg_error_t err;
+ unsigned char *buf;
+ size_t len, buflen, erroff;
+ gcry_sexp_t s_skey;
+
+ *result = NULL;
+ if (shadow_info)
+ *shadow_info = NULL;
+ if (r_passphrase)
+ *r_passphrase = NULL;
+
+ err = read_key_file (grip, &s_skey, NULL);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ err = gpg_error (GPG_ERR_NO_SECKEY);
+ return err;
+ }
+
+ /* For use with the protection functions we also need the key as an
+ canonical encoded S-expression in a buffer. Create this buffer
+ now. */
+ err = make_canon_sexp (s_skey, &buf, &len);
+ if (err)
+ return err;
+
+ switch (agent_private_key_type (buf))
+ {
+ case PRIVATE_KEY_CLEAR:
+ break; /* no unprotection needed */
+ case PRIVATE_KEY_OPENPGP_NONE:
+ {
+ unsigned char *buf_new;
+ size_t buf_newlen;
+
+ err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
+ if (err)
+ log_error ("failed to convert unprotected openpgp key: %s\n",
+ gpg_strerror (err));
+ else
+ {
+ xfree (buf);
+ buf = buf_new;
+ }
+ }
+ break;
+ case PRIVATE_KEY_PROTECTED:
+ {
+ char *desc_text_final;
+ char *comment = NULL;
+
+ /* Note, that we will take the comment as a C string for
+ display purposes; i.e. all stuff beyond a Nul character is
+ ignored. */
+ {
+ gcry_sexp_t comment_sexp;
+
+ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
+ if (comment_sexp)
+ comment = gcry_sexp_nth_string (comment_sexp, 1);
+ gcry_sexp_release (comment_sexp);
+ }
+
+ desc_text_final = NULL;
+ if (desc_text)
+ err = agent_modify_description (desc_text, comment, s_skey,
+ &desc_text_final);
+ gcry_free (comment);
+
+ if (!err)
+ {
+ err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
+ cache_mode, lookup_ttl, r_passphrase);
+ if (err)
+ log_error ("failed to unprotect the secret key: %s\n",
+ gpg_strerror (err));
+ }
+
+ xfree (desc_text_final);
+ }
+ break;
+ case PRIVATE_KEY_SHADOWED:
+ if (shadow_info)
+ {
+ const unsigned char *s;
+ size_t n;
+
+ err = agent_get_shadow_info (buf, &s);
+ if (!err)
+ {
+ n = gcry_sexp_canon_len (s, 0, NULL,NULL);
+ log_assert (n);
+ *shadow_info = xtrymalloc (n);
+ if (!*shadow_info)
+ err = out_of_core ();
+ else
+ {
+ memcpy (*shadow_info, s, n);
+ err = 0;
+ }
+ }
+ if (err)
+ log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
+ }
+ else
+ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ break;
+ default:
+ log_error ("invalid private key format\n");
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ break;
+ }
+ gcry_sexp_release (s_skey);
+ s_skey = NULL;
+ if (err)
+ {
+ xfree (buf);
+ if (r_passphrase)
+ {
+ xfree (*r_passphrase);
+ *r_passphrase = NULL;
+ }
+ return err;
+ }
+
+ buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
+ err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
+ wipememory (buf, buflen);
+ xfree (buf);
+ if (err)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (err));
+ if (r_passphrase)
+ {
+ xfree (*r_passphrase);
+ *r_passphrase = NULL;
+ }
+ return err;
+ }
+
+ *result = s_skey;
+ return 0;
+}
+
+
+/* Return the string name from the S-expression S_KEY as well as a
+ string describing the names of the parameters. ALGONAMESIZE and
+ ELEMSSIZE give the allocated size of the provided buffers. The
+ buffers may be NULL if not required. If R_LIST is not NULL the top
+ level list will be stored there; the caller needs to release it in
+ this case. */
+static gpg_error_t
+key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
+ char *r_algoname, size_t algonamesize,
+ char *r_elems, size_t elemssize)
+{
+ gcry_sexp_t list, l2;
+ const char *name, *algoname, *elems;
+ size_t n;
+
+ if (r_list)
+ *r_list = NULL;
+
+ list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_key, "private-key", 0 );
+ if (!list)
+ {
+ log_error ("invalid private key format\n");
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ l2 = gcry_sexp_cadr (list);
+ gcry_sexp_release (list);
+ list = l2;
+ name = gcry_sexp_nth_data (list, 0, &n);
+ if (n==3 && !memcmp (name, "rsa", 3))
+ {
+ algoname = "rsa";
+ elems = "ne";
+ }
+ else if (n==3 && !memcmp (name, "dsa", 3))
+ {
+ algoname = "dsa";
+ elems = "pqgy";
+ }
+ else if (n==3 && !memcmp (name, "ecc", 3))
+ {
+ algoname = "ecc";
+ elems = "pabgnq";
+ }
+ else if (n==5 && !memcmp (name, "ecdsa", 5))
+ {
+ algoname = "ecdsa";
+ elems = "pabgnq";
+ }
+ else if (n==4 && !memcmp (name, "ecdh", 4))
+ {
+ algoname = "ecdh";
+ elems = "pabgnq";
+ }
+ else if (n==3 && !memcmp (name, "elg", 3))
+ {
+ algoname = "elg";
+ elems = "pgy";
+ }
+ else
+ {
+ log_error ("unknown private key algorithm\n");
+ gcry_sexp_release (list);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ if (r_algoname)
+ {
+ if (strlen (algoname) >= algonamesize)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+ strcpy (r_algoname, algoname);
+ }
+ if (r_elems)
+ {
+ if (strlen (elems) >= elemssize)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+ strcpy (r_elems, elems);
+ }
+
+ if (r_list)
+ *r_list = list;
+ else
+ gcry_sexp_release (list);
+
+ return 0;
+}
+
+
+/* Return true if KEYPARMS holds an EdDSA key. */
+static int
+is_eddsa (gcry_sexp_t keyparms)
+{
+ int result = 0;
+ gcry_sexp_t list;
+ const char *s;
+ size_t n;
+ int i;
+
+ list = gcry_sexp_find_token (keyparms, "flags", 0);
+ for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
+ {
+ s = gcry_sexp_nth_data (list, i, &n);
+ if (!s)
+ continue; /* Not a data element. */
+
+ if (n == 5 && !memcmp (s, "eddsa", 5))
+ {
+ result = 1;
+ break;
+ }
+ }
+ gcry_sexp_release (list);
+ return result;
+}
+
+
+/* Return the public key algorithm number if S_KEY is a DSA style key.
+ If it is not a DSA style key, return 0. */
+int
+agent_is_dsa_key (gcry_sexp_t s_key)
+{
+ int result;
+ gcry_sexp_t list;
+ char algoname[6];
+
+ if (!s_key)
+ return 0;
+
+ if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
+ return 0; /* Error - assume it is not an DSA key. */
+
+ if (!strcmp (algoname, "dsa"))
+ result = GCRY_PK_DSA;
+ else if (!strcmp (algoname, "ecc"))
+ {
+ if (is_eddsa (list))
+ result = 0;
+ else
+ result = GCRY_PK_ECDSA;
+ }
+ else if (!strcmp (algoname, "ecdsa"))
+ result = GCRY_PK_ECDSA;
+ else
+ result = 0;
+
+ gcry_sexp_release (list);
+ return result;
+}
+
+
+/* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */
+int
+agent_is_eddsa_key (gcry_sexp_t s_key)
+{
+ int result;
+ gcry_sexp_t list;
+ char algoname[6];
+
+ if (!s_key)
+ return 0;
+
+ if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
+ return 0; /* Error - assume it is not an EdDSA key. */
+
+ if (!strcmp (algoname, "ecc") && is_eddsa (list))
+ result = 1;
+ else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */
+ result = 1;
+ else
+ result = 0;
+
+ gcry_sexp_release (list);
+ return result;
+}
+
+
+/* Return the key for the keygrip GRIP. The result is stored at
+ RESULT. This function extracts the key from the private key
+ database and returns it as an S-expression object as it is. On
+ failure an error code is returned and NULL stored at RESULT. */
+gpg_error_t
+agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+ gcry_sexp_t *result)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey;
+
+ (void)ctrl;
+
+ *result = NULL;
+
+ err = read_key_file (grip, &s_skey, NULL);
+ if (!err)
+ *result = s_skey;
+ return err;
+}
+
+
+gpg_error_t
+agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip,
+ nvc_t *r_keymeta)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey;
+
+ (void)ctrl;
+
+ err = read_key_file (grip, &s_skey, r_keymeta);
+ gcry_sexp_release (s_skey);
+ return err;
+}
+
+
+/* Return the public key for the keygrip GRIP. The result is stored
+ at RESULT. This function extracts the public key from the private
+ key database. On failure an error code is returned and NULL stored
+ at RESULT. */
+gpg_error_t
+agent_public_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result)
+{
+ gpg_error_t err;
+ int i, idx;
+ gcry_sexp_t s_skey;
+ const char *algoname, *elems;
+ int npkey;
+ gcry_mpi_t array[10];
+ gcry_sexp_t curve = NULL;
+ gcry_sexp_t flags = NULL;
+ gcry_sexp_t uri_sexp, comment_sexp;
+ const char *uri, *comment;
+ size_t uri_length, comment_length;
+ int uri_intlen, comment_intlen;
+ char *format, *p;
+ void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
+ for comment + end-of-list. */
+ int argidx;
+ gcry_sexp_t list = NULL;
+ const char *s;
+
+ (void)ctrl;
+
+ *result = NULL;
+
+ err = read_key_file (grip, &s_skey, NULL);
+ if (err)
+ return err;
+
+ for (i=0; i < DIM (array); i++)
+ array[i] = NULL;
+
+ err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
+ array, DIM (array), &curve, &flags);
+ if (err)
+ {
+ gcry_sexp_release (s_skey);
+ return err;
+ }
+
+ uri = NULL;
+ uri_length = 0;
+ uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0);
+ if (uri_sexp)
+ uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length);
+
+ comment = NULL;
+ comment_length = 0;
+ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
+ if (comment_sexp)
+ comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
+
+ gcry_sexp_release (s_skey);
+ s_skey = NULL;
+
+
+ /* FIXME: The following thing is pretty ugly code; we should
+ investigate how to make it cleaner. Probably code to handle
+ canonical S-expressions in a memory buffer is better suited for
+ such a task. After all that is what we do in protect.c. Need
+ to find common patterns and write a straightformward API to use
+ them. */
+ assert (sizeof (size_t) <= sizeof (void*));
+
+ format = xtrymalloc (15+4+7*npkey+10+15+1+1);
+ if (!format)
+ {
+ err = gpg_error_from_syserror ();
+ for (i=0; array[i]; i++)
+ gcry_mpi_release (array[i]);
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+ gcry_sexp_release (uri_sexp);
+ gcry_sexp_release (comment_sexp);
+ return err;
+ }
+
+ argidx = 0;
+ p = stpcpy (stpcpy (format, "(public-key("), algoname);
+ p = stpcpy (p, "%S%S"); /* curve name and flags. */
+ args[argidx++] = &curve;
+ args[argidx++] = &flags;
+ for (idx=0, s=elems; idx < npkey; idx++)
+ {
+ *p++ = '(';
+ *p++ = *s++;
+ p = stpcpy (p, " %m)");
+ assert (argidx < DIM (args));
+ args[argidx++] = &array[idx];
+ }
+ *p++ = ')';
+ if (uri)
+ {
+ p = stpcpy (p, "(uri %b)");
+ assert (argidx+1 < DIM (args));
+ uri_intlen = (int)uri_length;
+ args[argidx++] = (void *)&uri_intlen;
+ args[argidx++] = (void *)&uri;
+ }
+ if (comment)
+ {
+ p = stpcpy (p, "(comment %b)");
+ assert (argidx+1 < DIM (args));
+ comment_intlen = (int)comment_length;
+ args[argidx++] = (void *)&comment_intlen;
+ args[argidx++] = (void*)&comment;
+ }
+ *p++ = ')';
+ *p = 0;
+ assert (argidx < DIM (args));
+ args[argidx] = NULL;
+
+ err = gcry_sexp_build_array (&list, NULL, format, args);
+ xfree (format);
+ for (i=0; array[i]; i++)
+ gcry_mpi_release (array[i]);
+ gcry_sexp_release (curve);
+ gcry_sexp_release (flags);
+ gcry_sexp_release (uri_sexp);
+ gcry_sexp_release (comment_sexp);
+
+ if (!err)
+ *result = list;
+ return err;
+}
+
+
+
+/* Check whether the secret key identified by GRIP is available.
+ Returns 0 is the key is available. */
+int
+agent_key_available (const unsigned char *grip)
+{
+ int result;
+ char *fname;
+ char hexgrip[40+4+1];
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+ hexgrip, NULL);
+ result = !gnupg_access (fname, R_OK)? 0 : -1;
+ xfree (fname);
+ return result;
+}
+
+
+
+/* Return the information about the secret key specified by the binary
+ keygrip GRIP. If the key is a shadowed one the shadow information
+ will be stored at the address R_SHADOW_INFO as an allocated
+ S-expression. */
+gpg_error_t
+agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
+ int *r_keytype, unsigned char **r_shadow_info)
+{
+ gpg_error_t err;
+ unsigned char *buf;
+ size_t len;
+ int keytype;
+
+ (void)ctrl;
+
+ if (r_keytype)
+ *r_keytype = PRIVATE_KEY_UNKNOWN;
+ if (r_shadow_info)
+ *r_shadow_info = NULL;
+
+ {
+ gcry_sexp_t sexp;
+
+ err = read_key_file (grip, &sexp, NULL);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ return gpg_error (GPG_ERR_NOT_FOUND);
+ else
+ return err;
+ }
+ err = make_canon_sexp (sexp, &buf, &len);
+ gcry_sexp_release (sexp);
+ if (err)
+ return err;
+ }
+
+ keytype = agent_private_key_type (buf);
+ switch (keytype)
+ {
+ case PRIVATE_KEY_CLEAR:
+ case PRIVATE_KEY_OPENPGP_NONE:
+ break;
+ case PRIVATE_KEY_PROTECTED:
+ /* If we ever require it we could retrieve the comment fields
+ from such a key. */
+ break;
+ case PRIVATE_KEY_SHADOWED:
+ if (r_shadow_info)
+ {
+ const unsigned char *s;
+ size_t n;
+
+ err = agent_get_shadow_info (buf, &s);
+ if (!err)
+ {
+ n = gcry_sexp_canon_len (s, 0, NULL, NULL);
+ assert (n);
+ *r_shadow_info = xtrymalloc (n);
+ if (!*r_shadow_info)
+ err = gpg_error_from_syserror ();
+ else
+ memcpy (*r_shadow_info, s, n);
+ }
+ }
+ break;
+ default:
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ break;
+ }
+
+ if (!err && r_keytype)
+ *r_keytype = keytype;
+
+ xfree (buf);
+ return err;
+}
+
+
+
+/* Delete the key with GRIP from the disk after having asked for
+ * confirmation using DESC_TEXT. If FORCE is set the function won't
+ * require a confirmation via Pinentry or warns if the key is also
+ * used by ssh. If ONLY_STUBS is set only stub keys (references to
+ * smartcards) will be affected.
+ *
+ * Common error codes are:
+ * GPG_ERR_NO_SECKEY
+ * GPG_ERR_KEY_ON_CARD
+ * GPG_ERR_NOT_CONFIRMED
+ * GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested.
+ */
+gpg_error_t
+agent_delete_key (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *grip, int force, int only_stubs)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *buf = NULL;
+ size_t len;
+ char *desc_text_final = NULL;
+ char *comment = NULL;
+ ssh_control_file_t cf = NULL;
+ char hexgrip[40+4+1];
+ char *default_desc = NULL;
+ int key_type;
+
+ err = read_key_file (grip, &s_skey, NULL);
+ if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ err = gpg_error (GPG_ERR_NO_SECKEY);
+ if (err)
+ goto leave;
+
+ err = make_canon_sexp (s_skey, &buf, &len);
+ if (err)
+ goto leave;
+
+ key_type = agent_private_key_type (buf);
+ if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
+ {
+ err = gpg_error (GPG_ERR_FORBIDDEN);
+ goto leave;
+ }
+
+ switch (key_type)
+ {
+ case PRIVATE_KEY_CLEAR:
+ case PRIVATE_KEY_OPENPGP_NONE:
+ case PRIVATE_KEY_PROTECTED:
+ bin2hex (grip, 20, hexgrip);
+ if (!force)
+ {
+ if (!desc_text)
+ {
+ default_desc = xtryasprintf
+ (L_("Do you really want to delete the key identified by keygrip%%0A"
+ " %s%%0A %%C%%0A?"), hexgrip);
+ desc_text = default_desc;
+ }
+
+ /* Note, that we will take the comment as a C string for
+ display purposes; i.e. all stuff beyond a Nul character is
+ ignored. */
+ {
+ gcry_sexp_t comment_sexp;
+
+ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
+ if (comment_sexp)
+ comment = gcry_sexp_nth_string (comment_sexp, 1);
+ gcry_sexp_release (comment_sexp);
+ }
+
+ if (desc_text)
+ err = agent_modify_description (desc_text, comment, s_skey,
+ &desc_text_final);
+ if (err)
+ goto leave;
+
+ err = agent_get_confirmation (ctrl, desc_text_final,
+ L_("Delete key"), L_("No"), 0);
+ if (err)
+ goto leave;
+
+ cf = ssh_open_control_file ();
+ if (cf)
+ {
+ if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
+ {
+ err = agent_get_confirmation
+ (ctrl,
+ L_("Warning: This key is also listed for use with SSH!\n"
+ "Deleting the key might remove your ability to "
+ "access remote machines."),
+ L_("Delete key"), L_("No"), 0);
+ if (err)
+ goto leave;
+ }
+ }
+ }
+ err = remove_key_file (grip);
+ break;
+
+ case PRIVATE_KEY_SHADOWED:
+ err = remove_key_file (grip);
+ break;
+
+ default:
+ log_error ("invalid private key format\n");
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ break;
+ }
+
+ leave:
+ ssh_close_control_file (cf);
+ gcry_free (comment);
+ xfree (desc_text_final);
+ xfree (default_desc);
+ xfree (buf);
+ gcry_sexp_release (s_skey);
+ return err;
+}
+
+
+/* Write an S-expression formatted shadow key to our key storage.
+ Shadow key is created by an S-expression public key in PKBUF and
+ card's SERIALNO and the IDSTRING. With FORCE passed as true an
+ existing key with the given GRIP will get overwritten. If
+ DISPSERIALNO is not NULL the human readable s/n will also be
+ recorded in the key file. If MAYBE_UPDATE is set it is assumed that
+ the shadow key already exists and we test whether we should update
+ it (FORCE is ignored in this case). */
+gpg_error_t
+agent_write_shadow_key (int maybe_update, const unsigned char *grip,
+ const char *serialno, const char *keyid,
+ const unsigned char *pkbuf, int force,
+ const char *dispserialno)
+{
+ gpg_error_t err;
+ unsigned char *shadow_info;
+ unsigned char *shdkey;
+ size_t len;
+ char *fname = NULL;
+ estream_t fp = NULL;
+ char first;
+
+ if (maybe_update && !opt.enable_extended_key_format)
+ return 0; /* Silently ignore. */
+
+ /* Just in case some caller did not parse the stuff correctly, skip
+ * leading spaces. */
+ while (spacep (serialno))
+ serialno++;
+ while (spacep (keyid))
+ keyid++;
+
+ shadow_info = make_shadow_info (serialno, keyid);
+ if (!shadow_info)
+ return gpg_error_from_syserror ();
+
+ err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
+ xfree (shadow_info);
+ if (err)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+
+ if (maybe_update) /* Update mode. */
+ {
+ fname = fname_from_keygrip (grip);
+ if (!fname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ fp = es_fopen (fname, "rb+,mode=-rw");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("shadow key file '%s' disappeared\n", fname);
+ goto leave;
+ }
+
+ /* See if an existing key is in extended format. */
+ if (es_fread (&first, 1, 1, fp) != 1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading first byte from '%s': %s\n",
+ fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ if (es_fseek (fp, 0, SEEK_SET))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ /* "(first == '(')" indicates that the key is in the old format. */
+ err = write_extended_private_key (maybe_update,
+ fname, fp, (first == '('), 0,
+ shdkey, len,
+ 0, serialno, keyid,
+ dispserialno);
+ fname = NULL; /* Ownership was transferred. */
+ fp = NULL; /* Ditto. */
+ }
+ else /* Standard mode */
+ {
+ err = agent_write_private_key (grip, shdkey, len, force, 0,
+ serialno, keyid, dispserialno);
+ }
+
+ leave:
+ xfree (fname);
+ es_fclose (fp);
+ xfree (shdkey);
+ if (err)
+ log_error ("error %s key: %s\n", maybe_update? "updating":"writing",
+ gpg_strerror (err));
+
+ return err;
+}
diff --git a/agent/genkey.c b/agent/genkey.c
new file mode 100644
index 0000000..a944ac7
--- /dev/null
+++ b/agent/genkey.c
@@ -0,0 +1,650 @@
+/* genkey.c - Generate a keypair
+ * Copyright (C) 2002, 2003, 2004, 2007, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2015 g10 Code GmbH.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include "agent.h"
+#include "../common/i18n.h"
+#include "../common/exechelp.h"
+#include "../common/sysutils.h"
+
+static int
+store_key (gcry_sexp_t private, const char *passphrase, int force,
+ unsigned long s2k_count, time_t timestamp)
+{
+ int rc;
+ unsigned char *buf;
+ size_t len;
+ unsigned char grip[20];
+
+ if ( !gcry_pk_get_keygrip (private, grip) )
+ {
+ log_error ("can't calculate keygrip\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = gcry_malloc_secure (len);
+ if (!buf)
+ return out_of_core ();
+ len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+
+ if (passphrase)
+ {
+ unsigned char *p;
+
+ rc = agent_protect (buf, passphrase, &p, &len, s2k_count, -1);
+ if (rc)
+ {
+ xfree (buf);
+ return rc;
+ }
+ xfree (buf);
+ buf = p;
+ }
+
+ rc = agent_write_private_key (grip, buf, len, force, timestamp,
+ NULL, NULL, NULL);
+ xfree (buf);
+ return rc;
+}
+
+
+/* Count the number of non-alpha characters in S. Control characters
+ and non-ascii characters are not considered. */
+static size_t
+nonalpha_count (const char *s)
+{
+ size_t n;
+
+ for (n=0; *s; s++)
+ if (isascii (*s) && ( isdigit (*s) || ispunct (*s) ))
+ n++;
+
+ return n;
+}
+
+
+/* Check PW against a list of pattern. Return 0 if PW does not match
+ these pattern. If CHECK_CONSTRAINTS_NEW_SYMKEY is set in flags and
+ --check-sym-passphrase-pattern has been configured, use the pattern
+ file from that option. */
+static int
+do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags)
+{
+ gpg_error_t err = 0;
+ const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN);
+ estream_t stream_to_check_pattern = NULL;
+ const char *argv[10];
+ pid_t pid;
+ int result, i;
+ const char *pattern;
+ char *patternfname;
+
+ (void)ctrl;
+
+ pattern = opt.check_passphrase_pattern;
+ if ((flags & CHECK_CONSTRAINTS_NEW_SYMKEY)
+ && opt.check_sym_passphrase_pattern)
+ pattern = opt.check_sym_passphrase_pattern;
+ if (!pattern)
+ return 1; /* Oops - Assume password should not be used */
+
+ if (strchr (pattern, '/') || strchr (pattern, '\\')
+ || (*pattern == '~' && pattern[1] == '/'))
+ patternfname = make_absfilename_try (pattern, NULL);
+ else
+ patternfname = make_filename_try (gnupg_sysconfdir (), pattern, NULL);
+ if (!patternfname)
+ {
+ log_error ("error making filename from '%s': %s\n",
+ pattern, gpg_strerror (gpg_error_from_syserror ()));
+ return 1; /* Do not pass the check. */
+ }
+
+ /* Make debugging a broken config easier by printing a useful error
+ * message. */
+ if (gnupg_access (patternfname, F_OK))
+ {
+ log_error ("error accessing '%s': %s\n",
+ patternfname, gpg_strerror (gpg_error_from_syserror ()));
+ xfree (patternfname);
+ return 1; /* Do not pass the check. */
+ }
+
+ i = 0;
+ argv[i++] = "--null";
+ argv[i++] = "--",
+ argv[i++] = patternfname,
+ argv[i] = NULL;
+ assert (i < sizeof argv);
+
+ if (gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
+ &stream_to_check_pattern, NULL, NULL, &pid))
+ result = 1; /* Execute error - assume password should no be used. */
+ else
+ {
+ es_set_binary (stream_to_check_pattern);
+ if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error writing to pipe: %s\n"), gpg_strerror (err));
+ result = 1; /* Error - assume password should not be used. */
+ }
+ else
+ es_fflush (stream_to_check_pattern);
+ es_fclose (stream_to_check_pattern);
+ if (gnupg_wait_process (pgmname, pid, 1, NULL))
+ result = 1; /* Helper returned an error - probably a match. */
+ else
+ result = 0; /* Success; i.e. no match. */
+ gnupg_release_process (pid);
+ }
+
+ xfree (patternfname);
+ return result;
+}
+
+
+static int
+take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn)
+{
+ gpg_error_t err;
+
+ if (opt.enforce_passphrase_constraints)
+ {
+ err = agent_show_message (ctrl, desc, L_("Enter new passphrase"));
+ if (!err)
+ err = gpg_error (GPG_ERR_CANCELED);
+ }
+ else
+ err = agent_get_confirmation (ctrl, desc,
+ anyway_btn, L_("Enter new passphrase"), 0);
+ return err;
+}
+
+
+static int
+take_this_one_anyway (ctrl_t ctrl, const char *desc)
+{
+ return take_this_one_anyway2 (ctrl, desc, L_("Take this one anyway"));
+}
+
+
+/* Check whether the passphrase PW is suitable. Returns 0 if the
+ * passphrase is suitable and true if it is not and the user should be
+ * asked to provide a different one. If FAILED_CONSTRAINT is set, a
+ * message describing the problem is returned at FAILED_CONSTRAINT.
+ * The FLAGS are:
+ * CHECK_CONSTRAINTS_NOT_EMPTY
+ * Do not allow an empty passphrase
+ * CHECK_CONSTRAINTS_NEW_SYMKEY
+ * Hint that the passphrase is used for a new symmetric key.
+ */
+int
+check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags,
+ char **failed_constraint)
+{
+ gpg_error_t err = 0;
+ unsigned int minlen = opt.min_passphrase_len;
+ unsigned int minnonalpha = opt.min_passphrase_nonalpha;
+ char *msg1 = NULL;
+ char *msg2 = NULL;
+ char *msg3 = NULL;
+ int no_empty = !!(flags & CHECK_CONSTRAINTS_NOT_EMPTY);
+
+ if (ctrl && ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ return 0;
+
+ if (!pw)
+ pw = "";
+
+ /* The first check is to warn about an empty passphrase. */
+ if (!*pw)
+ {
+ const char *desc = (opt.enforce_passphrase_constraints || no_empty?
+ L_("You have not entered a passphrase!%0A"
+ "An empty passphrase is not allowed.") :
+ L_("You have not entered a passphrase - "
+ "this is in general a bad idea!%0A"
+ "Please confirm that you do not want to "
+ "have any protection on your key."));
+
+ err = 1;
+ if (failed_constraint)
+ {
+ if (opt.enforce_passphrase_constraints || no_empty)
+ *failed_constraint = xstrdup (desc);
+ else
+ err = take_this_one_anyway2 (ctrl, desc,
+ L_("Yes, protection is not needed"));
+ }
+
+ goto leave;
+ }
+
+ /* Now check the constraints and collect the error messages unless
+ in silent mode which returns immediately. */
+ if (utf8_charcount (pw, -1) < minlen )
+ {
+ if (!failed_constraint)
+ {
+ err = gpg_error (GPG_ERR_INV_PASSPHRASE);
+ goto leave;
+ }
+
+ msg1 = xtryasprintf
+ ( ngettext ("A passphrase should be at least %u character long.",
+ "A passphrase should be at least %u characters long.",
+ minlen), minlen );
+ if (!msg1)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ if (nonalpha_count (pw) < minnonalpha )
+ {
+ if (!failed_constraint)
+ {
+ err = gpg_error (GPG_ERR_INV_PASSPHRASE);
+ goto leave;
+ }
+
+ msg2 = xtryasprintf
+ ( ngettext ("A passphrase should contain at least %u digit or%%0A"
+ "special character.",
+ "A passphrase should contain at least %u digits or%%0A"
+ "special characters.",
+ minnonalpha), minnonalpha );
+ if (!msg2)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ /* If configured check the passphrase against a list of known words
+ and pattern. The actual test is done by an external program.
+ The warning message is generic to give the user no hint on how to
+ circumvent this list. */
+ if (*pw
+ && (opt.check_passphrase_pattern || opt.check_sym_passphrase_pattern)
+ && do_check_passphrase_pattern (ctrl, pw, flags))
+ {
+ if (!failed_constraint)
+ {
+ err = gpg_error (GPG_ERR_INV_PASSPHRASE);
+ goto leave;
+ }
+
+ msg3 = xtryasprintf
+ (L_("A passphrase may not be a known term or match%%0A"
+ "certain pattern."));
+ if (!msg3)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ if (failed_constraint && (msg1 || msg2 || msg3))
+ {
+ char *msg;
+ size_t n;
+
+ msg = strconcat
+ (L_("Warning: You have entered an insecure passphrase."),
+ "%0A%0A",
+ msg1? msg1 : "", msg1? "%0A" : "",
+ msg2? msg2 : "", msg2? "%0A" : "",
+ msg3? msg3 : "", msg3? "%0A" : "",
+ NULL);
+ if (!msg)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ /* Strip a trailing "%0A". */
+ n = strlen (msg);
+ if (n > 3 && !strcmp (msg + n - 3, "%0A"))
+ msg[n-3] = 0;
+
+ err = 1;
+ if (opt.enforce_passphrase_constraints)
+ *failed_constraint = msg;
+ else
+ {
+ err = take_this_one_anyway (ctrl, msg);
+ xfree (msg);
+ }
+ }
+
+ leave:
+ xfree (msg1);
+ xfree (msg2);
+ xfree (msg3);
+ return err;
+}
+
+
+/* Callback function to compare the first entered PIN with the one
+ currently being entered. */
+static gpg_error_t
+reenter_compare_cb (struct pin_entry_info_s *pi)
+{
+ const char *pin1 = pi->check_cb_arg;
+
+ if (!strcmp (pin1, pi->pin))
+ return 0; /* okay */
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+}
+
+
+/* Ask the user for a new passphrase using PROMPT. On success the
+ function returns 0 and store the passphrase at R_PASSPHRASE; if the
+ user opted not to use a passphrase NULL will be stored there. The
+ user needs to free the returned string. In case of an error and
+ error code is returned and NULL stored at R_PASSPHRASE. */
+gpg_error_t
+agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
+ char **r_passphrase)
+{
+ gpg_error_t err;
+ const char *text1 = prompt;
+ const char *text2 = L_("Please re-enter this passphrase");
+ char *initial_errtext = NULL;
+ struct pin_entry_info_s *pi, *pi2;
+
+ *r_passphrase = NULL;
+
+ if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+ {
+ size_t size;
+ unsigned char *buffer;
+
+ err = pinentry_loopback (ctrl, "NEW_PASSPHRASE", &buffer, &size,
+ MAX_PASSPHRASE_LEN);
+ if (!err)
+ {
+ if (size)
+ {
+ buffer[size] = 0;
+ *r_passphrase = buffer;
+ }
+ else
+ *r_passphrase = NULL;
+ }
+ return err;
+ }
+
+ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
+ if (!pi2)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (pi);
+ return err;
+ }
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi->max_tries = 3;
+ pi->with_qualitybar = 0;
+ pi->with_repeat = 1;
+ pi2->max_length = MAX_PASSPHRASE_LEN + 1;
+ pi2->max_tries = 3;
+ pi2->check_cb = reenter_compare_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ next_try:
+ err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0);
+ xfree (initial_errtext);
+ initial_errtext = NULL;
+ if (!err)
+ {
+ if (check_passphrase_constraints (ctrl, pi->pin, 0, &initial_errtext))
+ {
+ pi->failed_tries = 0;
+ pi2->failed_tries = 0;
+ goto next_try;
+ }
+ /* Unless the passphrase is empty or the pinentry told us that
+ it already did the repetition check, ask to confirm it. */
+ if (*pi->pin && !pi->repeat_okay)
+ {
+ err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0);
+ if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
+ { /* The re-entered one did not match and the user did not
+ hit cancel. */
+ initial_errtext = xtrystrdup (L_("does not match - try again"));
+ if (initial_errtext)
+ goto next_try;
+ err = gpg_error_from_syserror ();
+ }
+ }
+ }
+
+ if (!err && *pi->pin)
+ {
+ /* User wants a passphrase. */
+ *r_passphrase = xtrystrdup (pi->pin);
+ if (!*r_passphrase)
+ err = gpg_error_from_syserror ();
+ }
+
+ xfree (initial_errtext);
+ xfree (pi2);
+ xfree (pi);
+ return err;
+}
+
+
+
+/* Generate a new keypair according to the parameters given in
+ KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase
+ using the cache nonce. If NO_PROTECTION is true the key will not
+ be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that
+ passphrase will be used for the new key. If TIMESTAMP is not zero
+ it will be recorded as creation date of the key (unless extended
+ format is disabled) . */
+int
+agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp,
+ const char *keyparam, size_t keyparamlen, int no_protection,
+ const char *override_passphrase, int preset, membuf_t *outbuf)
+{
+ gcry_sexp_t s_keyparam, s_key, s_private, s_public;
+ char *passphrase_buffer = NULL;
+ const char *passphrase;
+ int rc;
+ size_t len;
+ char *buf;
+
+ rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen);
+ if (rc)
+ {
+ log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc));
+ return gpg_error (GPG_ERR_INV_DATA);
+ }
+
+ /* Get the passphrase now, cause key generation may take a while. */
+ if (override_passphrase)
+ passphrase = override_passphrase;
+ else if (no_protection || !cache_nonce)
+ passphrase = NULL;
+ else
+ {
+ passphrase_buffer = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
+ passphrase = passphrase_buffer;
+ }
+
+ if (passphrase || no_protection)
+ ;
+ else
+ {
+ rc = agent_ask_new_passphrase (ctrl,
+ L_("Please enter the passphrase to%0A"
+ "protect your new key"),
+ &passphrase_buffer);
+ if (rc)
+ return rc;
+ passphrase = passphrase_buffer;
+ }
+
+ rc = gcry_pk_genkey (&s_key, s_keyparam );
+ gcry_sexp_release (s_keyparam);
+ if (rc)
+ {
+ log_error ("key generation failed: %s\n", gpg_strerror (rc));
+ xfree (passphrase_buffer);
+ return rc;
+ }
+
+ /* break out the parts */
+ s_private = gcry_sexp_find_token (s_key, "private-key", 0);
+ if (!s_private)
+ {
+ log_error ("key generation failed: invalid return value\n");
+ gcry_sexp_release (s_key);
+ xfree (passphrase_buffer);
+ return gpg_error (GPG_ERR_INV_DATA);
+ }
+ s_public = gcry_sexp_find_token (s_key, "public-key", 0);
+ if (!s_public)
+ {
+ log_error ("key generation failed: invalid return value\n");
+ gcry_sexp_release (s_private);
+ gcry_sexp_release (s_key);
+ xfree (passphrase_buffer);
+ return gpg_error (GPG_ERR_INV_DATA);
+ }
+ gcry_sexp_release (s_key); s_key = NULL;
+
+ /* store the secret key */
+ if (DBG_CRYPTO)
+ log_debug ("storing private key\n");
+ rc = store_key (s_private, passphrase, 0, ctrl->s2k_count, timestamp);
+ if (!rc)
+ {
+ if (!cache_nonce)
+ {
+ char tmpbuf[12];
+ gcry_create_nonce (tmpbuf, 12);
+ cache_nonce = bin2hex (tmpbuf, 12, NULL);
+ }
+ if (cache_nonce
+ && !no_protection
+ && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
+ passphrase, ctrl->cache_ttl_opt_preset))
+ agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL);
+ if (preset && !no_protection)
+ {
+ unsigned char grip[20];
+ char hexgrip[40+1];
+ if (gcry_pk_get_keygrip (s_private, grip))
+ {
+ bin2hex(grip, 20, hexgrip);
+ rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase,
+ ctrl->cache_ttl_opt_preset);
+ }
+ }
+ }
+ xfree (passphrase_buffer);
+ passphrase_buffer = NULL;
+ passphrase = NULL;
+ gcry_sexp_release (s_private);
+ if (rc)
+ {
+ gcry_sexp_release (s_public);
+ return rc;
+ }
+
+ /* return the public key */
+ if (DBG_CRYPTO)
+ log_debug ("returning public key\n");
+ len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xtrymalloc (len);
+ if (!buf)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ gcry_sexp_release (s_private);
+ gcry_sexp_release (s_public);
+ return tmperr;
+ }
+ len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ put_membuf (outbuf, buf, len);
+ gcry_sexp_release (s_public);
+ xfree (buf);
+
+ return 0;
+}
+
+
+
+/* Apply a new passphrase to the key S_SKEY and store it. If
+ PASSPHRASE_ADDR and *PASSPHRASE_ADDR are not NULL, use that
+ passphrase. If PASSPHRASE_ADDR is not NULL store a newly entered
+ passphrase at that address. */
+gpg_error_t
+agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
+ char **passphrase_addr)
+{
+ gpg_error_t err;
+
+ if (passphrase_addr && *passphrase_addr)
+ {
+ /* Take an empty string as request not to protect the key. */
+ err = store_key (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1,
+ ctrl->s2k_count, 0);
+ }
+ else
+ {
+ char *pass = NULL;
+
+ if (passphrase_addr)
+ {
+ xfree (*passphrase_addr);
+ *passphrase_addr = NULL;
+ }
+ err = agent_ask_new_passphrase (ctrl,
+ L_("Please enter the new passphrase"),
+ &pass);
+ if (!err)
+ err = store_key (s_skey, pass, 1, ctrl->s2k_count, 0);
+ if (!err && passphrase_addr)
+ *passphrase_addr = pass;
+ else
+ xfree (pass);
+ }
+
+ return err;
+}
diff --git a/agent/gpg-agent-w32info.rc b/agent/gpg-agent-w32info.rc
new file mode 100644
index 0000000..a0311b2
--- /dev/null
+++ b/agent/gpg-agent-w32info.rc
@@ -0,0 +1,52 @@
+/* gpg-agent-w32info.rc -*- c -*-
+ * Copyright (C) 2013 g10 Code GmbH
+ *
+ * This file is free software; as a special exception the author gives
+ * unlimited permission to copy and/or distribute it, with or without
+ * modifications, as long as this notice is preserved.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "afxres.h"
+#include "../common/w32info-rc.h"
+
+1 ICON "../common/gnupg.ico"
+
+1 VERSIONINFO
+ FILEVERSION W32INFO_VI_FILEVERSION
+ PRODUCTVERSION W32INFO_VI_PRODUCTVERSION
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/
+#else
+ FILEFLAGS 0x00L
+#endif
+ FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */
+ FILETYPE 0x1L /* VFT_APP (0x1) */
+ FILESUBTYPE 0x0L /* VFT2_UNKNOWN */
+ BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" /* US English (0409), Unicode (04b0) */
+ BEGIN
+ VALUE "FileDescription", L"GnuPG\x2019s private key daemon\0"
+ VALUE "InternalName", "gpg-agent\0"
+ VALUE "OriginalFilename", "gpg-agent.exe\0"
+ VALUE "ProductName", W32INFO_PRODUCTNAME
+ VALUE "ProductVersion", W32INFO_PRODUCTVERSION
+ VALUE "CompanyName", W32INFO_COMPANYNAME
+ VALUE "FileVersion", W32INFO_FILEVERSION
+ VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT
+ VALUE "Comments", W32INFO_COMMENTS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 0x4b0
+ END
+ END
+
+1 RT_MANIFEST "gpg-agent.w32-manifest"
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
new file mode 100644
index 0000000..53b86dd
--- /dev/null
+++ b/agent/gpg-agent.c
@@ -0,0 +1,3266 @@
+/* gpg-agent.c - The GnuPG Agent
+ * Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ * Copyright (C) 2000-2019 Werner Koch
+ * Copyright (C) 2015-2020 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#ifdef HAVE_W32_SYSTEM
+# ifndef WINVER
+# define WINVER 0x0500 /* Same as in common/sysutils.c */
+# endif
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <aclapi.h>
+# include <sddl.h>
+#else /*!HAVE_W32_SYSTEM*/
+# include <sys/socket.h>
+# include <sys/un.h>
+#endif /*!HAVE_W32_SYSTEM*/
+#include <unistd.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <npth.h>
+
+#define INCLUDED_BY_MAIN_MODULE 1
+#define GNUPG_COMMON_NEED_AFLOCAL
+#include "agent.h"
+#include <assuan.h> /* Malloc hooks and socket wrappers. */
+
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/gc-opt-flags.h"
+#include "../common/exechelp.h"
+#include "../common/asshelp.h"
+#include "../common/init.h"
+
+
+enum cmd_and_opt_values
+{ aNull = 0,
+ oCsh = 'c',
+ oQuiet = 'q',
+ oSh = 's',
+ oVerbose = 'v',
+
+ oNoVerbose = 500,
+ aGPGConfList,
+ aGPGConfTest,
+ aUseStandardSocketP,
+ oOptions,
+ oDebug,
+ oDebugAll,
+ oDebugLevel,
+ oDebugWait,
+ oDebugQuickRandom,
+ oDebugPinentry,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oGrab,
+ oNoGrab,
+ oLogFile,
+ oServer,
+ oDaemon,
+ oSupervised,
+ oBatch,
+
+ oPinentryProgram,
+ oPinentryTouchFile,
+ oPinentryInvisibleChar,
+ oPinentryTimeout,
+ oPinentryFormattedPassphrase,
+ oDisplay,
+ oTTYname,
+ oTTYtype,
+ oLCctype,
+ oLCmessages,
+ oXauthority,
+ oScdaemonProgram,
+ oDefCacheTTL,
+ oDefCacheTTLSSH,
+ oMaxCacheTTL,
+ oMaxCacheTTLSSH,
+ oEnforcePassphraseConstraints,
+ oMinPassphraseLen,
+ oMinPassphraseNonalpha,
+ oCheckPassphrasePattern,
+ oCheckSymPassphrasePattern,
+ oMaxPassphraseDays,
+ oEnablePassphraseHistory,
+ oDisableExtendedKeyFormat,
+ oEnableExtendedKeyFormat,
+ oStealSocket,
+ oUseStandardSocket,
+ oNoUseStandardSocket,
+ oExtraSocket,
+ oBrowserSocket,
+ oFakedSystemTime,
+
+ oIgnoreCacheForSigning,
+ oAllowMarkTrusted,
+ oNoAllowMarkTrusted,
+ oNoUserTrustlist,
+ oSysTrustlistName,
+ oAllowPresetPassphrase,
+ oAllowLoopbackPinentry,
+ oNoAllowLoopbackPinentry,
+ oNoAllowExternalCache,
+ oAllowEmacsPinentry,
+ oKeepTTY,
+ oKeepDISPLAY,
+ oSSHSupport,
+ oSSHFingerprintDigest,
+ oPuttySupport,
+ oDisableScdaemon,
+ oDisableCheckOwnSocket,
+ oS2KCount,
+ oS2KCalibration,
+ oAutoExpandSecmem,
+ oListenBacklog,
+
+ oWriteEnvFile,
+
+ oNoop
+};
+
+
+#ifndef ENAMETOOLONG
+# define ENAMETOOLONG EINVAL
+#endif
+
+static ARGPARSE_OPTS opts[] = {
+
+ ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
+ ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
+ ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
+
+
+ ARGPARSE_header (NULL, N_("Options used for startup")),
+
+ ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
+ ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
+#ifndef HAVE_W32_SYSTEM
+ ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")),
+#endif
+ ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
+ ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
+ ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
+ ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
+ ARGPARSE_s_s (oDisplay, "display", "@"),
+ ARGPARSE_s_s (oTTYname, "ttyname", "@"),
+ ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
+ ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
+ ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
+ ARGPARSE_s_s (oXauthority, "xauthority", "@"),
+ ARGPARSE_s_s (oHomedir, "homedir", "@"),
+ ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
+ ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
+
+
+ ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
+ ARGPARSE_s_s (oDebug, "debug", "@"),
+ ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
+ ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
+ ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
+ ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
+ ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
+ ARGPARSE_s_s (oLogFile, "log-file",
+ /* */ N_("|FILE|write server mode logs to FILE")),
+
+
+ ARGPARSE_header ("Configuration",
+ N_("Options controlling the configuration")),
+
+ ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
+ /* */ N_("do not use the SCdaemon") ),
+ ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
+ /* */ N_("|PGM|use PGM as the SCdaemon program") ),
+ ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
+
+ ARGPARSE_s_s (oExtraSocket, "extra-socket",
+ /* */ N_("|NAME|accept some commands via NAME")),
+
+ ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
+ ARGPARSE_s_n (oKeepTTY, "keep-tty",
+ /* */ N_("ignore requests to change the TTY")),
+ ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
+ /* */ N_("ignore requests to change the X display")),
+ ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")),
+ ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest",
+ N_("|ALGO|use ALGO to show ssh fingerprints")),
+ ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
+#ifdef HAVE_W32_SYSTEM
+ /* */ N_("enable putty support")
+#else
+ /* */ "@"
+#endif
+ ),
+ ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"),
+ ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"),
+ ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
+ ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"),
+ ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
+
+
+ ARGPARSE_header ("Security", N_("Options controlling the security")),
+
+ ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl",
+ N_("|N|expire cached PINs after N seconds")),
+ ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh",
+ /* */ N_("|N|expire SSH keys after N seconds")),
+ ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl",
+ /* */ N_("|N|set maximum PIN cache lifetime to N seconds")),
+ ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh",
+ /* */ N_("|N|set maximum SSH key lifetime to N seconds")),
+ ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
+ /* */ N_("do not use the PIN cache when signing")),
+ ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache",
+ /* */ N_("disallow the use of an external password cache")),
+ ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
+ /* */ N_("disallow clients to mark keys as \"trusted\"")),
+ ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"),
+ ARGPARSE_s_n (oNoUserTrustlist, "no-user-trustlist", "@"),
+ ARGPARSE_s_s (oSysTrustlistName, "sys-trustlist-name", "@"),
+ ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
+ /* */ N_("allow presetting passphrase")),
+ ARGPARSE_s_u (oS2KCount, "s2k-count", "@"),
+ ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"),
+
+ ARGPARSE_header ("Passphrase policy",
+ N_("Options enforcing a passphrase policy")),
+
+ ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
+ N_("do not allow bypassing the passphrase policy")),
+ ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len",
+ N_("|N|set minimal required length for new passphrases to N")),
+ ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha",
+ N_("|N|require at least N non-alpha"
+ " characters for a new passphrase")),
+ ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern",
+ N_("|FILE|check new passphrases against pattern in FILE")),
+ ARGPARSE_s_s (oCheckSymPassphrasePattern, "check-sym-passphrase-pattern",
+ "@"),
+ ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days",
+ N_("|N|expire the passphrase after N days")),
+ ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history",
+ N_("do not allow the reuse of old passphrases")),
+
+
+ ARGPARSE_header ("Pinentry", N_("Options controlling the PIN-Entry")),
+
+ ARGPARSE_s_n (oBatch, "batch", N_("never use the PIN-entry")),
+ ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry",
+ N_("disallow caller to override the pinentry")),
+ ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"),
+ ARGPARSE_s_n (oGrab, "grab", N_("let PIN-Entry grab keyboard and mouse")),
+ ARGPARSE_s_n (oNoGrab, "no-grab", "@"),
+ ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
+ /* */ N_("|PGM|use PGM as the PIN-Entry program")),
+ ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
+ ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
+ ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout",
+ N_("|N|set the Pinentry timeout to N seconds")),
+ ARGPARSE_s_n (oPinentryFormattedPassphrase, "pinentry-formatted-passphrase",
+ "@"),
+ ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry",
+ N_("allow passphrase to be prompted through Emacs")),
+
+ /* Dummy options for backward compatibility. */
+ ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
+ ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
+ ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
+
+ /* Dummy options. */
+
+
+ ARGPARSE_end () /* End of list */
+};
+
+
+/* The list of supported debug flags. */
+static struct debug_flags_s debug_flags [] =
+ {
+ { DBG_MPI_VALUE , "mpi" },
+ { DBG_CRYPTO_VALUE , "crypto" },
+ { DBG_MEMORY_VALUE , "memory" },
+ { DBG_CACHE_VALUE , "cache" },
+ { DBG_MEMSTAT_VALUE, "memstat" },
+ { DBG_HASHING_VALUE, "hashing" },
+ { DBG_IPC_VALUE , "ipc" },
+ { 77, NULL } /* 77 := Do not exit on "help" or "?". */
+ };
+
+
+
+#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
+#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
+#define MAX_CACHE_TTL (120*60) /* 2 hours */
+#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
+#define MIN_PASSPHRASE_LEN (8)
+#define MIN_PASSPHRASE_NONALPHA (1)
+#define MAX_PASSPHRASE_DAYS (0)
+
+/* The timer tick used for housekeeping stuff. Note that on Windows
+ * we use a SetWaitableTimer seems to signal earlier than about 2
+ * seconds. Thus we use 4 seconds on all platforms except for
+ * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
+ * our own socket in standard socket mode. If that value is 0 we
+ * don't check at all. All values are in seconds. */
+#if defined(HAVE_W32CE_SYSTEM)
+# define TIMERTICK_INTERVAL (60)
+# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
+#else
+# define TIMERTICK_INTERVAL (4)
+# define CHECK_OWN_SOCKET_INTERVAL (60)
+#endif
+
+
+/* Flag indicating that the ssh-agent subsystem has been enabled. */
+static int ssh_support;
+
+#ifdef HAVE_W32_SYSTEM
+/* Flag indicating that support for Putty has been enabled. */
+static int putty_support;
+/* A magic value used with WM_COPYDATA. */
+#define PUTTY_IPC_MAGIC 0x804e50ba
+/* To avoid surprises we limit the size of the mapped IPC file to this
+ value. Putty currently (0.62) uses 8k, thus 16k should be enough
+ for the foreseeable future. */
+#define PUTTY_IPC_MAXLEN 16384
+#endif /*HAVE_W32_SYSTEM*/
+
+/* The list of open file descriptors at startup. Note that this list
+ * has been allocated using the standard malloc. */
+#ifndef HAVE_W32_SYSTEM
+static int *startup_fd_list;
+#endif
+
+/* The signal mask at startup and a flag telling whether it is valid. */
+#ifdef HAVE_SIGPROCMASK
+static sigset_t startup_signal_mask;
+static int startup_signal_mask_valid;
+#endif
+
+/* Flag to indicate that a shutdown was requested. */
+static int shutdown_pending;
+
+/* Counter for the currently running own socket checks. */
+static int check_own_socket_running;
+
+/* Flags to indicate that check_own_socket shall not be called. */
+static int disable_check_own_socket;
+
+/* Flag indicating that we are in supervised mode. */
+static int is_supervised;
+
+/* Flag indicating to start the daemon even if one already runs. */
+static int steal_socket;
+
+/* Flag to inhibit socket removal in cleanup. */
+static int inhibit_socket_removal;
+
+/* It is possible that we are currently running under setuid permissions */
+static int maybe_setuid = 1;
+
+/* Name of the communication socket used for native gpg-agent
+ requests. The second variable is either NULL or a malloced string
+ with the real socket name in case it has been redirected. */
+static char *socket_name;
+static char *redir_socket_name;
+
+/* Name of the optional extra socket used for native gpg-agent requests. */
+static char *socket_name_extra;
+static char *redir_socket_name_extra;
+
+/* Name of the optional browser socket used for native gpg-agent requests. */
+static char *socket_name_browser;
+static char *redir_socket_name_browser;
+
+/* Name of the communication socket used for ssh-agent protocol. */
+static char *socket_name_ssh;
+static char *redir_socket_name_ssh;
+
+/* We need to keep track of the server's nonces (these are dummies for
+ POSIX systems). */
+static assuan_sock_nonce_t socket_nonce;
+static assuan_sock_nonce_t socket_nonce_extra;
+static assuan_sock_nonce_t socket_nonce_browser;
+static assuan_sock_nonce_t socket_nonce_ssh;
+
+/* Value for the listen() backlog argument. We use the same value for
+ * all sockets - 64 is on current Linux half of the default maximum.
+ * Let's try this as default. Change at runtime with --listen-backlog. */
+static int listen_backlog = 64;
+
+/* Default values for options passed to the pinentry. */
+static char *default_display;
+static char *default_ttyname;
+static char *default_ttytype;
+static char *default_lc_ctype;
+static char *default_lc_messages;
+static char *default_xauthority;
+
+/* Name of a config file which was last read on startup or, if missing,
+ * the name of the standard config file. Any value here enables the
+ * rereading of the standard config files on SIGHUP. */
+static char *config_filename;
+
+/* Helper to implement --debug-level */
+static const char *debug_level;
+
+/* Keep track of the current log file so that we can avoid updating
+ the log file after a SIGHUP if it didn't changed. Malloced. */
+static char *current_logfile;
+
+/* The handle_tick() function may test whether a parent is still
+ * running. We record the PID of the parent here or -1 if it should
+ * be watched. */
+static pid_t parent_pid = (pid_t)(-1);
+
+/* This flag is true if the inotify mechanism for detecting the
+ * removal of the homedir is active. This flag is used to disable the
+ * alternative but portable stat based check. */
+static int have_homedir_inotify;
+
+/* Depending on how gpg-agent was started, the homedir inotify watch
+ * may not be reliable. This flag is set if we assume that inotify
+ * works reliable. */
+static int reliable_homedir_inotify;
+
+/* Number of active connections. */
+static int active_connections;
+
+/* This object is used to dispatch progress messages from Libgcrypt to
+ * the right thread. Given that we will have at max only a few dozen
+ * connections at a time, using a linked list is the easiest way to
+ * handle this. */
+struct progress_dispatch_s
+{
+ struct progress_dispatch_s *next;
+ /* The control object of the connection. If this is NULL no
+ * connection is associated with this item and it is free for reuse
+ * by new connections. */
+ ctrl_t ctrl;
+
+ /* The thread id of (npth_self) of the connection. */
+ npth_t tid;
+
+ /* The callback set by the connection. This is similar to the
+ * Libgcrypt callback but with the control object passed as the
+ * first argument. */
+ void (*cb)(ctrl_t ctrl,
+ const char *what, int printchar,
+ int current, int total);
+};
+struct progress_dispatch_s *progress_dispatch_list;
+
+
+
+
+/*
+ Local prototypes.
+ */
+
+static char *create_socket_name (char *standard_name, int with_homedir);
+static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
+ char **r_redir_name,
+ assuan_sock_nonce_t *nonce);
+static void create_directories (void);
+
+static void agent_libgcrypt_progress_cb (void *data, const char *what,
+ int printchar,
+ int current, int total);
+static void agent_init_default_ctrl (ctrl_t ctrl);
+static void agent_deinit_default_ctrl (ctrl_t ctrl);
+
+static void handle_connections (gnupg_fd_t listen_fd,
+ gnupg_fd_t listen_fd_extra,
+ gnupg_fd_t listen_fd_browser,
+ gnupg_fd_t listen_fd_ssh);
+static void check_own_socket (void);
+static int check_for_running_agent (int silent);
+
+/* Pth wrapper function definitions. */
+ASSUAN_SYSTEM_NPTH_IMPL;
+
+
+/*
+ Functions.
+ */
+
+/* Allocate a string describing a library version by calling a GETFNC.
+ This function is expected to be called only once. GETFNC is
+ expected to have a semantic like gcry_check_version (). */
+static char *
+make_libversion (const char *libname, const char *(*getfnc)(const char*))
+{
+ const char *s;
+ char *result;
+
+ if (maybe_setuid)
+ {
+ gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
+ maybe_setuid = 0;
+ }
+ s = getfnc (NULL);
+ result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
+ strcpy (stpcpy (stpcpy (result, libname), " "), s);
+ return result;
+}
+
+/* Return strings describing this program. The case values are
+ described in common/argparse.c:strusage. The values here override
+ the default values given by strusage. */
+static const char *
+my_strusage (int level)
+{
+ static char *ver_gcry;
+ const char *p;
+
+ switch (level)
+ {
+ case 9: p = "GPL-3.0-or-later"; break;
+ case 11: p = "@GPG_AGENT@ (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
+ reporting address. This is so that we can change the
+ reporting address without breaking the translations. */
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+
+ case 20:
+ if (!ver_gcry)
+ ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
+ p = ver_gcry;
+ break;
+
+ case 1:
+ case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)");
+ break;
+ case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n"
+ "Secret key management for @GNUPG@\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL
+ only the active debug flags are propagated to the subsystems. With
+ DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding
+ all flags already set. Note that we don't fail here, because it is
+ important to keep gpg-agent running even after re-reading the
+ options due to a SIGHUP. */
+static void
+set_debug (void)
+{
+ int numok = (debug_level && digitp (debug_level));
+ int numlvl = numok? atoi (debug_level) : 0;
+
+ if (!debug_level)
+ ;
+ else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
+ opt.debug = 0;
+ else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
+ opt.debug = DBG_IPC_VALUE;
+ else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
+ opt.debug = DBG_IPC_VALUE;
+ else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
+ opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
+ else if (!strcmp (debug_level, "guru") || numok)
+ {
+ opt.debug = ~0;
+ /* Unless the "guru" string has been used we don't want to allow
+ hashing debugging. The rationale is that people tend to
+ select the highest debug value and would then clutter their
+ disk with debug files which may reveal confidential data. */
+ if (numok)
+ opt.debug &= ~(DBG_HASHING_VALUE);
+ }
+ else
+ {
+ log_error (_("invalid debug-level '%s' given\n"), debug_level);
+ opt.debug = 0; /* Reset debugging, so that prior debug
+ statements won't have an undesired effect. */
+ }
+
+ if (opt.debug && !opt.verbose)
+ opt.verbose = 1;
+ if (opt.debug && opt.quiet)
+ opt.quiet = 0;
+
+ if (opt.debug & DBG_MPI_VALUE)
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
+ if (opt.debug & DBG_CRYPTO_VALUE )
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
+ gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
+
+ if (opt.debug)
+ parse_debug_flag (NULL, &opt.debug, debug_flags);
+}
+
+
+/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is
+ the corresponding real name if the socket has been redirected. */
+static void
+remove_socket (char *name, char *redir_name)
+{
+ if (name && *name)
+ {
+ if (redir_name)
+ name = redir_name;
+
+ gnupg_remove (name);
+ *name = 0;
+ }
+}
+
+
+/* Discover which inherited file descriptors correspond to which
+ * services/sockets offered by gpg-agent, using the LISTEN_FDS and
+ * LISTEN_FDNAMES convention. The understood labels are "ssh",
+ * "extra", and "browser". "std" or other labels will be interpreted
+ * as the standard socket.
+ *
+ * This function is designed to log errors when the expected file
+ * descriptors don't make sense, but to do its best to continue to
+ * work even in the face of minor misconfigurations.
+ *
+ * For more information on the LISTEN_FDS convention, see
+ * sd_listen_fds(3) on certain Linux distributions.
+ */
+#ifndef HAVE_W32_SYSTEM
+static void
+map_supervised_sockets (gnupg_fd_t *r_fd,
+ gnupg_fd_t *r_fd_extra,
+ gnupg_fd_t *r_fd_browser,
+ gnupg_fd_t *r_fd_ssh)
+{
+ struct {
+ const char *label;
+ int **fdaddr;
+ char **nameaddr;
+ } tbl[] = {
+ { "ssh", &r_fd_ssh, &socket_name_ssh },
+ { "browser", &r_fd_browser, &socket_name_browser },
+ { "extra", &r_fd_extra, &socket_name_extra },
+ { "std", &r_fd, &socket_name } /* (Must be the last item.) */
+ };
+ const char *envvar;
+ char **fdnames;
+ int nfdnames;
+ int fd_count;
+
+ *r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1;
+
+ /* Print a warning if LISTEN_PID does not match outr pid. */
+ envvar = getenv ("LISTEN_PID");
+ if (!envvar)
+ log_error ("no LISTEN_PID environment variable found in "
+ "--supervised mode (ignoring)\n");
+ else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ())
+ log_error ("environment variable LISTEN_PID (%lu) does not match"
+ " our pid (%lu) in --supervised mode (ignoring)\n",
+ (unsigned long)strtoul (envvar, NULL, 10),
+ (unsigned long)getpid ());
+
+ /* Parse LISTEN_FDNAMES into the array FDNAMES. */
+ envvar = getenv ("LISTEN_FDNAMES");
+ if (envvar)
+ {
+ fdnames = strtokenize (envvar, ":");
+ if (!fdnames)
+ {
+ log_error ("strtokenize failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ agent_exit (1);
+ }
+ for (nfdnames=0; fdnames[nfdnames]; nfdnames++)
+ ;
+ }
+ else
+ {
+ fdnames = NULL;
+ nfdnames = 0;
+ }
+
+ /* Parse LISTEN_FDS into fd_count or provide a replacement. */
+ envvar = getenv ("LISTEN_FDS");
+ if (envvar)
+ fd_count = atoi (envvar);
+ else if (fdnames)
+ {
+ log_error ("no LISTEN_FDS environment variable found in --supervised"
+ " mode (relying on LISTEN_FDNAMES instead)\n");
+ fd_count = nfdnames;
+ }
+ else
+ {
+ log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
+ "found in --supervised mode"
+ " (assuming 1 active descriptor)\n");
+ fd_count = 1;
+ }
+
+ if (fd_count < 1)
+ {
+ log_error ("--supervised mode expects at least one file descriptor"
+ " (was told %d, carrying on as though it were 1)\n",
+ fd_count);
+ fd_count = 1;
+ }
+
+ /* Assign the descriptors to the return values. */
+ if (!fdnames)
+ {
+ struct stat statbuf;
+
+ if (fd_count != 1)
+ log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1"
+ " in --supervised mode."
+ " (ignoring all sockets but the first one)\n",
+ fd_count);
+ if (fstat (3, &statbuf) == -1 && errno ==EBADF)
+ log_fatal ("file descriptor 3 must be valid in --supervised mode"
+ " if LISTEN_FDNAMES is not set\n");
+ *r_fd = 3;
+ socket_name = gnupg_get_socket_name (3);
+ }
+ else if (fd_count != nfdnames)
+ {
+ log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
+ "LISTEN_FDS (%d) in --supervised mode\n",
+ nfdnames, fd_count);
+ }
+ else
+ {
+ int i, j, fd;
+ char *name;
+
+ for (i = 0; i < nfdnames; i++)
+ {
+ for (j = 0; j < DIM (tbl); j++)
+ {
+ if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1)
+ {
+ fd = 3 + i;
+ if (**tbl[j].fdaddr == -1)
+ {
+ name = gnupg_get_socket_name (fd);
+ if (name)
+ {
+ **tbl[j].fdaddr = fd;
+ *tbl[j].nameaddr = name;
+ log_info ("using fd %d for %s socket (%s)\n",
+ fd, tbl[j].label, name);
+ }
+ else
+ {
+ log_error ("cannot listen on fd %d for %s socket\n",
+ fd, tbl[j].label);
+ close (fd);
+ }
+ }
+ else
+ {
+ log_error ("cannot listen on more than one %s socket\n",
+ tbl[j].label);
+ close (fd);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ xfree (fdnames);
+}
+#endif /*!HAVE_W32_SYSTEM*/
+
+
+/* Cleanup code for this program. This is either called has an atexit
+ handler or directly. */
+static void
+cleanup (void)
+{
+ static int done;
+
+ if (done)
+ return;
+ done = 1;
+ deinitialize_module_cache ();
+ if (!is_supervised && !inhibit_socket_removal)
+ {
+ remove_socket (socket_name, redir_socket_name);
+ if (opt.extra_socket > 1)
+ remove_socket (socket_name_extra, redir_socket_name_extra);
+ if (opt.browser_socket > 1)
+ remove_socket (socket_name_browser, redir_socket_name_browser);
+ remove_socket (socket_name_ssh, redir_socket_name_ssh);
+ }
+}
+
+
+
+/* Handle options which are allowed to be reset after program start.
+ Return true when the current option in PARGS could be handled and
+ false if not. As a special feature, passing a value of NULL for
+ PARGS, resets the options to the default. REREAD should be set
+ true if it is not the initial option parsing. */
+static int
+parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
+{
+ int i;
+
+ if (!pargs)
+ { /* reset mode */
+ opt.quiet = 0;
+ opt.verbose = 0;
+ opt.debug = 0;
+ opt.no_grab = 1;
+ opt.debug_pinentry = 0;
+ opt.pinentry_program = NULL;
+ opt.pinentry_touch_file = NULL;
+ xfree (opt.pinentry_invisible_char);
+ opt.pinentry_invisible_char = NULL;
+ opt.pinentry_timeout = 0;
+ opt.pinentry_formatted_passphrase = 0;
+ opt.scdaemon_program = NULL;
+ opt.def_cache_ttl = DEFAULT_CACHE_TTL;
+ opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
+ opt.max_cache_ttl = MAX_CACHE_TTL;
+ opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
+ opt.enforce_passphrase_constraints = 0;
+ opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
+ opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
+ opt.check_passphrase_pattern = NULL;
+ opt.check_sym_passphrase_pattern = NULL;
+ opt.max_passphrase_days = MAX_PASSPHRASE_DAYS;
+ opt.enable_passphrase_history = 0;
+ opt.enable_extended_key_format = 1;
+ opt.ignore_cache_for_signing = 0;
+ opt.allow_mark_trusted = 1;
+ opt.sys_trustlist_name = NULL;
+ opt.allow_external_cache = 1;
+ opt.allow_loopback_pinentry = 1;
+ opt.allow_emacs_pinentry = 0;
+ opt.disable_scdaemon = 0;
+ disable_check_own_socket = 0;
+ /* Note: When changing the next line, change also gpgconf_list. */
+ opt.ssh_fingerprint_digest = GCRY_MD_MD5;
+ opt.s2k_count = 0;
+ set_s2k_calibration_time (0); /* Set to default. */
+ return 1;
+ }
+
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+
+ case oDebug:
+ parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
+ break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugLevel: debug_level = pargs->r.ret_str; break;
+ case oDebugPinentry: opt.debug_pinentry = 1; break;
+
+ case oLogFile:
+ if (!reread)
+ return 0; /* not handeld */
+ if (!current_logfile || !pargs->r.ret_str
+ || strcmp (current_logfile, pargs->r.ret_str))
+ {
+ log_set_file (pargs->r.ret_str);
+ xfree (current_logfile);
+ current_logfile = xtrystrdup (pargs->r.ret_str);
+ }
+ break;
+
+ case oNoGrab: opt.no_grab |= 1; break;
+ case oGrab: opt.no_grab |= 2; break;
+
+ case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
+ case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
+ case oPinentryInvisibleChar:
+ xfree (opt.pinentry_invisible_char);
+ opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
+ break;
+ case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break;
+ case oPinentryFormattedPassphrase:
+ opt.pinentry_formatted_passphrase = 1;
+ break;
+ case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
+ case oDisableScdaemon: opt.disable_scdaemon = 1; break;
+ case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
+
+ case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
+ case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break;
+ case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
+ case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
+
+ case oEnforcePassphraseConstraints:
+ opt.enforce_passphrase_constraints=1;
+ break;
+ case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
+ case oMinPassphraseNonalpha:
+ opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
+ break;
+ case oCheckPassphrasePattern:
+ opt.check_passphrase_pattern = pargs->r.ret_str;
+ break;
+ case oCheckSymPassphrasePattern:
+ opt.check_sym_passphrase_pattern = pargs->r.ret_str;
+ break;
+ case oMaxPassphraseDays:
+ opt.max_passphrase_days = pargs->r.ret_ulong;
+ break;
+ case oEnablePassphraseHistory:
+ opt.enable_passphrase_history = 1;
+ break;
+
+ case oEnableExtendedKeyFormat:
+ opt.enable_extended_key_format = 2;
+ break;
+ case oDisableExtendedKeyFormat:
+ if (opt.enable_extended_key_format != 2)
+ opt.enable_extended_key_format = 0;
+ break;
+
+ case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
+
+ case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
+ case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break;
+ case oNoUserTrustlist: opt.no_user_trustlist = 1; break;
+ case oSysTrustlistName: opt.sys_trustlist_name = pargs->r.ret_str; break;
+
+ case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
+
+ case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
+ case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break;
+
+ case oNoAllowExternalCache: opt.allow_external_cache = 0;
+ break;
+
+ case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
+ break;
+
+ case oSSHFingerprintDigest:
+ i = gcry_md_map_name (pargs->r.ret_str);
+ if (!i)
+ log_error (_("selected digest algorithm is invalid\n"));
+ else
+ opt.ssh_fingerprint_digest = i;
+ break;
+
+ case oS2KCount:
+ opt.s2k_count = pargs->r.ret_ulong;
+ break;
+
+ case oS2KCalibration:
+ set_s2k_calibration_time (pargs->r.ret_ulong);
+ break;
+
+ case oNoop: break;
+
+ default:
+ return 0; /* not handled */
+ }
+
+ return 1; /* handled */
+}
+
+
+/* Fixup some options after all have been processed. */
+static void
+finalize_rereadable_options (void)
+{
+ /* Hack to allow --grab to override --no-grab. */
+ if ((opt.no_grab & 2))
+ opt.no_grab = 0;
+
+ /* With --no-user-trustlist it does not make sense to allow the mark
+ * trusted feature. */
+ if (opt.no_user_trustlist)
+ opt.allow_mark_trusted = 0;
+}
+
+
+static void
+thread_init_once (void)
+{
+ static int npth_initialized = 0;
+
+ if (!npth_initialized)
+ {
+ npth_initialized++;
+ npth_init ();
+ }
+ gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
+ /* Now that we have set the syscall clamp we need to tell Libgcrypt
+ * that it should get them from libgpg-error. Note that Libgcrypt
+ * has already been initialized but at that point nPth was not
+ * initialized and thus Libgcrypt could not set its system call
+ * clamp. */
+ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
+}
+
+
+static void
+initialize_modules (void)
+{
+ thread_init_once ();
+ assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
+ initialize_module_cache ();
+ initialize_module_call_pinentry ();
+ initialize_module_call_scd ();
+ initialize_module_trustlist ();
+}
+
+
+/* The main entry point. */
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ char **orig_argv;
+ char *last_configname = NULL;
+ const char *configname = NULL;
+ int debug_argparser = 0;
+ const char *shell;
+ int pipe_server = 0;
+ int is_daemon = 0;
+ int nodetach = 0;
+ int csh_style = 0;
+ char *logfile = NULL;
+ int debug_wait = 0;
+ int gpgconf_list = 0;
+ gpg_error_t err;
+ struct assuan_malloc_hooks malloc_hooks;
+
+ early_system_init ();
+
+ /* Before we do anything else we save the list of currently open
+ file descriptors and the signal mask. This info is required to
+ do the exec call properly. We don't need it on Windows. */
+#ifndef HAVE_W32_SYSTEM
+ startup_fd_list = get_all_open_fds ();
+#endif /*!HAVE_W32_SYSTEM*/
+#ifdef HAVE_SIGPROCMASK
+ if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
+ startup_signal_mask_valid = 1;
+#endif /*HAVE_SIGPROCMASK*/
+
+ /* Set program name etc. */
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ /* Please note that we may running SUID(ROOT), so be very CAREFUL
+ when adding any stuff between here and the call to INIT_SECMEM()
+ somewhere after the option parsing */
+ log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+
+ malloc_hooks.malloc = gcry_malloc;
+ malloc_hooks.realloc = gcry_realloc;
+ malloc_hooks.free = gcry_free;
+ assuan_set_malloc_hooks (&malloc_hooks);
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ assuan_sock_init ();
+ assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
+ setup_libassuan_logging (&opt.debug, NULL);
+
+ setup_libgcrypt_logging ();
+ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+ gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL);
+
+ disable_core_dumps ();
+
+ /* Set default options. */
+ parse_rereadable_options (NULL, 0); /* Reset them to default values. */
+
+ shell = getenv ("SHELL");
+ if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
+ csh_style = 1;
+
+ /* Record some of the original environment strings. */
+ {
+ const char *s;
+ int idx;
+ static const char *names[] =
+ { "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
+
+ err = 0;
+ opt.startup_env = session_env_new ();
+ if (!opt.startup_env)
+ err = gpg_error_from_syserror ();
+ for (idx=0; !err && names[idx]; idx++)
+ {
+ s = getenv (names[idx]);
+ if (s)
+ err = session_env_setenv (opt.startup_env, names[idx], s);
+ }
+ if (!err)
+ {
+ s = gnupg_ttyname (0);
+ if (s)
+ err = session_env_setenv (opt.startup_env, "GPG_TTY", s);
+ }
+ if (err)
+ log_fatal ("error recording startup environment: %s\n",
+ gpg_strerror (err));
+
+ /* Fixme: Better use the locale function here. */
+ opt.startup_lc_ctype = getenv ("LC_CTYPE");
+ if (opt.startup_lc_ctype)
+ opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype);
+ opt.startup_lc_messages = getenv ("LC_MESSAGES");
+ if (opt.startup_lc_messages)
+ opt.startup_lc_messages = xstrdup (opt.startup_lc_messages);
+ }
+
+ /* Check whether we have a config file on the commandline */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
+ while (gnupg_argparse (NULL, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case oDebug:
+ case oDebugAll:
+ debug_argparser++;
+ break;
+
+ case oHomedir:
+ gnupg_set_homedir (pargs.r.ret_str);
+ break;
+
+ case oDebugQuickRandom:
+ gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+ break;
+ }
+ }
+ /* Reset the flags. */
+ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
+
+ /* Initialize the secure memory. */
+ gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
+ maybe_setuid = 0;
+
+ /*
+ * Now we are now working under our real uid
+ */
+
+ gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ());
+ gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ());
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ /* We are re-using the struct, thus the reset flag. We OR the
+ * flags so that the internal intialized flag won't be cleared. */
+ pargs.flags |= (ARGPARSE_FLAG_RESET
+ | ARGPARSE_FLAG_KEEP
+ | ARGPARSE_FLAG_SYS
+ | ARGPARSE_FLAG_USER);
+
+ while (gnupg_argparser (&pargs, opts, GPG_AGENT_NAME EXTSEP_S "conf"))
+ {
+ if (pargs.r_opt == ARGPARSE_CONFFILE)
+ {
+ if (debug_argparser)
+ log_info (_("reading options from '%s'\n"),
+ pargs.r_type? pargs.r.ret_str: "[cmdline]");
+ if (pargs.r_type)
+ {
+ xfree (last_configname);
+ last_configname = xstrdup (pargs.r.ret_str);
+ configname = last_configname;
+ }
+ else
+ configname = NULL;
+ continue;
+ }
+ if (parse_rereadable_options (&pargs, 0))
+ continue; /* Already handled */
+ switch (pargs.r_opt)
+ {
+ case aGPGConfList: gpgconf_list = 1; break;
+ case aGPGConfTest: gpgconf_list = 2; break;
+ case aUseStandardSocketP: gpgconf_list = 3; break;
+ case oBatch: opt.batch=1; break;
+
+ case oDebugWait: debug_wait = pargs.r.ret_int; break;
+
+ case oNoVerbose: opt.verbose = 0; break;
+ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
+ case oNoDetach: nodetach = 1; break;
+ case oLogFile: logfile = pargs.r.ret_str; break;
+ case oCsh: csh_style = 1; break;
+ case oSh: csh_style = 0; break;
+ case oServer: pipe_server = 1; break;
+ case oDaemon: is_daemon = 1; break;
+ case oStealSocket: steal_socket = 1; break;
+ case oSupervised: is_supervised = 1; break;
+
+ case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
+ case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
+ case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
+ case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
+ case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str);
+ break;
+ case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
+ break;
+
+ case oUseStandardSocket:
+ case oNoUseStandardSocket:
+ obsolete_option (configname, pargs.lineno, "use-standard-socket");
+ break;
+
+ case oFakedSystemTime:
+ {
+ time_t faked_time = isotime2epoch (pargs.r.ret_str);
+ if (faked_time == (time_t)(-1))
+ faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
+ gnupg_set_time (faked_time, 0);
+ }
+ break;
+
+ case oKeepTTY: opt.keep_tty = 1; break;
+ case oKeepDISPLAY: opt.keep_display = 1; break;
+
+ case oSSHSupport:
+ ssh_support = 1;
+ break;
+
+ case oPuttySupport:
+# ifdef HAVE_W32_SYSTEM
+ putty_support = 1;
+# endif
+ break;
+
+ case oExtraSocket:
+ opt.extra_socket = 1; /* (1 = points into argv) */
+ socket_name_extra = pargs.r.ret_str;
+ break;
+
+ case oBrowserSocket:
+ opt.browser_socket = 1; /* (1 = points into argv) */
+ socket_name_browser = pargs.r.ret_str;
+ break;
+
+ case oAutoExpandSecmem:
+ /* Try to enable this option. It will officially only be
+ * supported by Libgcrypt 1.9 but 1.8.2 already supports it
+ * on the quiet and thus we use the numeric value value. */
+ gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/,
+ (unsigned int)pargs.r.ret_ulong, 0);
+ break;
+
+ case oListenBacklog:
+ listen_backlog = pargs.r.ret_int;
+ break;
+
+ case oDebugQuickRandom:
+ /* Only used by the first stage command line parser. */
+ break;
+
+ case oWriteEnvFile:
+ obsolete_option (configname, pargs.lineno, "write-env-file");
+ break;
+
+ default:
+ if (configname)
+ pargs.err = ARGPARSE_PRINT_WARNING;
+ else
+ pargs.err = ARGPARSE_PRINT_ERROR;
+ break;
+ }
+ }
+
+ /* 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]);
+ }
+
+ gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */
+
+ if (!last_configname)
+ config_filename = make_filename (gnupg_homedir (),
+ GPG_AGENT_NAME EXTSEP_S "conf",
+ NULL);
+ else
+ {
+ config_filename = last_configname;
+ last_configname = NULL;
+ }
+
+ if (log_get_errorcount(0))
+ exit(2);
+
+ finalize_rereadable_options ();
+
+
+#ifdef ENABLE_NLS
+ /* gpg-agent usually does not output any messages because it runs in
+ the background. For log files it is acceptable to have messages
+ always encoded in utf-8. We switch here to utf-8, so that
+ commands like --help still give native messages. It is far
+ easier to switch only once instead of for every message and it
+ actually helps when more then one thread is active (avoids an
+ extra copy step). */
+ bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
+#endif
+
+ if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
+ {
+ /* We have been called without any command and thus we merely
+ check whether an agent is already running. We do this right
+ here so that we don't clobber a logfile with this check but
+ print the status directly to stderr. */
+ opt.debug = 0;
+ set_debug ();
+ check_for_running_agent (0);
+ agent_exit (0);
+ }
+
+ if (is_supervised)
+ ;
+ else if (!opt.extra_socket)
+ opt.extra_socket = 1;
+ else if (socket_name_extra
+ && (!strcmp (socket_name_extra, "none")
+ || !strcmp (socket_name_extra, "/dev/null")))
+ {
+ /* User requested not to create this socket. */
+ opt.extra_socket = 0;
+ socket_name_extra = NULL;
+ }
+
+ if (is_supervised)
+ ;
+ else if (!opt.browser_socket)
+ opt.browser_socket = 1;
+ else if (socket_name_browser
+ && (!strcmp (socket_name_browser, "none")
+ || !strcmp (socket_name_browser, "/dev/null")))
+ {
+ /* User requested not to create this socket. */
+ opt.browser_socket = 0;
+ socket_name_browser = NULL;
+ }
+
+ set_debug ();
+
+ if (atexit (cleanup))
+ {
+ log_error ("atexit failed\n");
+ cleanup ();
+ exit (1);
+ }
+
+ /* Try to create missing directories. */
+ if (!gpgconf_list)
+ create_directories ();
+
+ if (debug_wait && pipe_server)
+ {
+ thread_init_once ();
+ log_debug ("waiting for debugger - my pid is %u .....\n",
+ (unsigned int)getpid());
+ gnupg_sleep (debug_wait);
+ log_debug ("... okay\n");
+ }
+
+ if (gpgconf_list == 3)
+ {
+ /* We now use the standard socket always - return true for
+ backward compatibility. */
+ agent_exit (0);
+ }
+ else if (gpgconf_list == 2)
+ agent_exit (0);
+ else if (gpgconf_list)
+ {
+ es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
+ es_printf ("default-cache-ttl:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL );
+ es_printf ("default-cache-ttl-ssh:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL_SSH );
+ es_printf ("max-cache-ttl:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL );
+ es_printf ("max-cache-ttl-ssh:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL_SSH );
+ es_printf ("enforce-passphrase-constraints:%lu:\n",
+ GC_OPT_FLAG_NONE);
+ es_printf ("min-passphrase-len:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_LEN );
+ es_printf ("min-passphrase-nonalpha:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_NONALPHA);
+ es_printf ("max-passphrase-days:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT, MAX_PASSPHRASE_DAYS);
+ es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n",
+ GC_OPT_FLAG_DEFAULT, "md5");
+
+ agent_exit (0);
+ }
+
+ /* Now start with logging to a file if this is desired. */
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
+ | GPGRT_LOG_WITH_TIME
+ | GPGRT_LOG_WITH_PID));
+ current_logfile = xstrdup (logfile);
+ }
+
+ /* Make sure that we have a default ttyname. */
+ if (!default_ttyname && gnupg_ttyname (1))
+ default_ttyname = xstrdup (gnupg_ttyname (1));
+ if (!default_ttytype && getenv ("TERM"))
+ default_ttytype = xstrdup (getenv ("TERM"));
+
+
+ if (pipe_server)
+ {
+ /* This is the simple pipe based server */
+ ctrl_t ctrl;
+
+ initialize_modules ();
+
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (!ctrl)
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ agent_exit (1);
+ }
+ ctrl->session_env = session_env_new ();
+ if (!ctrl->session_env)
+ {
+ log_error ("error allocating session environment block: %s\n",
+ strerror (errno) );
+ xfree (ctrl);
+ agent_exit (1);
+ }
+ agent_init_default_ctrl (ctrl);
+ start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD);
+ agent_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ }
+ else if (is_supervised)
+ {
+#ifndef HAVE_W32_SYSTEM
+ gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh;
+
+ initialize_modules ();
+
+ /* when supervised and sending logs to stderr, the process
+ supervisor should handle log entry metadata (pid, name,
+ timestamp) */
+ if (!logfile)
+ log_set_prefix (NULL, 0);
+
+ log_info ("%s %s starting in supervised mode.\n",
+ strusage(11), strusage(13) );
+
+ /* See below in "regular server mode" on why we remove certain
+ * envvars. */
+ if (!opt.keep_display)
+ gnupg_unsetenv ("DISPLAY");
+ gnupg_unsetenv ("INSIDE_EMACS");
+
+ /* Virtually create the sockets. Note that we use -1 here
+ * because the whole thing works only on Unix. */
+ map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
+ if (fd == -1)
+ log_fatal ("no standard socket provided\n");
+
+#ifdef HAVE_SIGPROCMASK
+ if (startup_signal_mask_valid)
+ {
+ if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
+ log_error ("error restoring signal mask: %s\n",
+ strerror (errno));
+ }
+ else
+ log_info ("no saved signal mask\n");
+#endif /*HAVE_SIGPROCMASK*/
+
+ log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n",
+ fd, fd_extra, fd_browser, fd_ssh);
+ handle_connections (fd, fd_extra, fd_browser, fd_ssh);
+#endif /*!HAVE_W32_SYSTEM*/
+ }
+ else if (!is_daemon)
+ ; /* NOTREACHED */
+ else
+ { /* Regular server mode */
+ gnupg_fd_t fd;
+ gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
+ gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
+ gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
+#ifndef HAVE_W32_SYSTEM
+ pid_t pid;
+#endif
+
+ /* Remove the DISPLAY variable so that a pinentry does not
+ default to a specific display. There is still a default
+ display when gpg-agent was started using --display or a
+ client requested this using an OPTION command. Note, that we
+ don't do this when running in reverse daemon mode (i.e. when
+ exec the program given as arguments). */
+#ifndef HAVE_W32_SYSTEM
+ if (!opt.keep_display && !argc)
+ gnupg_unsetenv ("DISPLAY");
+#endif
+
+ /* Remove the INSIDE_EMACS variable so that a pinentry does not
+ always try to interact with Emacs. The variable is set when
+ a client requested this using an OPTION command. */
+ gnupg_unsetenv ("INSIDE_EMACS");
+
+ /* Create the sockets. */
+ socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
+ fd = create_server_socket (socket_name, 1, 0,
+ &redir_socket_name, &socket_nonce);
+
+ if (opt.extra_socket)
+ {
+ if (socket_name_extra)
+ socket_name_extra = create_socket_name (socket_name_extra, 0);
+ else
+ socket_name_extra = create_socket_name
+ /**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1);
+ opt.extra_socket = 2; /* Indicate that it has been malloced. */
+ fd_extra = create_server_socket (socket_name_extra, 0, 0,
+ &redir_socket_name_extra,
+ &socket_nonce_extra);
+ }
+
+ if (opt.browser_socket)
+ {
+ if (socket_name_browser)
+ socket_name_browser = create_socket_name (socket_name_browser, 0);
+ else
+ socket_name_browser= create_socket_name
+ /**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1);
+ opt.browser_socket = 2; /* Indicate that it has been malloced. */
+ fd_browser = create_server_socket (socket_name_browser, 0, 0,
+ &redir_socket_name_browser,
+ &socket_nonce_browser);
+ }
+
+ socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
+ fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
+ &redir_socket_name_ssh,
+ &socket_nonce_ssh);
+
+ /* If we are going to exec a program in the parent, we record
+ the PID, so that the child may check whether the program is
+ still alive. */
+ if (argc)
+ parent_pid = getpid ();
+
+ fflush (NULL);
+
+#ifdef HAVE_W32_SYSTEM
+
+ (void)csh_style;
+ (void)nodetach;
+ initialize_modules ();
+
+#else /*!HAVE_W32_SYSTEM*/
+
+ pid = fork ();
+ if (pid == (pid_t)-1)
+ {
+ log_fatal ("fork failed: %s\n", strerror (errno) );
+ exit (1);
+ }
+ else if (pid)
+ { /* We are the parent */
+ char *infostr_ssh_sock, *infostr_ssh_valid;
+
+ /* Close the socket FD. */
+ close (fd);
+
+ /* The signal mask might not be correct right now and thus
+ we restore it. That is not strictly necessary but some
+ programs falsely assume a cleared signal mask. */
+
+#ifdef HAVE_SIGPROCMASK
+ if (startup_signal_mask_valid)
+ {
+ if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
+ log_error ("error restoring signal mask: %s\n",
+ strerror (errno));
+ }
+ else
+ log_info ("no saved signal mask\n");
+#endif /*HAVE_SIGPROCMASK*/
+
+ /* Create the SSH info string if enabled. */
+ if (ssh_support)
+ {
+ if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s",
+ socket_name_ssh) < 0)
+ {
+ log_error ("out of core\n");
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu",
+ (unsigned long)getpid()) < 0)
+ {
+ log_error ("out of core\n");
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ }
+
+ *socket_name = 0; /* Don't let cleanup() remove the socket -
+ the child should do this from now on */
+ if (opt.extra_socket)
+ *socket_name_extra = 0;
+ if (opt.browser_socket)
+ *socket_name_browser = 0;
+ *socket_name_ssh = 0;
+
+ if (argc)
+ { /* Run the program given on the commandline. */
+ if (ssh_support && (putenv (infostr_ssh_sock)
+ || putenv (infostr_ssh_valid)))
+ {
+ log_error ("failed to set environment: %s\n",
+ strerror (errno) );
+ kill (pid, SIGTERM );
+ exit (1);
+ }
+
+ /* Close all the file descriptors except the standard
+ ones and those open at startup. We explicitly don't
+ close 0,1,2 in case something went wrong collecting
+ them at startup. */
+ close_all_fds (3, startup_fd_list);
+
+ /* Run the command. */
+ execvp (argv[0], argv);
+ log_error ("failed to run the command: %s\n", strerror (errno));
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ else
+ {
+ /* Print the environment string, so that the caller can use
+ shell's eval to set it */
+ if (csh_style)
+ {
+ if (ssh_support)
+ {
+ *strchr (infostr_ssh_sock, '=') = ' ';
+ es_printf ("setenv %s;\n", infostr_ssh_sock);
+ }
+ }
+ else
+ {
+ if (ssh_support)
+ {
+ es_printf ("%s; export SSH_AUTH_SOCK;\n",
+ infostr_ssh_sock);
+ }
+ }
+ if (ssh_support)
+ {
+ xfree (infostr_ssh_sock);
+ xfree (infostr_ssh_valid);
+ }
+ exit (0);
+ }
+ /*NOTREACHED*/
+ } /* End parent */
+
+ /*
+ This is the child
+ */
+
+ initialize_modules ();
+
+ /* Detach from tty and put process into a new session */
+ if (!nodetach )
+ {
+ int i;
+ unsigned int oldflags;
+
+ /* Close stdin, stdout and stderr unless it is the log stream */
+ for (i=0; i <= 2; i++)
+ {
+ if (!log_test_fd (i) && i != fd )
+ {
+ if ( ! close (i)
+ && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
+ {
+ log_error ("failed to open '%s': %s\n",
+ "/dev/null", strerror (errno));
+ cleanup ();
+ exit (1);
+ }
+ }
+ }
+ if (setsid() == -1)
+ {
+ log_error ("setsid() failed: %s\n", strerror(errno) );
+ cleanup ();
+ exit (1);
+ }
+
+ log_get_prefix (&oldflags);
+ log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
+ opt.running_detached = 1;
+
+ /* Unless we are running with a program given on the command
+ * line we can assume that the inotify things works and thus
+ * we can avoid tye regular stat calls. */
+ if (!argc)
+ reliable_homedir_inotify = 1;
+ }
+
+ {
+ struct sigaction sa;
+
+ sa.sa_handler = SIG_IGN;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction (SIGPIPE, &sa, NULL);
+ }
+#endif /*!HAVE_W32_SYSTEM*/
+
+ if (gnupg_chdir (gnupg_daemon_rootdir ()))
+ {
+ log_error ("chdir to '%s' failed: %s\n",
+ gnupg_daemon_rootdir (), strerror (errno));
+ exit (1);
+ }
+
+ log_info ("%s %s started\n", strusage(11), strusage(13) );
+ handle_connections (fd, fd_extra, fd_browser, fd_ssh);
+ assuan_sock_close (fd);
+ }
+
+ return 0;
+}
+
+
+/* Exit entry point. This function should be called instead of a
+ plain exit. */
+void
+agent_exit (int rc)
+{
+ /*FIXME: update_random_seed_file();*/
+
+ /* We run our cleanup handler because that may close cipher contexts
+ stored in secure memory and thus this needs to be done before we
+ explicitly terminate secure memory. */
+ cleanup ();
+
+#if 1
+ /* at this time a bit annoying */
+ if (opt.debug & DBG_MEMSTAT_VALUE)
+ {
+ gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
+ gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
+ }
+ if (opt.debug)
+ gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
+#endif
+ gcry_control (GCRYCTL_TERM_SECMEM );
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
+
+
+/* This is our callback function for gcrypt progress messages. It is
+ set once at startup and dispatches progress messages to the
+ corresponding threads of the agent. */
+static void
+agent_libgcrypt_progress_cb (void *data, const char *what, int printchar,
+ int current, int total)
+{
+ struct progress_dispatch_s *dispatch;
+ npth_t mytid = npth_self ();
+
+ (void)data;
+
+ for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
+ if (dispatch->ctrl && dispatch->tid == mytid)
+ break;
+ if (dispatch && dispatch->cb)
+ dispatch->cb (dispatch->ctrl, what, printchar, current, total);
+}
+
+
+/* If a progress dispatcher callback has been associated with the
+ * current connection unregister it. */
+static void
+unregister_progress_cb (void)
+{
+ struct progress_dispatch_s *dispatch;
+ npth_t mytid = npth_self ();
+
+ for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
+ if (dispatch->ctrl && dispatch->tid == mytid)
+ break;
+ if (dispatch)
+ {
+ dispatch->ctrl = NULL;
+ dispatch->cb = NULL;
+ }
+}
+
+
+/* Setup a progress callback CB for the current connection. Using a
+ * CB of NULL disables the callback. */
+void
+agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
+ int printchar, int current, int total),
+ ctrl_t ctrl)
+{
+ struct progress_dispatch_s *dispatch, *firstfree;
+ npth_t mytid = npth_self ();
+
+ firstfree = NULL;
+ for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
+ {
+ if (dispatch->ctrl && dispatch->tid == mytid)
+ break;
+ if (!dispatch->ctrl && !firstfree)
+ firstfree = dispatch;
+ }
+ if (!dispatch) /* None allocated: Reuse or allocate a new one. */
+ {
+ if (firstfree)
+ {
+ dispatch = firstfree;
+ }
+ else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
+ {
+ dispatch->next = progress_dispatch_list;
+ progress_dispatch_list = dispatch;
+ }
+ else
+ {
+ log_error ("error allocating new progress dispatcher slot: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return;
+ }
+ dispatch->ctrl = ctrl;
+ dispatch->tid = mytid;
+ }
+
+ dispatch->cb = cb;
+}
+
+
+/* Each thread has its own local variables conveyed by a control
+ structure usually identified by an argument named CTRL. This
+ function is called immediately after allocating the control
+ structure. Its purpose is to setup the default values for that
+ structure. Note that some values may have already been set. */
+static void
+agent_init_default_ctrl (ctrl_t ctrl)
+{
+ assert (ctrl->session_env);
+
+ /* Note we ignore malloc errors because we can't do much about it
+ and the request will fail anyway shortly after this
+ initialization. */
+ session_env_setenv (ctrl->session_env, "DISPLAY", default_display);
+ session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname);
+ session_env_setenv (ctrl->session_env, "TERM", default_ttytype);
+ session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority);
+ session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL);
+
+ if (ctrl->lc_ctype)
+ xfree (ctrl->lc_ctype);
+ ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
+
+ if (ctrl->lc_messages)
+ xfree (ctrl->lc_messages);
+ ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
+ /**/ : NULL;
+ ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
+}
+
+
+/* Release all resources allocated by default in the control
+ structure. This is the counterpart to agent_init_default_ctrl. */
+static void
+agent_deinit_default_ctrl (ctrl_t ctrl)
+{
+ unregister_progress_cb ();
+ session_env_release (ctrl->session_env);
+
+ if (ctrl->lc_ctype)
+ xfree (ctrl->lc_ctype);
+ if (ctrl->lc_messages)
+ xfree (ctrl->lc_messages);
+}
+
+
+/* Because the ssh protocol does not send us information about the
+ current TTY setting, we use this function to use those from startup
+ or those explicitly set. This is also used for the restricted mode
+ where we ignore requests to change the environment. */
+gpg_error_t
+agent_copy_startup_env (ctrl_t ctrl)
+{
+ gpg_error_t err = 0;
+ int iterator = 0;
+ const char *name, *value;
+
+ while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
+ {
+ if ((value = session_env_getenv (opt.startup_env, name)))
+ err = session_env_setenv (ctrl->session_env, name, value);
+ }
+
+ if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
+ if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
+ err = gpg_error_from_syserror ();
+
+ if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
+ if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
+ err = gpg_error_from_syserror ();
+
+ if (err)
+ log_error ("error setting default session environment: %s\n",
+ gpg_strerror (err));
+
+ return err;
+}
+
+
+/* Reread parts of the configuration. Note, that this function is
+ obviously not thread-safe and should only be called from the PTH
+ signal handler.
+
+ Fixme: Due to the way the argument parsing works, we create a
+ memory leak here for all string type arguments. There is currently
+ no clean way to tell whether the memory for the argument has been
+ allocated or points into the process' original arguments. Unless
+ we have a mechanism to tell this, we need to live on with this. */
+static void
+reread_configuration (void)
+{
+ ARGPARSE_ARGS pargs;
+ char *twopart;
+ int dummy;
+
+ if (!config_filename)
+ return; /* No config file. */
+
+ twopart = strconcat (GPG_AGENT_NAME EXTSEP_S "conf" PATHSEP_S,
+ config_filename, NULL);
+ if (!twopart)
+ return; /* Out of core. */
+
+ parse_rereadable_options (NULL, 1); /* Start from the default values. */
+
+ memset (&pargs, 0, sizeof pargs);
+ dummy = 0;
+ pargs.argc = &dummy;
+ pargs.flags = (ARGPARSE_FLAG_KEEP
+ |ARGPARSE_FLAG_SYS
+ |ARGPARSE_FLAG_USER);
+ while (gnupg_argparser (&pargs, opts, twopart))
+ {
+ if (pargs.r_opt == ARGPARSE_CONFFILE)
+ {
+ log_info (_("reading options from '%s'\n"),
+ pargs.r_type? pargs.r.ret_str: "[cmdline]");
+ }
+ else if (pargs.r_opt < -1)
+ pargs.err = ARGPARSE_PRINT_WARNING;
+ else /* Try to parse this option - ignore unchangeable ones. */
+ parse_rereadable_options (&pargs, 1);
+ }
+ gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */
+ xfree (twopart);
+ finalize_rereadable_options ();
+ set_debug ();
+}
+
+
+/* Return the file name of the socket we are using for native
+ requests. */
+const char *
+get_agent_socket_name (void)
+{
+ const char *s = socket_name;
+
+ return (s && *s)? s : NULL;
+}
+
+/* Return the file name of the socket we are using for SSH
+ requests. */
+const char *
+get_agent_ssh_socket_name (void)
+{
+ const char *s = socket_name_ssh;
+
+ return (s && *s)? s : NULL;
+}
+
+
+/* Return the number of active connections. */
+int
+get_agent_active_connection_count (void)
+{
+ return active_connections;
+}
+
+
+/* Under W32, this function returns the handle of the scdaemon
+ notification event. Calling it the first time creates that
+ event. */
+#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
+void *
+get_agent_scd_notify_event (void)
+{
+ static HANDLE the_event = INVALID_HANDLE_VALUE;
+
+ if (the_event == INVALID_HANDLE_VALUE)
+ {
+ HANDLE h, h2;
+ SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
+
+ /* We need to use a manual reset event object due to the way our
+ w32-pth wait function works: If we would use an automatic
+ reset event we are not able to figure out which handle has
+ been signaled because at the time we single out the signaled
+ handles using WFSO the event has already been reset due to
+ the WFMO. */
+ h = CreateEvent (&sa, TRUE, FALSE, NULL);
+ if (!h)
+ log_error ("can't create scd notify event: %s\n", w32_strerror (-1) );
+ else if (!DuplicateHandle (GetCurrentProcess(), h,
+ GetCurrentProcess(), &h2,
+ EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
+ {
+ log_error ("setting synchronize for scd notify event failed: %s\n",
+ w32_strerror (-1) );
+ CloseHandle (h);
+ }
+ else
+ {
+ CloseHandle (h);
+ the_event = h2;
+ }
+ }
+
+ return the_event;
+}
+#endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/
+
+
+
+/* Create a name for the socket in the home directory as using
+ STANDARD_NAME. We also check for valid characters as well as
+ against a maximum allowed length for a unix domain socket is done.
+ The function terminates the process in case of an error. Returns:
+ Pointer to an allocated string with the absolute name of the socket
+ used. */
+static char *
+create_socket_name (char *standard_name, int with_homedir)
+{
+ char *name;
+
+ if (with_homedir)
+ name = make_filename (gnupg_socketdir (), standard_name, NULL);
+ else
+ name = make_filename (standard_name, NULL);
+ if (strchr (name, PATHSEP_C))
+ {
+ log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
+ agent_exit (2);
+ }
+ return name;
+}
+
+
+
+/* Create a Unix domain socket with NAME. Returns the file descriptor
+ or terminates the process in case of an error. Note that this
+ function needs to be used for the regular socket first (indicated
+ by PRIMARY) and only then for the extra and the ssh sockets. If
+ the socket has been redirected the name of the real socket is
+ stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a
+ Cygwin compatible socket is created (Windows only). */
+static gnupg_fd_t
+create_server_socket (char *name, int primary, int cygwin,
+ char **r_redir_name, assuan_sock_nonce_t *nonce)
+{
+ struct sockaddr *addr;
+ struct sockaddr_un *unaddr;
+ socklen_t len;
+ gnupg_fd_t fd;
+ int rc;
+
+ xfree (*r_redir_name);
+ *r_redir_name = NULL;
+
+ fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == ASSUAN_INVALID_FD)
+ {
+ log_error (_("can't create socket: %s\n"), strerror (errno));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ agent_exit (2);
+ }
+
+ if (cygwin)
+ assuan_sock_set_flag (fd, "cygwin", 1);
+
+ unaddr = xmalloc (sizeof *unaddr);
+ addr = (struct sockaddr*)unaddr;
+
+ {
+ int redirected;
+
+ if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
+ {
+ if (errno == ENAMETOOLONG)
+ log_error (_("socket name '%s' is too long\n"), name);
+ else
+ log_error ("error preparing socket '%s': %s\n",
+ name, gpg_strerror (gpg_error_from_syserror ()));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ xfree (unaddr);
+ agent_exit (2);
+ }
+ if (redirected)
+ {
+ *r_redir_name = xstrdup (unaddr->sun_path);
+ if (opt.verbose)
+ log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
+ }
+ }
+
+ len = SUN_LEN (unaddr);
+ rc = assuan_sock_bind (fd, addr, len);
+
+ /* Our error code mapping on W32CE returns EEXIST thus we also test
+ for this. */
+ if (rc == -1
+ && (errno == EADDRINUSE
+#ifdef HAVE_W32_SYSTEM
+ || errno == EEXIST
+#endif
+ ))
+ {
+ /* Check whether a gpg-agent is already running. We do this
+ test only if this is the primary socket. For secondary
+ sockets we assume that a test for gpg-agent has already been
+ done and reuse the requested socket. Testing the ssh-socket
+ is not possible because at this point, though we know the new
+ Assuan socket, the Assuan server and thus the ssh-agent
+ server is not yet operational; this would lead to a hang. */
+ if (primary && !check_for_running_agent (1))
+ {
+ if (steal_socket)
+ log_info (N_("trying to steal socket from running %s\n"),
+ "gpg-agent");
+ else
+ {
+ log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
+ log_set_file (NULL);
+ log_error (_("a gpg-agent is already running - "
+ "not starting a new one\n"));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ assuan_sock_close (fd);
+ xfree (unaddr);
+ agent_exit (2);
+ }
+ }
+ gnupg_remove (unaddr->sun_path);
+ rc = assuan_sock_bind (fd, addr, len);
+ }
+ if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
+ log_error (_("error getting nonce for the socket\n"));
+ if (rc == -1)
+ {
+ /* We use gpg_strerror here because it allows us to get strings
+ for some W32 socket error codes. */
+ log_error (_("error binding socket to '%s': %s\n"),
+ unaddr->sun_path,
+ gpg_strerror (gpg_error_from_syserror ()));
+
+ assuan_sock_close (fd);
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ xfree (unaddr);
+ agent_exit (2);
+ }
+
+ if (gnupg_chmod (unaddr->sun_path, "-rwx"))
+ log_error (_("can't set permissions of '%s': %s\n"),
+ unaddr->sun_path, strerror (errno));
+
+ if (listen (FD2INT(fd), listen_backlog ) == -1)
+ {
+ log_error ("listen(fd,%d) failed: %s\n",
+ listen_backlog, strerror (errno));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ assuan_sock_close (fd);
+ xfree (unaddr);
+ agent_exit (2);
+ }
+
+ if (opt.verbose)
+ log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
+
+ xfree (unaddr);
+ return fd;
+}
+
+
+/* Check that the directory for storing the private keys exists and
+ create it if not. This function won't fail as it is only a
+ convenience function and not strictly necessary. */
+static void
+create_private_keys_directory (const char *home)
+{
+ char *fname;
+ struct stat statbuf;
+
+ fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
+ if (gnupg_stat (fname, &statbuf) && errno == ENOENT)
+ {
+ if (gnupg_mkdir (fname, "-rwx"))
+ log_error (_("can't create directory '%s': %s\n"),
+ fname, strerror (errno) );
+ else if (!opt.quiet)
+ log_info (_("directory '%s' created\n"), fname);
+
+ if (gnupg_chmod (fname, "-rwx"))
+ log_error (_("can't set permissions of '%s': %s\n"),
+ fname, strerror (errno));
+ }
+ else
+ {
+ /* The file exists or another error. Make sure we have sensible
+ * permissions. We enforce rwx for user but keep existing group
+ * permissions. Permissions for other are always cleared. */
+ if (gnupg_chmod (fname, "-rwx...---"))
+ log_error (_("can't set permissions of '%s': %s\n"),
+ fname, strerror (errno));
+ }
+ xfree (fname);
+}
+
+
+/* Create the directory only if the supplied directory name is the
+ same as the default one. This way we avoid to create arbitrary
+ directories when a non-default home directory is used. To cope
+ with HOME, we compare only the suffix if we see that the default
+ homedir does start with a tilde. We don't stop here in case of
+ problems because other functions will throw an error anyway.*/
+static void
+create_directories (void)
+{
+ struct stat statbuf;
+ const char *defhome = standard_homedir ();
+ char *home;
+
+ home = make_filename (gnupg_homedir (), NULL);
+ if (gnupg_stat (home, &statbuf))
+ {
+ if (errno == ENOENT)
+ {
+ if (
+#ifdef HAVE_W32_SYSTEM
+ ( !compare_filenames (home, defhome) )
+#else
+ (*defhome == '~'
+ && (strlen (home) >= strlen (defhome+1)
+ && !strcmp (home + strlen(home)
+ - strlen (defhome+1), defhome+1)))
+ || (*defhome != '~' && !strcmp (home, defhome) )
+#endif
+ )
+ {
+ if (gnupg_mkdir (home, "-rwx"))
+ log_error (_("can't create directory '%s': %s\n"),
+ home, strerror (errno) );
+ else
+ {
+ if (!opt.quiet)
+ log_info (_("directory '%s' created\n"), home);
+ create_private_keys_directory (home);
+ }
+ }
+ }
+ else
+ log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
+ }
+ else if ( !S_ISDIR(statbuf.st_mode))
+ {
+ log_error (_("can't use '%s' as home directory\n"), home);
+ }
+ else /* exists and is a directory. */
+ {
+ create_private_keys_directory (home);
+ }
+ xfree (home);
+}
+
+
+
+/* This is the worker for the ticker. It is called every few seconds
+ and may only do fast operations. */
+static void
+handle_tick (void)
+{
+ static time_t last_minute;
+ struct stat statbuf;
+
+ if (!last_minute)
+ last_minute = time (NULL);
+
+ /* Check whether the scdaemon has died and cleanup in this case. */
+ agent_scd_check_aliveness ();
+
+ /* If we are running as a child of another process, check whether
+ the parent is still alive and shutdown if not. */
+#ifndef HAVE_W32_SYSTEM
+ if (parent_pid != (pid_t)(-1))
+ {
+ if (kill (parent_pid, 0))
+ {
+ shutdown_pending = 2;
+ log_info ("parent process died - shutting down\n");
+ log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+ cleanup ();
+ agent_exit (0);
+ }
+ }
+#endif /*HAVE_W32_SYSTEM*/
+
+ /* Code to be run from time to time. */
+#if CHECK_OWN_SOCKET_INTERVAL > 0
+ if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
+ {
+ check_own_socket ();
+ last_minute = time (NULL);
+ }
+#endif
+
+ /* Need to check for expired cache entries. */
+ agent_cache_housekeeping ();
+
+ /* Check whether the homedir is still available. */
+ if (!shutdown_pending
+ && (!have_homedir_inotify || !reliable_homedir_inotify)
+ && gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
+ {
+ shutdown_pending = 1;
+ log_info ("homedir has been removed - shutting down\n");
+ }
+}
+
+
+/* A global function which allows us to call the reload stuff from
+ other places too. This is only used when build for W32. */
+void
+agent_sighup_action (void)
+{
+ log_info ("SIGHUP received - "
+ "re-reading configuration and flushing cache\n");
+
+ agent_flush_cache ();
+ reread_configuration ();
+ agent_reload_trustlist ();
+ /* We flush the module name cache so that after installing a
+ "pinentry" binary that one can be used in case the
+ "pinentry-basic" fallback was in use. */
+ gnupg_module_name_flush_some ();
+
+ if (opt.disable_scdaemon)
+ agent_card_killscd ();
+}
+
+
+/* A helper function to handle SIGUSR2. */
+static void
+agent_sigusr2_action (void)
+{
+ if (opt.verbose)
+ log_info ("SIGUSR2 received - updating card event counter\n");
+ /* Nothing to check right now. We only increment a counter. */
+ bump_card_eventcounter ();
+}
+
+
+#ifndef HAVE_W32_SYSTEM
+/* The signal handler for this program. It is expected to be run in
+ its own thread and not in the context of a signal handler. */
+static void
+handle_signal (int signo)
+{
+ switch (signo)
+ {
+#ifndef HAVE_W32_SYSTEM
+ case SIGHUP:
+ agent_sighup_action ();
+ break;
+
+ case SIGUSR1:
+ log_info ("SIGUSR1 received - printing internal information:\n");
+ /* Fixme: We need to see how to integrate pth dumping into our
+ logging system. */
+ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
+ agent_query_dump_state ();
+ agent_scd_dump_state ();
+ break;
+
+ case SIGUSR2:
+ agent_sigusr2_action ();
+ break;
+
+ case SIGTERM:
+ if (!shutdown_pending)
+ log_info ("SIGTERM received - shutting down ...\n");
+ else
+ log_info ("SIGTERM received - still %i open connections\n",
+ active_connections);
+ shutdown_pending++;
+ if (shutdown_pending > 2)
+ {
+ log_info ("shutdown forced\n");
+ log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+ cleanup ();
+ agent_exit (0);
+ }
+ break;
+
+ case SIGINT:
+ log_info ("SIGINT received - immediate shutdown\n");
+ log_info( "%s %s stopped\n", strusage(11), strusage(13));
+ cleanup ();
+ agent_exit (0);
+ break;
+#endif
+ default:
+ log_info ("signal %d received - no action defined\n", signo);
+ }
+}
+#endif
+
+/* Check the nonce on a new connection. This is a NOP unless we
+ are using our Unix domain socket emulation under Windows. */
+static int
+check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
+{
+ if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
+ {
+ log_info (_("error reading nonce on fd %d: %s\n"),
+ FD2INT(ctrl->thread_startup.fd), strerror (errno));
+ assuan_sock_close (ctrl->thread_startup.fd);
+ xfree (ctrl);
+ return -1;
+ }
+ else
+ return 0;
+}
+
+
+#ifdef HAVE_W32_SYSTEM
+/* The window message processing function for Putty. Warning: This
+ code runs as a native Windows thread. Use of our own functions
+ needs to be bracket with pth_leave/pth_enter. */
+static LRESULT CALLBACK
+putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ int ret = 0;
+ int w32rc;
+ COPYDATASTRUCT *cds;
+ const char *mapfile;
+ HANDLE maphd;
+ PSID mysid = NULL;
+ PSID mapsid = NULL;
+ void *data = NULL;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ ctrl_t ctrl = NULL;
+
+ if (msg != WM_COPYDATA)
+ {
+ return DefWindowProc (hwnd, msg, wparam, lparam);
+ }
+
+ cds = (COPYDATASTRUCT*)lparam;
+ if (cds->dwData != PUTTY_IPC_MAGIC)
+ return 0; /* Ignore data with the wrong magic. */
+ mapfile = cds->lpData;
+ if (!cds->cbData || mapfile[cds->cbData - 1])
+ return 0; /* Ignore empty and non-properly terminated strings. */
+
+ if (DBG_IPC)
+ {
+ npth_protect ();
+ log_debug ("ssh map file '%s'", mapfile);
+ npth_unprotect ();
+ }
+
+ maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
+ if (DBG_IPC)
+ {
+ npth_protect ();
+ log_debug ("ssh map handle %p\n", maphd);
+ npth_unprotect ();
+ }
+
+ if (!maphd || maphd == INVALID_HANDLE_VALUE)
+ return 0;
+
+ npth_protect ();
+
+ mysid = w32_get_user_sid ();
+ if (!mysid)
+ {
+ log_error ("error getting my sid\n");
+ goto leave;
+ }
+
+ w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
+ OWNER_SECURITY_INFORMATION,
+ &mapsid, NULL, NULL, NULL,
+ &psd);
+ if (w32rc)
+ {
+ log_error ("error getting sid of ssh map file: rc=%d", w32rc);
+ goto leave;
+ }
+
+ if (DBG_IPC)
+ {
+ char *sidstr;
+
+ if (!ConvertSidToStringSid (mysid, &sidstr))
+ sidstr = NULL;
+ log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
+ LocalFree (sidstr);
+ if (!ConvertSidToStringSid (mapsid, &sidstr))
+ sidstr = NULL;
+ log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
+ LocalFree (sidstr);
+ }
+
+ if (!EqualSid (mysid, mapsid))
+ {
+ log_error ("ssh map file has a non-matching sid\n");
+ goto leave;
+ }
+
+ data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
+ if (DBG_IPC)
+ log_debug ("ssh IPC buffer at %p\n", data);
+ if (!data)
+ goto leave;
+
+ /* log_printhex (data, 20, "request:"); */
+
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (!ctrl)
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ goto leave;
+ }
+ ctrl->session_env = session_env_new ();
+ if (!ctrl->session_env)
+ {
+ log_error ("error allocating session environment block: %s\n",
+ strerror (errno) );
+ goto leave;
+ }
+
+ agent_init_default_ctrl (ctrl);
+ if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
+ ret = 1; /* Valid ssh message has been constructed. */
+ agent_deinit_default_ctrl (ctrl);
+ /* log_printhex (data, 20, " reply:"); */
+
+ leave:
+ xfree (ctrl);
+ if (data)
+ UnmapViewOfFile (data);
+ xfree (mapsid);
+ if (psd)
+ LocalFree (psd);
+ xfree (mysid);
+ CloseHandle (maphd);
+
+ npth_unprotect ();
+
+ return ret;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+#ifdef HAVE_W32_SYSTEM
+/* The thread handling Putty's IPC requests. */
+static void *
+putty_message_thread (void *arg)
+{
+ WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
+ NULL, NULL, NULL, NULL, NULL, "Pageant"};
+ HWND hwnd;
+ MSG msg;
+
+ (void)arg;
+
+ if (opt.verbose)
+ log_info ("putty message loop thread started\n");
+
+ /* The message loop runs as thread independent from our nPth system.
+ This also means that we need to make sure that we switch back to
+ our system before calling any no-windows function. */
+ npth_unprotect ();
+
+ /* First create a window to make sure that a message queue exists
+ for this thread. */
+ if (!RegisterClass (&wndwclass))
+ {
+ npth_protect ();
+ log_error ("error registering Pageant window class");
+ return NULL;
+ }
+ hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
+ 0, 0, 0, 0,
+ HWND_MESSAGE, /* hWndParent */
+ NULL, /* hWndMenu */
+ NULL, /* hInstance */
+ NULL); /* lpParm */
+ if (!hwnd)
+ {
+ npth_protect ();
+ log_error ("error creating Pageant window");
+ return NULL;
+ }
+
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ /* Back to nPth. */
+ npth_protect ();
+
+ if (opt.verbose)
+ log_info ("putty message loop thread stopped\n");
+ return NULL;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+static void *
+do_start_connection_thread (ctrl_t ctrl)
+{
+ active_connections++;
+ agent_init_default_ctrl (ctrl);
+ if (opt.verbose && !DBG_IPC)
+ log_info (_("handler 0x%lx for fd %d started\n"),
+ (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
+
+ start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
+ if (opt.verbose && !DBG_IPC)
+ log_info (_("handler 0x%lx for fd %d terminated\n"),
+ (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
+
+ agent_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ active_connections--;
+ return NULL;
+}
+
+
+/* This is the standard connection thread's main function. */
+static void *
+start_connection_thread_std (void *arg)
+{
+ ctrl_t ctrl = arg;
+
+ if (check_nonce (ctrl, &socket_nonce))
+ {
+ log_error ("handler 0x%lx nonce check FAILED\n",
+ (unsigned long) npth_self());
+ return NULL;
+ }
+
+ return do_start_connection_thread (ctrl);
+}
+
+
+/* This is the extra socket connection thread's main function. */
+static void *
+start_connection_thread_extra (void *arg)
+{
+ ctrl_t ctrl = arg;
+
+ if (check_nonce (ctrl, &socket_nonce_extra))
+ {
+ log_error ("handler 0x%lx nonce check FAILED\n",
+ (unsigned long) npth_self());
+ return NULL;
+ }
+
+ ctrl->restricted = 1;
+ return do_start_connection_thread (ctrl);
+}
+
+
+/* This is the browser socket connection thread's main function. */
+static void *
+start_connection_thread_browser (void *arg)
+{
+ ctrl_t ctrl = arg;
+
+ if (check_nonce (ctrl, &socket_nonce_browser))
+ {
+ log_error ("handler 0x%lx nonce check FAILED\n",
+ (unsigned long) npth_self());
+ return NULL;
+ }
+
+ ctrl->restricted = 2;
+ return do_start_connection_thread (ctrl);
+}
+
+
+/* This is the ssh connection thread's main function. */
+static void *
+start_connection_thread_ssh (void *arg)
+{
+ ctrl_t ctrl = arg;
+
+ if (check_nonce (ctrl, &socket_nonce_ssh))
+ return NULL;
+
+ active_connections++;
+ agent_init_default_ctrl (ctrl);
+ if (opt.verbose)
+ log_info (_("ssh handler 0x%lx for fd %d started\n"),
+ (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
+
+ start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
+ if (opt.verbose)
+ log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
+ (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
+
+ agent_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ active_connections--;
+ return NULL;
+}
+
+
+/* Connection handler loop. Wait for connection requests and spawn a
+ thread after accepting a connection. */
+static void
+handle_connections (gnupg_fd_t listen_fd,
+ gnupg_fd_t listen_fd_extra,
+ gnupg_fd_t listen_fd_browser,
+ gnupg_fd_t listen_fd_ssh)
+{
+ gpg_error_t err;
+ npth_attr_t tattr;
+ struct sockaddr_un paddr;
+ socklen_t plen;
+ fd_set fdset, read_fdset;
+ int ret;
+ gnupg_fd_t fd;
+ int nfd;
+ int saved_errno;
+ struct timespec abstime;
+ struct timespec curtime;
+ struct timespec timeout;
+#ifdef HAVE_W32_SYSTEM
+ HANDLE events[2];
+ unsigned int events_set;
+#endif
+ int sock_inotify_fd = -1;
+ int home_inotify_fd = -1;
+ struct {
+ const char *name;
+ void *(*func) (void *arg);
+ gnupg_fd_t l_fd;
+ } listentbl[] = {
+ { "std", start_connection_thread_std },
+ { "extra", start_connection_thread_extra },
+ { "browser", start_connection_thread_browser },
+ { "ssh", start_connection_thread_ssh }
+ };
+
+
+ ret = npth_attr_init(&tattr);
+ if (ret)
+ log_fatal ("error allocating thread attributes: %s\n",
+ strerror (ret));
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+
+#ifndef HAVE_W32_SYSTEM
+ npth_sigev_init ();
+ npth_sigev_add (SIGHUP);
+ npth_sigev_add (SIGUSR1);
+ npth_sigev_add (SIGUSR2);
+ npth_sigev_add (SIGINT);
+ npth_sigev_add (SIGTERM);
+ npth_sigev_fini ();
+#else
+# ifdef HAVE_W32CE_SYSTEM
+ /* Use a dummy event. */
+ sigs = 0;
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+# else
+ events[0] = get_agent_scd_notify_event ();
+ events[1] = INVALID_HANDLE_VALUE;
+# endif
+#endif
+
+ if (disable_check_own_socket)
+ sock_inotify_fd = -1;
+ else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
+ {
+ if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
+ log_info ("error enabling daemon termination by socket removal: %s\n",
+ gpg_strerror (err));
+ }
+
+ if (disable_check_own_socket)
+ home_inotify_fd = -1;
+ else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
+ gnupg_homedir ())))
+ {
+ if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
+ log_info ("error enabling daemon termination by homedir removal: %s\n",
+ gpg_strerror (err));
+ }
+ else
+ have_homedir_inotify = 1;
+
+ /* On Windows we need to fire up a separate thread to listen for
+ requests from Putty (an SSH client), so we can replace Putty's
+ Pageant (its ssh-agent implementation). */
+#ifdef HAVE_W32_SYSTEM
+ if (putty_support)
+ {
+ npth_t thread;
+
+ ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
+ if (ret)
+ {
+ log_error ("error spawning putty message loop: %s\n", strerror (ret));
+ }
+ }
+#endif /*HAVE_W32_SYSTEM*/
+
+ /* Set a flag to tell call-scd.c that it may enable event
+ notifications. */
+ opt.sigusr2_enabled = 1;
+
+ FD_ZERO (&fdset);
+ FD_SET (FD2INT (listen_fd), &fdset);
+ nfd = FD2INT (listen_fd);
+ if (listen_fd_extra != GNUPG_INVALID_FD)
+ {
+ FD_SET ( FD2INT(listen_fd_extra), &fdset);
+ if (FD2INT (listen_fd_extra) > nfd)
+ nfd = FD2INT (listen_fd_extra);
+ }
+ if (listen_fd_browser != GNUPG_INVALID_FD)
+ {
+ FD_SET ( FD2INT(listen_fd_browser), &fdset);
+ if (FD2INT (listen_fd_browser) > nfd)
+ nfd = FD2INT (listen_fd_browser);
+ }
+ if (listen_fd_ssh != GNUPG_INVALID_FD)
+ {
+ FD_SET ( FD2INT(listen_fd_ssh), &fdset);
+ if (FD2INT (listen_fd_ssh) > nfd)
+ nfd = FD2INT (listen_fd_ssh);
+ }
+ if (sock_inotify_fd != -1)
+ {
+ FD_SET (sock_inotify_fd, &fdset);
+ if (sock_inotify_fd > nfd)
+ nfd = sock_inotify_fd;
+ }
+ if (home_inotify_fd != -1)
+ {
+ FD_SET (home_inotify_fd, &fdset);
+ if (home_inotify_fd > nfd)
+ nfd = home_inotify_fd;
+ }
+
+ listentbl[0].l_fd = listen_fd;
+ listentbl[1].l_fd = listen_fd_extra;
+ listentbl[2].l_fd = listen_fd_browser;
+ listentbl[3].l_fd = listen_fd_ssh;
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+
+ for (;;)
+ {
+ /* Shutdown test. */
+ if (shutdown_pending)
+ {
+ if (active_connections == 0)
+ break; /* ready */
+
+ /* Do not accept new connections but keep on running the
+ * loop to cope with the timer events.
+ *
+ * Note that we do not close the listening socket because a
+ * client trying to connect to that socket would instead
+ * restart a new dirmngr instance - which is unlikely the
+ * intention of a shutdown. */
+ FD_ZERO (&fdset);
+ nfd = -1;
+ if (sock_inotify_fd != -1)
+ {
+ FD_SET (sock_inotify_fd, &fdset);
+ nfd = sock_inotify_fd;
+ }
+ if (home_inotify_fd != -1)
+ {
+ FD_SET (home_inotify_fd, &fdset);
+ if (home_inotify_fd > nfd)
+ nfd = home_inotify_fd;
+ }
+ }
+
+ /* POSIX says that fd_set should be implemented as a structure,
+ thus a simple assignment is fine to copy the entire set. */
+ read_fdset = fdset;
+
+ npth_clock_gettime (&curtime);
+ if (!(npth_timercmp (&curtime, &abstime, <)))
+ {
+ /* Timeout. */
+ handle_tick ();
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+ }
+ npth_timersub (&abstime, &curtime, &timeout);
+
+#ifndef HAVE_W32_SYSTEM
+ ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
+ npth_sigev_sigmask ());
+ saved_errno = errno;
+
+ {
+ int signo;
+ while (npth_sigev_get_pending (&signo))
+ handle_signal (signo);
+ }
+#else
+ ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
+ events, &events_set);
+ saved_errno = errno;
+
+ /* This is valid even if npth_eselect returns an error. */
+ if (events_set & 1)
+ agent_sigusr2_action ();
+#endif
+
+ if (ret == -1 && saved_errno != EINTR)
+ {
+ log_error (_("npth_pselect failed: %s - waiting 1s\n"),
+ strerror (saved_errno));
+ npth_sleep (1);
+ continue;
+ }
+ if (ret <= 0)
+ /* Interrupt or timeout. Will be handled when calculating the
+ next timeout. */
+ continue;
+
+ /* The inotify fds are set even when a shutdown is pending (see
+ * above). So we must handle them in any case. To avoid that
+ * they trigger a second time we close them immediately. */
+ if (sock_inotify_fd != -1
+ && FD_ISSET (sock_inotify_fd, &read_fdset)
+ && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME))
+ {
+ shutdown_pending = 1;
+ close (sock_inotify_fd);
+ sock_inotify_fd = -1;
+ log_info ("socket file has been removed - shutting down\n");
+ }
+
+ if (home_inotify_fd != -1
+ && FD_ISSET (home_inotify_fd, &read_fdset))
+ {
+ shutdown_pending = 1;
+ close (home_inotify_fd);
+ home_inotify_fd = -1;
+ log_info ("homedir has been removed - shutting down\n");
+ }
+
+ if (!shutdown_pending)
+ {
+ int idx;
+ ctrl_t ctrl;
+ npth_t thread;
+
+ for (idx=0; idx < DIM(listentbl); idx++)
+ {
+ if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
+ continue;
+ if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
+ continue;
+
+ plen = sizeof paddr;
+ fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
+ (struct sockaddr *)&paddr, &plen));
+ if (fd == GNUPG_INVALID_FD)
+ {
+ log_error ("accept failed for %s: %s\n",
+ listentbl[idx].name, strerror (errno));
+ }
+ else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
+ {
+ log_error ("error allocating connection data for %s: %s\n",
+ listentbl[idx].name, strerror (errno) );
+ assuan_sock_close (fd);
+ }
+ else if ( !(ctrl->session_env = session_env_new ()))
+ {
+ log_error ("error allocating session env block for %s: %s\n",
+ listentbl[idx].name, strerror (errno) );
+ xfree (ctrl);
+ assuan_sock_close (fd);
+ }
+ else
+ {
+ ctrl->thread_startup.fd = fd;
+ ret = npth_create (&thread, &tattr,
+ listentbl[idx].func, ctrl);
+ if (ret)
+ {
+ log_error ("error spawning connection handler for %s:"
+ " %s\n", listentbl[idx].name, strerror (ret));
+ assuan_sock_close (fd);
+ xfree (ctrl);
+ }
+ }
+ }
+ }
+ }
+
+ if (sock_inotify_fd != -1)
+ close (sock_inotify_fd);
+ if (home_inotify_fd != -1)
+ close (home_inotify_fd);
+ cleanup ();
+ log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
+ npth_attr_destroy (&tattr);
+}
+
+
+
+/* Helper for check_own_socket. */
+static gpg_error_t
+check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
+{
+ membuf_t *mb = opaque;
+ put_membuf (mb, buffer, length);
+ return 0;
+}
+
+
+/* The thread running the actual check. We need to run this in a
+ separate thread so that check_own_thread can be called from the
+ timer tick. */
+static void *
+check_own_socket_thread (void *arg)
+{
+ int rc;
+ char *sockname = arg;
+ assuan_context_t ctx = NULL;
+ membuf_t mb;
+ char *buffer;
+
+ check_own_socket_running++;
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
+
+ rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
+ if (rc)
+ {
+ log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ init_membuf (&mb, 100);
+ rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
+ NULL, NULL, NULL, NULL);
+ put_membuf (&mb, "", 1);
+ buffer = get_membuf (&mb, NULL);
+ if (rc || !buffer)
+ {
+ log_error ("sending command \"%s\" to my own socket failed: %s\n",
+ "GETINFO pid", gpg_strerror (rc));
+ rc = 1;
+ }
+ else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
+ {
+ log_error ("socket is now serviced by another server\n");
+ rc = 1;
+ }
+ else if (opt.verbose > 1)
+ log_error ("socket is still served by this server\n");
+
+ xfree (buffer);
+
+ leave:
+ xfree (sockname);
+ if (ctx)
+ assuan_release (ctx);
+ if (rc)
+ {
+ /* We may not remove the socket as it is now in use by another
+ server. */
+ inhibit_socket_removal = 1;
+ shutdown_pending = 2;
+ log_info ("this process is useless - shutting down\n");
+ }
+ check_own_socket_running--;
+ return NULL;
+}
+
+
+/* Check whether we are still listening on our own socket. In case
+ another gpg-agent process started after us has taken ownership of
+ our socket, we would linger around without any real task. Thus we
+ better check once in a while whether we are really needed. */
+static void
+check_own_socket (void)
+{
+ char *sockname;
+ npth_t thread;
+ npth_attr_t tattr;
+ int err;
+
+ if (disable_check_own_socket)
+ return;
+
+ if (check_own_socket_running || shutdown_pending)
+ return; /* Still running or already shutting down. */
+
+ sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
+ if (!sockname)
+ return; /* Out of memory. */
+
+ err = npth_attr_init (&tattr);
+ if (err)
+ return;
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+ err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
+ if (err)
+ log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
+ npth_attr_destroy (&tattr);
+}
+
+
+
+/* Figure out whether an agent is available and running. Prints an
+ error if not. If SILENT is true, no messages are printed.
+ Returns 0 if the agent is running. */
+static int
+check_for_running_agent (int silent)
+{
+ gpg_error_t err;
+ char *sockname;
+ assuan_context_t ctx = NULL;
+
+ sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
+ if (!sockname)
+ return gpg_error_from_syserror ();
+
+ err = assuan_new (&ctx);
+ if (!err)
+ err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
+ xfree (sockname);
+ if (err)
+ {
+ if (!silent)
+ log_error (_("no gpg-agent running in this session\n"));
+
+ if (ctx)
+ assuan_release (ctx);
+ return -1;
+ }
+
+ if (!opt.quiet && !silent)
+ log_info ("gpg-agent running and available\n");
+
+ assuan_release (ctx);
+ return 0;
+}
diff --git a/agent/gpg-agent.w32-manifest.in b/agent/gpg-agent.w32-manifest.in
new file mode 100644
index 0000000..d865aef
--- /dev/null
+++ b/agent/gpg-agent.w32-manifest.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<description>GNU Privacy Guard (Private key daemon)</description>
+<assemblyIdentity
+ type="win32"
+ name="GnuPG.gpg-agent"
+ version="@BUILD_VERSION@"
+ />
+<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista -->
+ </application>
+</compatibility>
+</assembly>
diff --git a/agent/learncard.c b/agent/learncard.c
new file mode 100644
index 0000000..2e491e1
--- /dev/null
+++ b/agent/learncard.c
@@ -0,0 +1,452 @@
+/* learncard.c - Handle the LEARN command
+ * Copyright (C) 2002, 2003, 2004, 2009 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+#include <assuan.h>
+
+/* Structures used by the callback mechanism to convey information
+ pertaining to key pairs. */
+struct keypair_info_s
+{
+ struct keypair_info_s *next;
+ int no_cert;
+ char *id; /* points into grip */
+ char hexgrip[1]; /* The keygrip (i.e. a hash over the public key
+ parameters) formatted as a hex string.
+ Allocated somewhat large to also act as
+ memeory for the above ID field. */
+};
+typedef struct keypair_info_s *KEYPAIR_INFO;
+
+struct kpinfo_cb_parm_s
+{
+ ctrl_t ctrl;
+ int error;
+ KEYPAIR_INFO info;
+};
+
+
+/* Structures used by the callback mechanism to convey information
+ pertaining to certificates. */
+struct certinfo_s {
+ struct certinfo_s *next;
+ int type;
+ int done;
+ char id[1];
+};
+typedef struct certinfo_s *CERTINFO;
+
+struct certinfo_cb_parm_s
+{
+ ctrl_t ctrl;
+ int error;
+ CERTINFO info;
+};
+
+
+/* Structures used by the callback mechanism to convey assuan status
+ lines. */
+struct sinfo_s {
+ struct sinfo_s *next;
+ char *data; /* Points into keyword. */
+ char keyword[1];
+};
+typedef struct sinfo_s *SINFO;
+
+struct sinfo_cb_parm_s {
+ int error;
+ SINFO info;
+};
+
+
+/* Destructor for key information objects. */
+static void
+release_keypair_info (KEYPAIR_INFO info)
+{
+ while (info)
+ {
+ KEYPAIR_INFO tmp = info->next;
+ xfree (info);
+ info = tmp;
+ }
+}
+
+/* Destructor for certificate information objects. */
+static void
+release_certinfo (CERTINFO info)
+{
+ while (info)
+ {
+ CERTINFO tmp = info->next;
+ xfree (info);
+ info = tmp;
+ }
+}
+
+/* Destructor for status information objects. */
+static void
+release_sinfo (SINFO info)
+{
+ while (info)
+ {
+ SINFO tmp = info->next;
+ xfree (info);
+ info = tmp;
+ }
+}
+
+
+
+/* This callback is used by agent_card_learn and passed the content of
+ all KEYPAIRINFO lines. It merely stores this data away */
+static void
+kpinfo_cb (void *opaque, const char *line)
+{
+ struct kpinfo_cb_parm_s *parm = opaque;
+ KEYPAIR_INFO item;
+ char *p;
+
+ if (parm->error)
+ return; /* no need to gather data after an error occurred */
+
+ if ((parm->error = agent_write_status (parm->ctrl, "PROGRESS",
+ "learncard", "k", "0", "0", NULL)))
+ return;
+
+ item = xtrycalloc (1, sizeof *item + strlen (line));
+ if (!item)
+ {
+ parm->error = out_of_core ();
+ return;
+ }
+ strcpy (item->hexgrip, line);
+ for (p = item->hexgrip; hexdigitp (p); p++)
+ ;
+ if (p == item->hexgrip && *p == 'X' && spacep (p+1))
+ {
+ item->no_cert = 1;
+ p++;
+ }
+ else if ((p - item->hexgrip) != 40 || !spacep (p))
+ { /* not a 20 byte hex keygrip or not followed by a space */
+ parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
+ xfree (item);
+ return;
+ }
+ *p++ = 0;
+ while (spacep (p))
+ p++;
+ item->id = p;
+ while (*p && !spacep (p))
+ p++;
+ if (p == item->id)
+ { /* invalid ID string */
+ parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
+ xfree (item);
+ return;
+ }
+ *p = 0; /* ignore trailing stuff */
+
+ /* store it */
+ item->next = parm->info;
+ parm->info = item;
+}
+
+
+/* This callback is used by agent_card_learn and passed the content of
+ all CERTINFO lines. It merely stores this data away */
+static void
+certinfo_cb (void *opaque, const char *line)
+{
+ struct certinfo_cb_parm_s *parm = opaque;
+ CERTINFO item;
+ int type;
+ char *p, *pend;
+
+ if (parm->error)
+ return; /* no need to gather data after an error occurred */
+
+ if ((parm->error = agent_write_status (parm->ctrl, "PROGRESS",
+ "learncard", "c", "0", "0", NULL)))
+ return;
+
+ type = strtol (line, &p, 10);
+ while (spacep (p))
+ p++;
+ for (pend = p; *pend && !spacep (pend); pend++)
+ ;
+ if (p == pend || !*p)
+ {
+ parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
+ return;
+ }
+ *pend = 0; /* ignore trailing stuff */
+
+ item = xtrycalloc (1, sizeof *item + strlen (p));
+ if (!item)
+ {
+ parm->error = out_of_core ();
+ return;
+ }
+ item->type = type;
+ strcpy (item->id, p);
+ /* store it */
+ item->next = parm->info;
+ parm->info = item;
+}
+
+
+/* This callback is used by agent_card_learn and passed the content of
+ all SINFO lines. It merely stores this data away */
+static void
+sinfo_cb (void *opaque, const char *keyword, size_t keywordlen,
+ const char *data)
+{
+ struct sinfo_cb_parm_s *sparm = opaque;
+ SINFO item;
+
+ if (sparm->error)
+ return; /* no need to gather data after an error occurred */
+
+ item = xtrycalloc (1, sizeof *item + keywordlen + 1 + strlen (data));
+ if (!item)
+ {
+ sparm->error = out_of_core ();
+ return;
+ }
+ memcpy (item->keyword, keyword, keywordlen);
+ item->data = item->keyword + keywordlen;
+ *item->data = 0;
+ item->data++;
+ strcpy (item->data, data);
+ /* store it */
+ item->next = sparm->info;
+ sparm->info = item;
+}
+
+
+
+static int
+send_cert_back (ctrl_t ctrl, const char *id, void *assuan_context)
+{
+ int rc;
+ char *derbuf;
+ size_t derbuflen;
+
+ rc = agent_card_readcert (ctrl, id, &derbuf, &derbuflen);
+ if (rc)
+ {
+ const char *action;
+
+ switch (gpg_err_code (rc))
+ {
+ case GPG_ERR_INV_ID:
+ case GPG_ERR_NOT_FOUND:
+ action = " - ignored";
+ break;
+ default:
+ action = "";
+ break;
+ }
+ if (opt.verbose || !*action)
+ log_info ("error reading certificate '%s': %s%s\n",
+ id? id:"?", gpg_strerror (rc), action);
+
+ return *action? 0 : rc;
+ }
+
+ rc = assuan_send_data (assuan_context, derbuf, derbuflen);
+ xfree (derbuf);
+ if (!rc)
+ rc = assuan_send_data (assuan_context, NULL, 0);
+ if (!rc)
+ rc = assuan_write_line (assuan_context, "END");
+ if (rc)
+ {
+ log_error ("sending certificate failed: %s\n",
+ gpg_strerror (rc));
+ return rc;
+ }
+ return 0;
+}
+
+/* Perform the learn operation. If ASSUAN_CONTEXT is not NULL and
+ SEND is true all new certificates are send back via Assuan. */
+int
+agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force)
+{
+ int rc;
+ struct kpinfo_cb_parm_s parm;
+ struct certinfo_cb_parm_s cparm;
+ struct sinfo_cb_parm_s sparm;
+ const char *serialno = NULL;
+ KEYPAIR_INFO item;
+ SINFO sitem;
+ unsigned char grip[20];
+ char *p;
+ int i;
+ static int certtype_list[] = {
+ 111, /* Root CA */
+ 101, /* trusted */
+ 102, /* useful */
+ 100, /* regular */
+ /* We don't include 110 here because gpgsm can't handle that
+ special root CA format. */
+ -1 /* end of list */
+ };
+
+
+ memset (&parm, 0, sizeof parm);
+ memset (&cparm, 0, sizeof cparm);
+ memset (&sparm, 0, sizeof sparm);
+ parm.ctrl = ctrl;
+ cparm.ctrl = ctrl;
+
+ /* Now gather all the available info. */
+ rc = agent_card_learn (ctrl, kpinfo_cb, &parm, certinfo_cb, &cparm,
+ sinfo_cb, &sparm);
+ if (!rc && (parm.error || cparm.error || sparm.error))
+ rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error;
+ if (rc)
+ {
+ log_debug ("agent_card_learn failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ /* Pass on all the collected status information. */
+ for (sitem = sparm.info; sitem; sitem = sitem->next)
+ {
+ if (!strcmp (sitem->keyword, "SERIALNO"))
+ serialno = sitem->data;
+ if (assuan_context)
+ assuan_write_status (assuan_context, sitem->keyword, sitem->data);
+ }
+
+ if (!serialno)
+ {
+ rc = GPG_ERR_NOT_FOUND;
+ goto leave;
+ }
+
+ log_info ("card has S/N: %s\n", serialno);
+
+ /* Write out the certificates in a standard order. */
+ for (i=0; certtype_list[i] != -1; i++)
+ {
+ CERTINFO citem;
+ for (citem = cparm.info; citem; citem = citem->next)
+ {
+ if (certtype_list[i] != citem->type)
+ continue;
+
+ if (opt.verbose)
+ log_info (" id: %s (type=%d)\n",
+ citem->id, citem->type);
+
+ if (assuan_context && send)
+ {
+ rc = send_cert_back (ctrl, citem->id, assuan_context);
+ if (rc)
+ goto leave;
+ citem->done = 1;
+ }
+ }
+ }
+
+ for (item = parm.info; item; item = item->next)
+ {
+ unsigned char *pubkey;
+
+ if (opt.verbose)
+ log_info (" id: %s (grip=%s)\n", item->id, item->hexgrip);
+
+ if (item->no_cert)
+ continue; /* No public key yet available. */
+
+ if (assuan_context)
+ {
+ agent_write_status (ctrl, "KEYPAIRINFO",
+ item->hexgrip, item->id, NULL);
+ }
+
+ for (p=item->hexgrip, i=0; i < 20; p += 2, i++)
+ grip[i] = xtoi_2 (p);
+
+ if (!force && !agent_key_available (grip))
+ continue; /* The key is already available. */
+
+ /* Unknown key - store it. */
+ rc = agent_card_readkey (ctrl, item->id, &pubkey);
+ if (rc)
+ {
+ log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ {
+ char *dispserialno;
+
+ agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno);
+ rc = agent_write_shadow_key (0, grip, serialno, item->id, pubkey,
+ force, dispserialno);
+ xfree (dispserialno);
+ }
+ xfree (pubkey);
+ if (rc)
+ goto leave;
+
+ if (opt.verbose)
+ log_info (" id: %s - shadow key created\n", item->id);
+
+ if (assuan_context && send)
+ {
+ CERTINFO citem;
+
+ /* only send the certificate if we have not done so before */
+ for (citem = cparm.info; citem; citem = citem->next)
+ {
+ if (!strcmp (citem->id, item->id))
+ break;
+ }
+ if (!citem)
+ {
+ rc = send_cert_back (ctrl, item->id, assuan_context);
+ if (rc)
+ goto leave;
+ }
+ }
+ }
+
+
+ leave:
+ release_keypair_info (parm.info);
+ release_certinfo (cparm.info);
+ release_sinfo (sparm.info);
+ return rc;
+}
diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
new file mode 100644
index 0000000..6aed96b
--- /dev/null
+++ b/agent/pkdecrypt.c
@@ -0,0 +1,147 @@
+/* pkdecrypt.c - public key decryption (well, actually using a secret key)
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+
+
+/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp.
+ Try to get the key from CTRL and write the decoded stuff back to
+ OUTFP. The padding information is stored at R_PADDING with -1
+ for not known. */
+int
+agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *ciphertext, size_t ciphertextlen,
+ membuf_t *outbuf, int *r_padding)
+{
+ gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL;
+ unsigned char *shadow_info = NULL;
+ int rc;
+ char *buf = NULL;
+ size_t len;
+
+ *r_padding = -1;
+
+ if (!ctrl->have_keygrip)
+ {
+ log_error ("speculative decryption not yet supported\n");
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+
+ rc = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen);
+ if (rc)
+ {
+ log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc));
+ rc = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ {
+ log_printhex (ctrl->keygrip, 20, "keygrip:");
+ log_printhex (ciphertext, ciphertextlen, "cipher: ");
+ }
+ rc = agent_key_from_file (ctrl, NULL, desc_text,
+ ctrl->keygrip, &shadow_info,
+ CACHE_MODE_NORMAL, NULL, &s_skey, NULL);
+ if (rc)
+ {
+ if (gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
+ log_error ("failed to read the secret key\n");
+ goto leave;
+ }
+
+ if (shadow_info)
+ { /* divert operation to the smartcard */
+
+ if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL))
+ {
+ rc = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+
+ rc = divert_pkdecrypt (ctrl, desc_text, ciphertext,
+ ctrl->keygrip, shadow_info,
+ &buf, &len, r_padding);
+ if (rc)
+ {
+ log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ put_membuf_printf (outbuf, "(5:value%u:", (unsigned int)len);
+ put_membuf (outbuf, buf, len);
+ put_membuf (outbuf, ")", 2);
+ }
+ else
+ { /* No smartcard, but a private key */
+/* if (DBG_CRYPTO ) */
+/* { */
+/* log_debug ("skey: "); */
+/* gcry_sexp_dump (s_skey); */
+/* } */
+
+ rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey);
+ if (rc)
+ {
+ log_error ("decryption failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ {
+ log_debug ("plain: ");
+ gcry_sexp_dump (s_plain);
+ }
+ len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xmalloc (len);
+ len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ if (*buf == '(')
+ put_membuf (outbuf, buf, len);
+ else
+ {
+ /* Old style libgcrypt: This is only an S-expression
+ part. Turn it into a complete S-expression. */
+ put_membuf (outbuf, "(5:value", 8);
+ put_membuf (outbuf, buf, len);
+ put_membuf (outbuf, ")", 2);
+ }
+ }
+
+
+ leave:
+ gcry_sexp_release (s_skey);
+ gcry_sexp_release (s_plain);
+ gcry_sexp_release (s_cipher);
+ xfree (buf);
+ xfree (shadow_info);
+ return rc;
+}
diff --git a/agent/pksign.c b/agent/pksign.c
new file mode 100644
index 0000000..09d61b8
--- /dev/null
+++ b/agent/pksign.c
@@ -0,0 +1,572 @@
+/* pksign.c - public key signing (well, actually using a secret key)
+ * Copyright (C) 2001-2004, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2001-2004, 2010, 2013 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+#include "../common/i18n.h"
+
+
+static int
+do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash,
+ int raw_value)
+{
+ gcry_sexp_t hash;
+ int rc;
+
+ if (!raw_value)
+ {
+ const char *s;
+ char tmp[16+1];
+ int i;
+
+ s = gcry_md_algo_name (algo);
+ if (s && strlen (s) < 16)
+ {
+ for (i=0; i < strlen (s); i++)
+ tmp[i] = tolower (s[i]);
+ tmp[i] = '\0';
+ }
+
+ rc = gcry_sexp_build (&hash, NULL,
+ "(data (flags pkcs1) (hash %s %b))",
+ tmp, (int)mdlen, md);
+ }
+ else
+ {
+ gcry_mpi_t mpi;
+
+ rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL);
+ if (!rc)
+ {
+ rc = gcry_sexp_build (&hash, NULL,
+ "(data (flags raw) (value %m))",
+ mpi);
+ gcry_mpi_release (mpi);
+ }
+ else
+ hash = NULL;
+
+ }
+
+ *r_hash = hash;
+ return rc;
+}
+
+
+/* Return the number of bits of the Q parameter from the DSA key
+ KEY. */
+static unsigned int
+get_dsa_qbits (gcry_sexp_t key)
+{
+ gcry_sexp_t l1, l2;
+ gcry_mpi_t q;
+ unsigned int nbits;
+
+ l1 = gcry_sexp_find_token (key, "private-key", 0);
+ if (!l1)
+ l1 = gcry_sexp_find_token (key, "protected-private-key", 0);
+ if (!l1)
+ l1 = gcry_sexp_find_token (key, "shadowed-private-key", 0);
+ if (!l1)
+ l1 = gcry_sexp_find_token (key, "public-key", 0);
+ if (!l1)
+ return 0; /* Does not contain a key object. */
+ l2 = gcry_sexp_cadr (l1);
+ gcry_sexp_release (l1);
+ l1 = gcry_sexp_find_token (l2, "q", 1);
+ gcry_sexp_release (l2);
+ if (!l1)
+ return 0; /* Invalid object. */
+ q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release (l1);
+ if (!q)
+ return 0; /* Missing value. */
+ nbits = gcry_mpi_get_nbits (q);
+ gcry_mpi_release (q);
+
+ return nbits;
+}
+
+
+/* Return an appropriate hash algorithm to be used with RFC-6979 for a
+ message digest of length MDLEN. Although a fallback of SHA-256 is
+ used the current implementation in Libgcrypt will reject a hash
+ algorithm which does not match the length of the message. */
+static const char *
+rfc6979_hash_algo_string (size_t mdlen)
+{
+ switch (mdlen)
+ {
+ case 20: return "sha1";
+ case 28: return "sha224";
+ case 32: return "sha256";
+ case 48: return "sha384";
+ case 64: return "sha512";
+ default: return "sha256";
+ }
+}
+
+
+/* Encode a message digest for use with the EdDSA algorithm
+ (i.e. curve Ed25519). */
+static gpg_error_t
+do_encode_eddsa (const byte *md, size_t mdlen, gcry_sexp_t *r_hash)
+{
+ gpg_error_t err;
+ gcry_sexp_t hash;
+
+ *r_hash = NULL;
+ err = gcry_sexp_build (&hash, NULL,
+ "(data(flags eddsa)(hash-algo sha512)(value %b))",
+ (int)mdlen, md);
+ if (!err)
+ *r_hash = hash;
+ return err;
+}
+
+
+/* Encode a message digest for use with an DSA algorithm. */
+static gpg_error_t
+do_encode_dsa (const byte *md, size_t mdlen, int pkalgo, gcry_sexp_t pkey,
+ gcry_sexp_t *r_hash)
+{
+ gpg_error_t err;
+ gcry_sexp_t hash;
+ unsigned int qbits;
+
+ *r_hash = NULL;
+
+ if (pkalgo == GCRY_PK_ECDSA)
+ qbits = gcry_pk_get_nbits (pkey);
+ else if (pkalgo == GCRY_PK_DSA)
+ qbits = get_dsa_qbits (pkey);
+ else
+ return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
+
+ if (pkalgo == GCRY_PK_DSA && (qbits%8))
+ {
+ /* FIXME: We check the QBITS but print a message about the hash
+ length. */
+ log_error (_("DSA requires the hash length to be a"
+ " multiple of 8 bits\n"));
+ return gpg_error (GPG_ERR_INV_LENGTH);
+ }
+
+ /* Don't allow any Q smaller than 160 bits. We don't want someone
+ to issue signatures from a key with a 16-bit Q or something like
+ that, which would look correct but allow trivial forgeries. Yes,
+ I know this rules out using MD5 with DSA. ;) */
+ if (qbits < 160)
+ {
+ log_error (_("%s key uses an unsafe (%u bit) hash\n"),
+ gcry_pk_algo_name (pkalgo), qbits);
+ return gpg_error (GPG_ERR_INV_LENGTH);
+ }
+
+ /* ECDSA 521 is special has it is larger than the largest hash
+ we have (SHA-512). Thus we change the size for further
+ processing to 512. */
+ if (pkalgo == GCRY_PK_ECDSA && qbits > 512)
+ qbits = 512;
+
+ /* Check if we're too short. Too long is safe as we'll
+ automatically left-truncate. */
+ if (mdlen < qbits/8)
+ {
+ log_error (_("a %zu bit hash is not valid for a %u bit %s key\n"),
+ mdlen*8,
+ gcry_pk_get_nbits (pkey),
+ gcry_pk_algo_name (pkalgo));
+ return gpg_error (GPG_ERR_INV_LENGTH);
+ }
+
+ /* Truncate. */
+ if (mdlen > qbits/8)
+ mdlen = qbits/8;
+
+ /* Create the S-expression. */
+ err = gcry_sexp_build (&hash, NULL,
+ "(data (flags rfc6979) (hash %s %b))",
+ rfc6979_hash_algo_string (mdlen),
+ (int)mdlen, md);
+ if (!err)
+ *r_hash = hash;
+ return err;
+}
+
+
+/* Special version of do_encode_md to take care of pkcs#1 padding.
+ For TLS-MD5SHA1 we need to do the padding ourself as Libgrypt does
+ not know about this special scheme. Fixme: We should have a
+ pkcs1-only-padding flag for Libgcrypt. */
+static int
+do_encode_raw_pkcs1 (const byte *md, size_t mdlen, unsigned int nbits,
+ gcry_sexp_t *r_hash)
+{
+ int rc;
+ gcry_sexp_t hash;
+ unsigned char *frame;
+ size_t i, n, nframe;
+
+ nframe = (nbits+7) / 8;
+ if ( !mdlen || mdlen + 8 + 4 > nframe )
+ {
+ /* Can't encode this hash into a frame of size NFRAME. */
+ return gpg_error (GPG_ERR_TOO_SHORT);
+ }
+
+ frame = xtrymalloc (nframe);
+ if (!frame)
+ return gpg_error_from_syserror ();
+
+ /* Assemble the pkcs#1 block type 1. */
+ n = 0;
+ frame[n++] = 0;
+ frame[n++] = 1; /* Block type. */
+ i = nframe - mdlen - 3 ;
+ assert (i >= 8); /* At least 8 bytes of padding. */
+ memset (frame+n, 0xff, i );
+ n += i;
+ frame[n++] = 0;
+ memcpy (frame+n, md, mdlen );
+ n += mdlen;
+ assert (n == nframe);
+
+ /* Create the S-expression. */
+ rc = gcry_sexp_build (&hash, NULL,
+ "(data (flags raw) (value %b))",
+ (int)nframe, frame);
+ xfree (frame);
+
+ *r_hash = hash;
+ return rc;
+}
+
+
+
+/* SIGN whatever information we have accumulated in CTRL and return
+ * the signature S-expression. LOOKUP is an optional function to
+ * provide a way for lower layers to ask for the caching TTL. If a
+ * CACHE_NONCE is given that cache item is first tried to get a
+ * passphrase. If OVERRIDEDATA is not NULL, OVERRIDEDATALEN bytes
+ * from this buffer are used instead of the data in CTRL. The
+ * override feature is required to allow the use of Ed25519 with ssh
+ * because Ed25519 does the hashing itself. */
+gpg_error_t
+agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
+ const char *desc_text,
+ gcry_sexp_t *signature_sexp,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
+ const void *overridedata, size_t overridedatalen)
+{
+ gpg_error_t err = 0;
+ gcry_sexp_t s_skey = NULL;
+ gcry_sexp_t s_sig = NULL;
+ gcry_sexp_t s_hash = NULL;
+ gcry_sexp_t s_pkey = NULL;
+ unsigned char *shadow_info = NULL;
+ const unsigned char *data;
+ int datalen;
+ int check_signature = 0;
+
+ if (overridedata)
+ {
+ data = overridedata;
+ datalen = overridedatalen;
+ }
+ else
+ {
+ data = ctrl->digest.value;
+ datalen = ctrl->digest.valuelen;
+ }
+
+ if (!ctrl->have_keygrip)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ err = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip,
+ &shadow_info, cache_mode, lookup_ttl,
+ &s_skey, NULL);
+ if (err)
+ {
+ if (gpg_err_code (err) != GPG_ERR_NO_SECKEY)
+ log_error ("failed to read the secret key\n");
+ goto leave;
+ }
+
+ if (shadow_info)
+ {
+ /* Divert operation to the smartcard */
+ size_t len;
+ unsigned char *buf = NULL;
+ int key_type;
+ int is_RSA = 0;
+ int is_ECDSA = 0;
+ int is_EdDSA = 0;
+
+ err = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey);
+ if (err)
+ {
+ log_error ("failed to read the public key\n");
+ goto leave;
+ }
+
+ if (agent_is_eddsa_key (s_skey))
+ is_EdDSA = 1;
+ else
+ {
+ key_type = agent_is_dsa_key (s_skey);
+ if (key_type == 0)
+ is_RSA = 1;
+ else if (key_type == GCRY_PK_ECDSA)
+ is_ECDSA = 1;
+ }
+
+ {
+ char *desc2 = NULL;
+
+ if (desc_text)
+ agent_modify_description (desc_text, NULL, s_skey, &desc2);
+
+ err = divert_pksign (ctrl, desc2? desc2 : desc_text,
+ data, datalen,
+ ctrl->digest.algo,
+ ctrl->keygrip,
+ shadow_info, &buf, &len);
+ xfree (desc2);
+ }
+ if (err)
+ {
+ log_error ("smartcard signing failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ if (is_RSA)
+ {
+ check_signature = 1;
+ if (*buf & 0x80)
+ {
+ len++;
+ buf = xtryrealloc (buf, len);
+ if (!buf)
+ goto leave;
+
+ memmove (buf + 1, buf, len - 1);
+ *buf = 0;
+ }
+
+ err = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))",
+ (int)len, buf);
+ }
+ else if (is_EdDSA)
+ {
+ err = gcry_sexp_build (&s_sig, NULL, "(sig-val(eddsa(r%b)(s%b)))",
+ (int)len/2, buf, (int)len/2, buf + len/2);
+ }
+ else if (is_ECDSA)
+ {
+ unsigned char *r_buf_allocated = NULL;
+ unsigned char *s_buf_allocated = NULL;
+ unsigned char *r_buf, *s_buf;
+ int r_buflen, s_buflen;
+
+ r_buflen = s_buflen = len/2;
+
+ if (*buf & 0x80)
+ {
+ r_buflen++;
+ r_buf_allocated = xtrymalloc (r_buflen);
+ if (!r_buf_allocated)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ r_buf = r_buf_allocated;
+ memcpy (r_buf + 1, buf, len/2);
+ *r_buf = 0;
+ }
+ else
+ r_buf = buf;
+
+ if (*(buf + len/2) & 0x80)
+ {
+ s_buflen++;
+ s_buf_allocated = xtrymalloc (s_buflen);
+ if (!s_buf_allocated)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (r_buf_allocated);
+ goto leave;
+ }
+
+ s_buf = s_buf_allocated;
+ memcpy (s_buf + 1, buf + len/2, len/2);
+ *s_buf = 0;
+ }
+ else
+ s_buf = buf + len/2;
+
+ err = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))",
+ r_buflen, r_buf,
+ s_buflen, s_buf);
+ xfree (r_buf_allocated);
+ xfree (s_buf_allocated);
+ }
+ else
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ xfree (buf);
+ if (err)
+ {
+ log_error ("failed to convert sigbuf returned by divert_pksign "
+ "into S-Exp: %s", gpg_strerror (err));
+ goto leave;
+ }
+ }
+ else
+ {
+ /* No smartcard, but a private key */
+ int dsaalgo = 0;
+
+ /* Put the hash into a sexp */
+ if (agent_is_eddsa_key (s_skey))
+ err = do_encode_eddsa (data, datalen,
+ &s_hash);
+ else if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
+ err = do_encode_raw_pkcs1 (data, datalen,
+ gcry_pk_get_nbits (s_skey),
+ &s_hash);
+ else if ( (dsaalgo = agent_is_dsa_key (s_skey)) )
+ err = do_encode_dsa (data, datalen,
+ dsaalgo, s_skey,
+ &s_hash);
+ else
+ err = do_encode_md (data, datalen,
+ ctrl->digest.algo,
+ &s_hash,
+ ctrl->digest.raw_value);
+ if (err)
+ goto leave;
+
+ if (DBG_CRYPTO)
+ {
+ gcry_log_debugsxp ("skey", s_skey);
+ gcry_log_debugsxp ("hash", s_hash);
+ }
+
+ /* sign */
+ err = gcry_pk_sign (&s_sig, s_hash, s_skey);
+ if (err)
+ {
+ log_error ("signing failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ gcry_log_debugsxp ("rslt", s_sig);
+ }
+
+ /* Check that the signature verification worked and nothing is
+ * fooling us e.g. by a bug in the signature create code or by
+ * deliberately introduced faults. Because Libgcrypt 1.7 does this
+ * for RSA internally there is no need to do it here again. */
+ if (check_signature)
+ {
+ gcry_sexp_t sexp_key = s_pkey? s_pkey: s_skey;
+
+ if (s_hash == NULL)
+ {
+ if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
+ err = do_encode_raw_pkcs1 (data, datalen,
+ gcry_pk_get_nbits (sexp_key), &s_hash);
+ else
+ err = do_encode_md (data, datalen, ctrl->digest.algo, &s_hash,
+ ctrl->digest.raw_value);
+ }
+
+ if (!err)
+ err = gcry_pk_verify (s_sig, s_hash, sexp_key);
+
+ if (err)
+ {
+ log_error (_("checking created signature failed: %s\n"),
+ gpg_strerror (err));
+ gcry_sexp_release (s_sig);
+ s_sig = NULL;
+ }
+ }
+
+ leave:
+
+ *signature_sexp = s_sig;
+
+ gcry_sexp_release (s_pkey);
+ gcry_sexp_release (s_skey);
+ gcry_sexp_release (s_hash);
+ xfree (shadow_info);
+
+ return err;
+}
+
+
+/* SIGN whatever information we have accumulated in CTRL and write it
+ * back to OUTFP. If a CACHE_NONCE is given that cache item is first
+ * tried to get a passphrase. */
+gpg_error_t
+agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
+ membuf_t *outbuf, cache_mode_t cache_mode)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_sig = NULL;
+ char *buf = NULL;
+ size_t len = 0;
+
+ err = agent_pksign_do (ctrl, cache_nonce, desc_text, &s_sig, cache_mode,
+ NULL, NULL, 0);
+ if (err)
+ goto leave;
+
+ len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0);
+ log_assert (len);
+ buf = xtrymalloc (len);
+ if (!buf)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len);
+ log_assert (len);
+ put_membuf (outbuf, buf, len);
+
+ leave:
+ gcry_sexp_release (s_sig);
+ xfree (buf);
+
+ return err;
+}
diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c
new file mode 100644
index 0000000..c5aeafe
--- /dev/null
+++ b/agent/preset-passphrase.c
@@ -0,0 +1,272 @@
+/* preset-passphrase.c - A tool to preset a passphrase.
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef HAVE_LANGINFO_CODESET
+#include <langinfo.h>
+#endif
+#ifdef HAVE_DOSISH_SYSTEM
+#include <fcntl.h> /* for setmode() */
+#endif
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h> /* To initialize the sockets. fixme */
+#endif
+
+#define INCLUDED_BY_MAIN_MODULE 1
+#include "agent.h"
+#include "../common/simple-pwquery.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/init.h"
+
+
+enum cmd_and_opt_values
+{ aNull = 0,
+ oVerbose = 'v',
+ oPassphrase = 'P',
+
+ oPreset = 'c',
+ oForget = 'f',
+
+ oNoVerbose = 500,
+
+ oHomedir,
+
+aTest };
+
+
+static const char *opt_passphrase;
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 301, NULL, 0, N_("@Options:\n ") },
+
+ { oVerbose, "verbose", 0, "verbose" },
+ { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" },
+ { oPreset, "preset", 256, "preset passphrase"},
+ { oForget, "forget", 256, "forget passphrase"},
+
+ { oHomedir, "homedir", 2, "@" },
+
+ ARGPARSE_end ()
+};
+
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 9: p = "GPL-3.0-or-later"; break;
+ case 11: p = "gpg-preset-passphrase (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 14: p = GNUPG_DEF_COPYRIGHT_LINE; 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-preset-passphrase [options] KEYGRIP (-h for help)\n");
+ break;
+ case 41:
+ p = _("Syntax: gpg-preset-passphrase [options] KEYGRIP\n"
+ "Password cache maintenance\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+
+static void
+preset_passphrase (const char *keygrip)
+{
+ int rc;
+ char *line;
+ /* FIXME: Use secure memory. */
+ char passphrase[500];
+ char *passphrase_esc;
+
+ if (!opt_passphrase)
+ {
+ rc = read (0, passphrase, sizeof (passphrase) - 1);
+ if (rc < 0)
+ {
+ log_error ("reading passphrase failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return;
+ }
+ passphrase[rc] = '\0';
+ line = strchr (passphrase, '\n');
+ if (line)
+ {
+ if (line > passphrase && line[-1] == '\r')
+ line--;
+ *line = '\0';
+ }
+
+ /* FIXME: How to handle empty passwords? */
+ }
+
+ {
+ const char *s = opt_passphrase ? opt_passphrase : passphrase;
+ passphrase_esc = bin2hex (s, strlen (s), NULL);
+ }
+ if (!passphrase_esc)
+ {
+ log_error ("can not escape string: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return;
+ }
+
+ rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip,
+ passphrase_esc);
+ wipememory (passphrase_esc, strlen (passphrase_esc));
+ xfree (passphrase_esc);
+
+ if (rc < 0)
+ {
+ log_error ("caching passphrase failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return;
+ }
+ if (!opt_passphrase)
+ wipememory (passphrase, sizeof (passphrase));
+
+ rc = simple_query (line);
+ if (rc)
+ {
+ log_error ("caching passphrase failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+
+ wipememory (line, strlen (line));
+ xfree (line);
+}
+
+
+static void
+forget_passphrase (const char *keygrip)
+{
+ int rc;
+ char *line;
+
+ rc = asprintf (&line, "CLEAR_PASSPHRASE %s\n", keygrip);
+ if (rc < 0)
+ rc = gpg_error_from_syserror ();
+ else
+ rc = simple_query (line);
+ if (rc)
+ {
+ log_error ("clearing passphrase failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+
+ xfree (line);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ ARGPARSE_ARGS pargs;
+ int cmd = 0;
+ const char *keygrip = NULL;
+
+ early_system_init ();
+ set_strusage (my_strusage);
+ log_set_prefix ("gpg-preset-passphrase", GPGRT_LOG_WITH_PREFIX);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= ARGPARSE_FLAG_KEEP;
+ while (gnupg_argparse (NULL, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
+
+ case oPreset: cmd = oPreset; break;
+ case oForget: cmd = oForget; break;
+ case oPassphrase: opt_passphrase = pargs.r.ret_str; break;
+
+ default : pargs.err = 2; break;
+ }
+ }
+ gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */
+ if (log_get_errorcount(0))
+ exit(2);
+
+ if (argc == 1)
+ keygrip = *argv;
+ else
+ usage (1);
+
+ /* Tell simple-pwquery about the standard socket name. */
+ {
+ char *tmp = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
+ simple_pw_set_socket (tmp);
+ xfree (tmp);
+ }
+
+ if (cmd == oPreset)
+ preset_passphrase (keygrip);
+ else if (cmd == oForget)
+ forget_passphrase (keygrip);
+ else
+ log_error ("one of the options --preset or --forget must be given\n");
+
+ agent_exit (0);
+ return 8; /*NOTREACHED*/
+}
+
+
+void
+agent_exit (int rc)
+{
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
new file mode 100644
index 0000000..fb2c71d
--- /dev/null
+++ b/agent/protect-tool.c
@@ -0,0 +1,837 @@
+/* protect-tool.c - A tool to test the secret key protection
+ * Copyright (C) 2002, 2003, 2004, 2006 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef HAVE_LANGINFO_CODESET
+#include <langinfo.h>
+#endif
+#ifdef HAVE_DOSISH_SYSTEM
+#include <fcntl.h> /* for setmode() */
+#endif
+
+#define INCLUDED_BY_MAIN_MODULE 1
+#include "agent.h"
+#include "../common/i18n.h"
+#include "../common/get-passphrase.h"
+#include "../common/sysutils.h"
+#include "../common/init.h"
+
+
+enum cmd_and_opt_values
+{
+ aNull = 0,
+ oVerbose = 'v',
+ oArmor = 'a',
+ oPassphrase = 'P',
+
+ oProtect = 'p',
+ oUnprotect = 'u',
+
+ oNoVerbose = 500,
+ oShadow,
+ oShowShadowInfo,
+ oShowKeygrip,
+ oS2Kcalibration,
+ oCanonical,
+
+ oStore,
+ oForce,
+ oHaveCert,
+ oNoFailOnExist,
+ oHomedir,
+ oPrompt,
+ oStatusMsg,
+ oDebugUseOCB,
+
+ oAgentProgram
+};
+
+
+struct rsa_secret_key_s
+{
+ gcry_mpi_t n; /* public modulus */
+ gcry_mpi_t e; /* public exponent */
+ gcry_mpi_t d; /* exponent */
+ gcry_mpi_t p; /* prime p. */
+ gcry_mpi_t q; /* prime q. */
+ gcry_mpi_t u; /* inverse of p mod q. */
+};
+
+
+static int opt_armor;
+static int opt_canonical;
+static int opt_store;
+static int opt_force;
+static int opt_no_fail_on_exist;
+static int opt_have_cert;
+static const char *opt_passphrase;
+static char *opt_prompt;
+static int opt_status_msg;
+static const char *opt_agent_program;
+static int opt_debug_use_ocb;
+
+static char *get_passphrase (int promptno);
+static void release_passphrase (char *pw);
+
+
+static ARGPARSE_OPTS opts[] = {
+ ARGPARSE_group (300, N_("@Commands:\n ")),
+
+ ARGPARSE_c (oProtect, "protect", "protect a private key"),
+ ARGPARSE_c (oUnprotect, "unprotect", "unprotect a private key"),
+ ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"),
+ ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"),
+ ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""),
+ ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"),
+
+ ARGPARSE_group (301, N_("@\nOptions:\n ")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", "verbose"),
+ ARGPARSE_s_n (oArmor, "armor", "write output in advanced format"),
+ ARGPARSE_s_n (oCanonical, "canonical", "write output in canonical format"),
+
+ ARGPARSE_s_s (oPassphrase, "passphrase", "|STRING|use passphrase STRING"),
+ ARGPARSE_s_n (oHaveCert, "have-cert",
+ "certificate to export provided on STDIN"),
+ ARGPARSE_s_n (oStore, "store",
+ "store the created key in the appropriate place"),
+ ARGPARSE_s_n (oForce, "force",
+ "force overwriting"),
+ ARGPARSE_s_n (oNoFailOnExist, "no-fail-on-exist", "@"),
+ ARGPARSE_s_s (oHomedir, "homedir", "@"),
+ ARGPARSE_s_s (oPrompt, "prompt",
+ "|ESCSTRING|use ESCSTRING as prompt in pinentry"),
+ ARGPARSE_s_n (oStatusMsg, "enable-status-msg", "@"),
+
+ ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
+
+ ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */
+
+ ARGPARSE_end ()
+};
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 9: p = "GPL-3.0-or-later"; break;
+ case 11: p = "gpg-protect-tool (" GNUPG_NAME ")";
+ break;
+ case 13: p = VERSION; break;
+ case 14: p = GNUPG_DEF_COPYRIGHT_LINE; 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-protect-tool [options] (-h for help)\n");
+ break;
+ case 41: p = _("Syntax: gpg-protect-tool [options] [args]\n"
+ "Secret key maintenance tool\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+/* static void */
+/* print_mpi (const char *text, gcry_mpi_t a) */
+/* { */
+/* char *buf; */
+/* void *bufaddr = &buf; */
+/* int rc; */
+
+/* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */
+/* if (rc) */
+/* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */
+/* else */
+/* { */
+/* log_info ("%s: %s\n", text, buf); */
+/* gcry_free (buf); */
+/* } */
+/* } */
+
+
+
+static unsigned char *
+make_canonical (const char *fname, const char *buf, size_t buflen)
+{
+ int rc;
+ size_t erroff, len;
+ gcry_sexp_t sexp;
+ unsigned char *result;
+
+ rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
+ if (rc)
+ {
+ log_error ("invalid S-Expression in '%s' (off=%u): %s\n",
+ fname, (unsigned int)erroff, gpg_strerror (rc));
+ return NULL;
+ }
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ result = xmalloc (len);
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len);
+ assert (len);
+ gcry_sexp_release (sexp);
+ return result;
+}
+
+static char *
+make_advanced (const unsigned char *buf, size_t buflen)
+{
+ int rc;
+ size_t erroff, len;
+ gcry_sexp_t sexp;
+ char *result;
+
+ rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen);
+ if (rc)
+ {
+ log_error ("invalid canonical S-Expression (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ return NULL;
+ }
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
+ assert (len);
+ result = xmalloc (len);
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
+ assert (len);
+ gcry_sexp_release (sexp);
+ return result;
+}
+
+
+static char *
+read_file (const char *fname, size_t *r_length)
+{
+ FILE *fp;
+ char *buf;
+ size_t buflen;
+
+ if (!strcmp (fname, "-"))
+ {
+ size_t nread, bufsize = 0;
+
+ fp = stdin;
+#ifdef HAVE_DOSISH_SYSTEM
+ setmode ( fileno(fp) , O_BINARY );
+#endif
+ buf = NULL;
+ buflen = 0;
+#define NCHUNK 8192
+ do
+ {
+ bufsize += NCHUNK;
+ if (!buf)
+ buf = xmalloc (bufsize);
+ else
+ buf = xrealloc (buf, bufsize);
+
+ nread = fread (buf+buflen, 1, NCHUNK, fp);
+ if (nread < NCHUNK && ferror (fp))
+ {
+ log_error ("error reading '[stdin]': %s\n", strerror (errno));
+ xfree (buf);
+ return NULL;
+ }
+ buflen += nread;
+ }
+ while (nread == NCHUNK);
+#undef NCHUNK
+
+ }
+ else
+ {
+ struct stat st;
+
+ fp = gnupg_fopen (fname, "rb");
+ if (!fp)
+ {
+ log_error ("can't open '%s': %s\n", fname, strerror (errno));
+ return NULL;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ log_error ("can't stat '%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ return NULL;
+ }
+
+ buflen = st.st_size;
+ buf = xmalloc (buflen+1);
+ if (fread (buf, buflen, 1, fp) != 1)
+ {
+ log_error ("error reading '%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ xfree (buf);
+ return NULL;
+ }
+ fclose (fp);
+ }
+
+ *r_length = buflen;
+ return buf;
+}
+
+
+static unsigned char *
+read_key (const char *fname)
+{
+ char *buf;
+ size_t buflen;
+ unsigned char *key;
+
+ buf = read_file (fname, &buflen);
+ if (!buf)
+ return NULL;
+ if (buflen >= 4 && !memcmp (buf, "Key:", 4))
+ {
+ log_error ("Extended key format is not supported by this tool\n");
+ return NULL;
+ }
+ key = make_canonical (fname, buf, buflen);
+ xfree (buf);
+ return key;
+}
+
+
+
+static void
+read_and_protect (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ unsigned char *result;
+ size_t resultlen;
+ char *pw;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ pw = get_passphrase (1);
+ rc = agent_protect (key, pw, &result, &resultlen, 0,
+ opt_debug_use_ocb? 1 : -1);
+ release_passphrase (pw);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("protecting the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = (unsigned char*)p;
+ resultlen = strlen (p);
+ }
+
+ fwrite (result, resultlen, 1, stdout);
+ xfree (result);
+}
+
+
+static void
+read_and_unprotect (ctrl_t ctrl, const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ unsigned char *result;
+ size_t resultlen;
+ char *pw;
+ gnupg_isotime_t protected_at;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_unprotect (ctrl, key, (pw=get_passphrase (1)),
+ protected_at, &result, &resultlen);
+ release_passphrase (pw);
+ xfree (key);
+ if (rc)
+ {
+ if (opt_status_msg)
+ log_info ("[PROTECT-TOOL:] bad-passphrase\n");
+ log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+ if (opt.verbose)
+ {
+ if (*protected_at)
+ log_info ("key protection done at %.4s-%.2s-%.2s %.2s:%.2s:%s\n",
+ protected_at, protected_at+4, protected_at+6,
+ protected_at+9, protected_at+11, protected_at+13);
+ else
+ log_info ("key protection done at [unknown]\n");
+ }
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = (unsigned char*)p;
+ resultlen = strlen (p);
+ }
+
+ fwrite (result, resultlen, 1, stdout);
+ xfree (result);
+}
+
+
+
+static void
+read_and_shadow (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ unsigned char *result;
+ size_t resultlen;
+ unsigned char dummy_info[] = "(8:313233342:43)";
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_shadow_key (key, dummy_info, &result);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+ resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL);
+ assert (resultlen);
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = (unsigned char*)p;
+ resultlen = strlen (p);
+ }
+
+ fwrite (result, resultlen, 1, stdout);
+ xfree (result);
+}
+
+static void
+show_shadow_info (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ const unsigned char *info;
+ size_t infolen;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_get_shadow_info (key, &info);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+ infolen = gcry_sexp_canon_len (info, 0, NULL,NULL);
+ assert (infolen);
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (info, infolen);
+ if (!p)
+ return;
+ fwrite (p, strlen (p), 1, stdout);
+ xfree (p);
+ }
+ else
+ fwrite (info, infolen, 1, stdout);
+}
+
+
+static void
+show_file (const char *fname)
+{
+ unsigned char *key;
+ size_t keylen;
+ char *p;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ keylen = gcry_sexp_canon_len (key, 0, NULL,NULL);
+ assert (keylen);
+
+ if (opt_canonical)
+ {
+ fwrite (key, keylen, 1, stdout);
+ }
+ else
+ {
+ p = make_advanced (key, keylen);
+ if (p)
+ {
+ fwrite (p, strlen (p), 1, stdout);
+ xfree (p);
+ }
+ }
+ xfree (key);
+}
+
+static void
+show_keygrip (const char *fname)
+{
+ unsigned char *key;
+ gcry_sexp_t private;
+ unsigned char grip[20];
+ int i;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ if (gcry_sexp_new (&private, key, 0, 0))
+ {
+ log_error ("gcry_sexp_new failed\n");
+ return;
+ }
+ xfree (key);
+
+ if (!gcry_pk_get_keygrip (private, grip))
+ {
+ log_error ("can't calculate keygrip\n");
+ return;
+ }
+ gcry_sexp_release (private);
+
+ for (i=0; i < 20; i++)
+ printf ("%02X", grip[i]);
+ putchar ('\n');
+}
+
+
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int cmd = 0;
+ const char *fname;
+ ctrl_t ctrl;
+
+ early_system_init ();
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ log_set_prefix ("gpg-protect-tool", GPGRT_LOG_WITH_PREFIX);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+
+ setup_libgcrypt_logging ();
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= ARGPARSE_FLAG_KEEP;
+ while (gnupg_argparse (NULL, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oArmor: opt_armor=1; break;
+ case oCanonical: opt_canonical=1; break;
+ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
+
+ case oAgentProgram: opt_agent_program = pargs.r.ret_str; break;
+
+ case oProtect: cmd = oProtect; break;
+ case oUnprotect: cmd = oUnprotect; break;
+ case oShadow: cmd = oShadow; break;
+ case oShowShadowInfo: cmd = oShowShadowInfo; break;
+ case oShowKeygrip: cmd = oShowKeygrip; break;
+ case oS2Kcalibration: cmd = oS2Kcalibration; break;
+
+ case oPassphrase: opt_passphrase = pargs.r.ret_str; break;
+ case oStore: opt_store = 1; break;
+ case oForce: opt_force = 1; break;
+ case oNoFailOnExist: opt_no_fail_on_exist = 1; break;
+ case oHaveCert: opt_have_cert = 1; break;
+ case oPrompt: opt_prompt = pargs.r.ret_str; break;
+ case oStatusMsg: opt_status_msg = 1; break;
+ case oDebugUseOCB: opt_debug_use_ocb = 1; break;
+
+ default: pargs.err = ARGPARSE_PRINT_ERROR; break;
+ }
+ }
+ gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */
+
+ if (log_get_errorcount (0))
+ exit (2);
+
+ fname = "-";
+ if (argc == 1)
+ fname = *argv;
+ else if (argc > 1)
+ usage (1);
+
+ /* Allocate an CTRL object. An empty object should be sufficient. */
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (!ctrl)
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno));
+ agent_exit (1);
+ }
+
+ /* Set the information which can't be taken from envvars. */
+ gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT,
+ opt.verbose,
+ opt_agent_program,
+ NULL, NULL, NULL);
+
+ if (opt_prompt)
+ opt_prompt = percent_plus_unescape (opt_prompt, 0);
+
+ if (cmd == oProtect)
+ read_and_protect (fname);
+ else if (cmd == oUnprotect)
+ read_and_unprotect (ctrl, fname);
+ else if (cmd == oShadow)
+ read_and_shadow (fname);
+ else if (cmd == oShowShadowInfo)
+ show_shadow_info (fname);
+ else if (cmd == oShowKeygrip)
+ show_keygrip (fname);
+ else if (cmd == oS2Kcalibration)
+ {
+ if (!opt.verbose)
+ opt.verbose++; /* We need to see something. */
+ get_standard_s2k_count ();
+ }
+ else
+ show_file (fname);
+
+ xfree (ctrl);
+
+ agent_exit (0);
+ return 8; /*NOTREACHED*/
+}
+
+void
+agent_exit (int rc)
+{
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
+
+
+/* Return the passphrase string and ask the agent if it has not been
+ set from the command line PROMPTNO select the prompt to display:
+ 0 = default
+ 1 = taken from the option --prompt
+ 2 = for unprotecting a pkcs#12 object
+ 3 = for protecting a new pkcs#12 object
+ 4 = for protecting an imported pkcs#12 in our system
+*/
+static char *
+get_passphrase (int promptno)
+{
+ char *pw;
+ int err;
+ const char *desc;
+ char *orig_codeset;
+ int repeat = 0;
+
+ if (opt_passphrase)
+ return xstrdup (opt_passphrase);
+
+ orig_codeset = i18n_switchto_utf8 ();
+
+ if (promptno == 1 && opt_prompt)
+ {
+ desc = opt_prompt;
+ }
+ else if (promptno == 2)
+ {
+ desc = _("Please enter the passphrase to unprotect the "
+ "PKCS#12 object.");
+ }
+ else if (promptno == 3)
+ {
+ desc = _("Please enter the passphrase to protect the "
+ "new PKCS#12 object.");
+ repeat = 1;
+ }
+ else if (promptno == 4)
+ {
+ desc = _("Please enter the passphrase to protect the "
+ "imported object within the GnuPG system.");
+ repeat = 1;
+ }
+ else
+ desc = _("Please enter the passphrase or the PIN\n"
+ "needed to complete this operation.");
+
+ i18n_switchback (orig_codeset);
+
+ err = gnupg_get_passphrase (NULL, NULL, _("Passphrase:"), desc,
+ repeat, repeat, 1, &pw);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_CANCELED
+ || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
+ log_info (_("cancelled\n"));
+ else
+ log_error (_("error while asking for the passphrase: %s\n"),
+ gpg_strerror (err));
+ agent_exit (0);
+ }
+ assert (pw);
+
+ return pw;
+}
+
+
+static void
+release_passphrase (char *pw)
+{
+ if (pw)
+ {
+ wipememory (pw, strlen (pw));
+ xfree (pw);
+ }
+}
+
+
+/* Stub function. */
+int
+agent_key_available (const unsigned char *grip)
+{
+ (void)grip;
+ return -1; /* Not available. */
+}
+
+char *
+agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode)
+{
+ (void)ctrl;
+ (void)key;
+ (void)cache_mode;
+ return NULL;
+}
+
+gpg_error_t
+agent_askpin (ctrl_t ctrl,
+ const char *desc_text, const char *prompt_text,
+ const char *initial_errtext,
+ struct pin_entry_info_s *pininfo,
+ const char *keyinfo, cache_mode_t cache_mode)
+{
+ gpg_error_t err;
+ unsigned char *passphrase;
+ size_t size;
+
+ (void)ctrl;
+ (void)desc_text;
+ (void)prompt_text;
+ (void)initial_errtext;
+ (void)keyinfo;
+ (void)cache_mode;
+
+ *pininfo->pin = 0; /* Reset the PIN. */
+ passphrase = get_passphrase (0);
+ size = strlen (passphrase);
+ if (size >= pininfo->max_length)
+ return gpg_error (GPG_ERR_TOO_LARGE);
+
+ memcpy (&pininfo->pin, passphrase, size);
+ xfree (passphrase);
+ pininfo->pin[size] = 0;
+ if (pininfo->check_cb)
+ {
+ /* More checks by utilizing the optional callback. */
+ pininfo->cb_errtext = NULL;
+ err = pininfo->check_cb (pininfo);
+ }
+ else
+ err = 0;
+ return err;
+}
+
+/* Replacement for the function in findkey.c. Here we write the key
+ * to stdout. */
+int
+agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force,
+ time_t timestamp,
+ const char *serialno, const char *keyref,
+ const char *dispserialno)
+{
+ char hexgrip[40+4+1];
+ char *p;
+
+ (void)force;
+ (void)timestamp;
+ (void)serialno;
+ (void)keyref;
+ (void)dispserialno;
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+ p = make_advanced (buffer, length);
+ if (p)
+ {
+ printf ("# Begin dump of %s\n%s%s# End dump of %s\n",
+ hexgrip, p, (*p && p[strlen(p)-1] == '\n')? "":"\n", hexgrip);
+ xfree (p);
+ }
+
+ return 0;
+}
diff --git a/agent/protect.c b/agent/protect.c
new file mode 100644
index 0000000..87df685
--- /dev/null
+++ b/agent/protect.c
@@ -0,0 +1,1761 @@
+/* protect.c - Un/Protect a secret key
+ * Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc.
+ * Copyright (C) 1998-2003, 2007, 2009, 2011, 2013-2015 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+#else
+# include <sys/times.h>
+#endif
+
+#include "agent.h"
+
+#include "cvt-openpgp.h"
+#include "../common/sexp-parse.h"
+
+
+/* The protection mode for encryption. The supported modes for
+ decryption are listed in agent_unprotect(). */
+#define PROT_CIPHER GCRY_CIPHER_AES128
+#define PROT_CIPHER_STRING "aes"
+#define PROT_CIPHER_KEYLEN (128/8)
+
+/* Decode an rfc4880 encoded S2K count. */
+#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6))
+
+
+/* A table containing the information needed to create a protected
+ private key. */
+static const struct {
+ const char *algo;
+ const char *parmlist;
+ int prot_from, prot_to;
+ int ecc_hack;
+} protect_info[] = {
+ { "rsa", "nedpqu", 2, 5 },
+ { "dsa", "pqgyx", 4, 4 },
+ { "elg", "pgyx", 3, 3 },
+ { "ecdsa","pabgnqd", 6, 6, 1 },
+ { "ecdh", "pabgnqd", 6, 6, 1 },
+ { "ecc", "pabgnqd", 6, 6, 1 },
+ { NULL }
+};
+
+
+/* The number of milliseconds we use in the S2K function and the
+ * calibrated count value. A count value of zero indicates that the
+ * calibration has not yet been done or needs to be done again. */
+static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION;
+static unsigned long s2k_calibrated_count;
+
+
+/* A helper object for time measurement. */
+struct calibrate_time_s
+{
+#ifdef HAVE_W32_SYSTEM
+ FILETIME creation_time, exit_time, kernel_time, user_time;
+#else
+ clock_t ticks;
+#endif
+};
+
+
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt, unsigned long s2kcount,
+ unsigned char *key, size_t keylen);
+
+
+
+
+/* Get the process time and store it in DATA. */
+static void
+calibrate_get_time (struct calibrate_time_s *data)
+{
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_W32CE_SYSTEM
+ GetThreadTimes (GetCurrentThread (),
+ &data->creation_time, &data->exit_time,
+ &data->kernel_time, &data->user_time);
+# else
+ GetProcessTimes (GetCurrentProcess (),
+ &data->creation_time, &data->exit_time,
+ &data->kernel_time, &data->user_time);
+# endif
+#elif defined (CLOCK_THREAD_CPUTIME_ID)
+ struct timespec tmp;
+
+ clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp);
+ data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 +
+ tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000);
+#else
+ data->ticks = clock ();
+#endif
+}
+
+
+static unsigned long
+calibrate_elapsed_time (struct calibrate_time_s *starttime)
+{
+ struct calibrate_time_s stoptime;
+
+ calibrate_get_time (&stoptime);
+#ifdef HAVE_W32_SYSTEM
+ {
+ unsigned long long t1, t2;
+
+ t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32)
+ + starttime->kernel_time.dwLowDateTime);
+ t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32)
+ + starttime->user_time.dwLowDateTime);
+ t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32)
+ + stoptime.kernel_time.dwLowDateTime);
+ t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32)
+ + stoptime.user_time.dwLowDateTime);
+ return (unsigned long)((t2 - t1)/10000);
+ }
+#else
+ return (unsigned long)((((double) (stoptime.ticks - starttime->ticks))
+ /CLOCKS_PER_SEC)*1000);
+#endif
+}
+
+
+/* Run a test hashing for COUNT and return the time required in
+ milliseconds. */
+static unsigned long
+calibrate_s2k_count_one (unsigned long count)
+{
+ int rc;
+ char keybuf[PROT_CIPHER_KEYLEN];
+ struct calibrate_time_s starttime;
+
+ calibrate_get_time (&starttime);
+ rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1,
+ 3, "saltsalt", count, keybuf, sizeof keybuf);
+ if (rc)
+ BUG ();
+ return calibrate_elapsed_time (&starttime);
+}
+
+
+/* Measure the time we need to do the hash operations and deduce an
+ S2K count which requires roughly some targeted amount of time. */
+static unsigned long
+calibrate_s2k_count (void)
+{
+ unsigned long count;
+ unsigned long ms;
+
+ for (count = 65536; count; count *= 2)
+ {
+ ms = calibrate_s2k_count_one (count);
+ if (opt.verbose > 1)
+ log_info ("S2K calibration: %lu -> %lums\n", count, ms);
+ if (ms > s2k_calibration_time)
+ break;
+ }
+
+ count = (unsigned long)(((double)count / ms) * s2k_calibration_time);
+ count /= 1024;
+ count *= 1024;
+ if (count < 65536)
+ count = 65536;
+
+ if (opt.verbose)
+ {
+ ms = calibrate_s2k_count_one (count);
+ log_info ("S2K calibration: %lu -> %lums\n", count, ms);
+ }
+
+ return count;
+}
+
+
+/* Set the calibration time. This may be called early at startup or
+ * at any time. Thus it should one set variables. */
+void
+set_s2k_calibration_time (unsigned int milliseconds)
+{
+ if (!milliseconds)
+ milliseconds = AGENT_S2K_CALIBRATION;
+ else if (milliseconds > 60 * 1000)
+ milliseconds = 60 * 1000; /* Cap at 60 seconds. */
+ s2k_calibration_time = milliseconds;
+ s2k_calibrated_count = 0; /* Force re-calibration. */
+}
+
+
+/* Return the calibrated S2K count. This is only public for the use
+ * of the Assuan getinfo s2k_count_cal command. */
+unsigned long
+get_calibrated_s2k_count (void)
+{
+ if (!s2k_calibrated_count)
+ s2k_calibrated_count = calibrate_s2k_count ();
+
+ /* Enforce a lower limit. */
+ return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count;
+}
+
+
+/* Return the standard S2K count. */
+unsigned long
+get_standard_s2k_count (void)
+{
+ if (opt.s2k_count)
+ return opt.s2k_count < 65536 ? 65536 : opt.s2k_count;
+
+ return get_calibrated_s2k_count ();
+}
+
+
+/* Return the milliseconds required for the standard S2K
+ * operation. */
+unsigned long
+get_standard_s2k_time (void)
+{
+ return calibrate_s2k_count_one (get_standard_s2k_count ());
+}
+
+
+/* Same as get_standard_s2k_count but return the count in the encoding
+ as described by rfc4880. */
+unsigned char
+get_standard_s2k_count_rfc4880 (void)
+{
+ unsigned long iterations;
+ unsigned int count;
+ unsigned char result;
+ unsigned char c=0;
+
+ iterations = get_standard_s2k_count ();
+ if (iterations >= 65011712)
+ return 255;
+
+ /* Need count to be in the range 16-31 */
+ for (count=iterations>>6; count>=32; count>>=1)
+ c++;
+
+ result = (c<<4)|(count-16);
+
+ if (S2K_DECODE_COUNT(result) < iterations)
+ result++;
+
+ return result;
+
+}
+
+
+
+/* Calculate the MIC for a private key or shared secret S-expression.
+ SHA1HASH should point to a 20 byte buffer. This function is
+ suitable for all algorithms. */
+static gpg_error_t
+calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
+{
+ const unsigned char *hash_begin, *hash_end;
+ const unsigned char *s;
+ size_t n;
+ int is_shared_secret;
+
+ s = plainkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "private-key"))
+ is_shared_secret = 0;
+ else if (smatch (&s, n, "shared-secret"))
+ is_shared_secret = 1;
+ else
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ hash_begin = s;
+ if (!is_shared_secret)
+ {
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* Skip the algorithm name. */
+ }
+
+ while (*s == '(')
+ {
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ if ( *s != ')' )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ }
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ hash_end = s;
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
+ hash_begin, hash_end - hash_begin);
+
+ return 0;
+}
+
+
+
+/* Encrypt the parameter block starting at PROTBEGIN with length
+ PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
+ encrypted block in RESULT or return with an error code. SHA1HASH
+ is the 20 byte SHA-1 hash required for the integrity code.
+
+ The parameter block is expected to be an incomplete canonical
+ encoded S-Expression of the form (example in advanced format):
+
+ (d #046129F..[some bytes not shown]..81#)
+ (p #00e861b..[some bytes not shown]..f1#)
+ (q #00f7a7c..[some bytes not shown]..61#)
+ (u #304559a..[some bytes not shown]..9b#)
+
+ the returned block is the S-Expression:
+
+ (protected mode (parms) encrypted_octet_string)
+
+*/
+static int
+do_encryption (const unsigned char *hashbegin, size_t hashlen,
+ const unsigned char *protbegin, size_t protlen,
+ const char *passphrase,
+ const char *timestamp_exp, size_t timestamp_exp_len,
+ unsigned char **result, size_t *resultlen,
+ unsigned long s2k_count, int use_ocb)
+{
+ gcry_cipher_hd_t hd;
+ const char *modestr;
+ unsigned char hashvalue[20];
+ int blklen, enclen, outlen;
+ unsigned char *iv = NULL;
+ unsigned int ivsize; /* Size of the buffer allocated for IV. */
+ const unsigned char *s2ksalt; /* Points into IV. */
+ int rc;
+ char *outbuf = NULL;
+ char *p;
+ int saltpos, ivpos, encpos;
+
+ s2ksalt = iv; /* Silence compiler warning. */
+
+ *resultlen = 0;
+ *result = NULL;
+
+ modestr = (use_ocb? "openpgp-s2k3-ocb-aes"
+ /* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc");
+
+ rc = gcry_cipher_open (&hd, PROT_CIPHER,
+ use_ocb? GCRY_CIPHER_MODE_OCB :
+ GCRY_CIPHER_MODE_CBC,
+ GCRY_CIPHER_SECURE);
+ if (rc)
+ return rc;
+
+ /* We need to work on a copy of the data because this makes it
+ * easier to add the trailer and the padding and more important we
+ * have to prefix the text with 2 parenthesis. In CBC mode we
+ * have to allocate enough space for:
+ *
+ * ((<parameter_list>)(4:hash4:sha120:<hashvalue>)) + padding
+ *
+ * we always append a full block of random bytes as padding but
+ * encrypt only what is needed for a full blocksize. In OCB mode we
+ * have to allocate enough space for just:
+ *
+ * ((<parameter_list>))
+ */
+ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+ if (use_ocb)
+ {
+ /* (( )) */
+ outlen = 2 + protlen + 2 ;
+ enclen = outlen + 16 /* taglen */;
+ outbuf = gcry_malloc_secure (enclen);
+ }
+ else
+ {
+ /* (( )( 4:hash 4:sha1 20:<hash> )) <padding> */
+ outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
+ enclen = outlen/blklen * blklen;
+ outbuf = gcry_malloc_secure (outlen);
+ }
+ if (!outbuf)
+ {
+ rc = out_of_core ();
+ goto leave;
+ }
+
+ /* Allocate a buffer for the nonce and the salt. */
+ if (!rc)
+ {
+ /* Allocate random bytes to be used as IV, padding and s2k salt
+ * or in OCB mode for a nonce and the s2k salt. The IV/nonce is
+ * set later because for OCB we need to set the key first. */
+ ivsize = (use_ocb? 12 : (blklen*2)) + 8;
+ iv = xtrymalloc (ivsize);
+ if (!iv)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ gcry_create_nonce (iv, ivsize);
+ s2ksalt = iv + ivsize - 8;
+ }
+ }
+
+ /* Hash the passphrase and set the key. */
+ if (!rc)
+ {
+ unsigned char *key;
+ size_t keylen = PROT_CIPHER_KEYLEN;
+
+ key = gcry_malloc_secure (keylen);
+ if (!key)
+ rc = out_of_core ();
+ else
+ {
+ rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
+ 3, s2ksalt,
+ s2k_count? s2k_count:get_standard_s2k_count(),
+ key, keylen);
+ if (!rc)
+ rc = gcry_cipher_setkey (hd, key, keylen);
+ xfree (key);
+ }
+ }
+
+ if (rc)
+ goto leave;
+
+ /* Set the IV/nonce. */
+ rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen);
+ if (rc)
+ goto leave;
+
+ if (use_ocb)
+ {
+ /* In OCB Mode we use only the public key parameters as AAD. */
+ rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin);
+ if (!rc)
+ rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len);
+ if (!rc)
+ rc = gcry_cipher_authenticate
+ (hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin));
+ }
+ else
+ {
+ /* Hash the entire expression for CBC mode. Because
+ * TIMESTAMP_EXP won't get protected, we can't simply hash a
+ * continuous buffer but need to call md_write several times. */
+ gcry_md_hd_t md;
+
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 );
+ if (!rc)
+ {
+ gcry_md_write (md, hashbegin, protbegin - hashbegin);
+ gcry_md_write (md, protbegin, protlen);
+ gcry_md_write (md, timestamp_exp, timestamp_exp_len);
+ gcry_md_write (md, protbegin+protlen,
+ hashlen - (protbegin+protlen - hashbegin));
+ memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20);
+ gcry_md_close (md);
+ }
+ }
+
+
+ /* Encrypt. */
+ if (!rc)
+ {
+ p = outbuf;
+ *p++ = '(';
+ *p++ = '(';
+ memcpy (p, protbegin, protlen);
+ p += protlen;
+ if (use_ocb)
+ {
+ *p++ = ')';
+ *p++ = ')';
+ }
+ else
+ {
+ memcpy (p, ")(4:hash4:sha120:", 17);
+ p += 17;
+ memcpy (p, hashvalue, 20);
+ p += 20;
+ *p++ = ')';
+ *p++ = ')';
+ memcpy (p, iv+blklen, blklen); /* Add padding. */
+ p += blklen;
+ }
+ assert ( p - outbuf == outlen);
+ if (use_ocb)
+ {
+ gcry_cipher_final (hd);
+ rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0);
+ if (!rc)
+ {
+ log_assert (outlen + 16 == enclen);
+ rc = gcry_cipher_gettag (hd, outbuf + outlen, 16);
+ }
+ }
+ else
+ {
+ rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
+ }
+ }
+
+ if (rc)
+ goto leave;
+
+ /* Release cipher handle and check for errors. */
+ gcry_cipher_close (hd);
+
+ /* Now allocate the buffer we want to return. This is
+
+ (protected openpgp-s2k3-sha1-aes-cbc
+ ((sha1 salt no_of_iterations) 16byte_iv)
+ encrypted_octet_string)
+
+ in canoncical format of course. We use asprintf and %n modifier
+ and dummy values as placeholders. */
+ {
+ char countbuf[35];
+
+ snprintf (countbuf, sizeof countbuf, "%lu",
+ s2k_count ? s2k_count : get_standard_s2k_count ());
+ p = xtryasprintf
+ ("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)",
+ (int)strlen (modestr), modestr,
+ &saltpos,
+ (unsigned int)strlen (countbuf), countbuf,
+ use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "",
+ enclen, &encpos, enclen, "");
+ if (!p)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ xfree (iv);
+ xfree (outbuf);
+ return tmperr;
+ }
+
+ }
+ *resultlen = strlen (p);
+ *result = (unsigned char*)p;
+ memcpy (p+saltpos, s2ksalt, 8);
+ memcpy (p+ivpos, iv, use_ocb? 12 : blklen);
+ memcpy (p+encpos, outbuf, enclen);
+ xfree (iv);
+ xfree (outbuf);
+ return 0;
+
+ leave:
+ gcry_cipher_close (hd);
+ xfree (iv);
+ xfree (outbuf);
+ return rc;
+}
+
+
+
+/* Protect the key encoded in canonical format in PLAINKEY. We assume
+ a valid S-Exp here. With USE_UCB set to -1 the default scheme is
+ used (ie. either CBC or OCB), set to 0 the old CBC mode is used,
+ and set to 1 OCB is used. */
+int
+agent_protect (const unsigned char *plainkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen,
+ unsigned long s2k_count, int use_ocb)
+{
+ int rc;
+ const char *parmlist;
+ int prot_from_idx, prot_to_idx;
+ const unsigned char *s;
+ const unsigned char *hash_begin, *hash_end;
+ const unsigned char *prot_begin, *prot_end, *real_end;
+ size_t n;
+ int c, infidx, i;
+ char timestamp_exp[35];
+ unsigned char *protected;
+ size_t protectedlen;
+ int depth = 0;
+ unsigned char *p;
+ int have_curve = 0;
+
+ if (use_ocb == -1)
+ use_ocb = !!opt.enable_extended_key_format;
+
+ /* Create an S-expression with the protected-at timestamp. */
+ memcpy (timestamp_exp, "(12:protected-at15:", 19);
+ gnupg_get_isotime (timestamp_exp+19);
+ timestamp_exp[19+15] = ')';
+
+ /* Parse original key. */
+ s = plainkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ depth++;
+ hash_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ for (infidx=0; protect_info[infidx].algo
+ && !smatch (&s, n, protect_info[infidx].algo); infidx++)
+ ;
+ if (!protect_info[infidx].algo)
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+ /* The parser below is a complete mess: To make it robust for ECC
+ use we should reorder the s-expression to include only what we
+ really need and thus guarantee the right order for saving stuff.
+ This should be done before calling this function and maybe with
+ the help of the new gcry_sexp_extract_param. */
+ parmlist = protect_info[infidx].parmlist;
+ prot_from_idx = protect_info[infidx].prot_from;
+ prot_to_idx = protect_info[infidx].prot_to;
+ prot_begin = prot_end = NULL;
+ for (i=0; (c=parmlist[i]); i++)
+ {
+ if (i == prot_from_idx)
+ prot_begin = s;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (n != 1 || c != *s)
+ {
+ if (n == 5 && !memcmp (s, "curve", 5)
+ && !i && protect_info[infidx].ecc_hack)
+ {
+ /* This is a private ECC key but the first parameter is
+ the name of the curve. We change the parameter list
+ here to the one we expect in this case. */
+ have_curve = 1;
+ parmlist = "?qd";
+ prot_from_idx = 2;
+ prot_to_idx = 2;
+ }
+ else if (n == 5 && !memcmp (s, "flags", 5)
+ && i == 1 && have_curve)
+ {
+ /* "curve" followed by "flags": Change again. */
+ parmlist = "??qd";
+ prot_from_idx = 3;
+ prot_to_idx = 3;
+ }
+ else
+ return gpg_error (GPG_ERR_INV_SEXP);
+ }
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s +=n; /* skip value */
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ if (i == prot_to_idx)
+ prot_end = s;
+ s++;
+ }
+ if (*s != ')' || !prot_begin || !prot_end )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ hash_end = s;
+ s++;
+ /* Skip to the end of the S-expression. */
+ assert (depth == 1);
+ rc = sskip (&s, &depth);
+ if (rc)
+ return rc;
+ assert (!depth);
+ real_end = s-1;
+
+ rc = do_encryption (hash_begin, hash_end - hash_begin + 1,
+ prot_begin, prot_end - prot_begin + 1,
+ passphrase, timestamp_exp, sizeof (timestamp_exp),
+ &protected, &protectedlen, s2k_count, use_ocb);
+ if (rc)
+ return rc;
+
+ /* Now create the protected version of the key. Note that the 10
+ extra bytes are for the inserted "protected-" string (the
+ beginning of the plaintext reads: "((11:private-key(" ). The 35
+ term is the space for (12:protected-at15:<timestamp>). */
+ *resultlen = (10
+ + (prot_begin-plainkey)
+ + protectedlen
+ + 35
+ + (real_end-prot_end));
+ *result = p = xtrymalloc (*resultlen);
+ if (!p)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ xfree (protected);
+ return tmperr;
+ }
+ memcpy (p, "(21:protected-", 14);
+ p += 14;
+ memcpy (p, plainkey+4, prot_begin - plainkey - 4);
+ p += prot_begin - plainkey - 4;
+ memcpy (p, protected, protectedlen);
+ p += protectedlen;
+
+ memcpy (p, timestamp_exp, 35);
+ p += 35;
+
+ memcpy (p, prot_end+1, real_end - prot_end);
+ p += real_end - prot_end;
+ assert ( p - *result == *resultlen);
+ xfree (protected);
+
+ return 0;
+}
+
+
+
+/* Do the actual decryption and check the return list for consistency. */
+static gpg_error_t
+do_decryption (const unsigned char *aad_begin, size_t aad_len,
+ const unsigned char *aadhole_begin, size_t aadhole_len,
+ const unsigned char *protected, size_t protectedlen,
+ const char *passphrase,
+ const unsigned char *s2ksalt, unsigned long s2kcount,
+ const unsigned char *iv, size_t ivlen,
+ int prot_cipher, int prot_cipher_keylen, int is_ocb,
+ unsigned char **result)
+{
+ int rc;
+ int blklen;
+ gcry_cipher_hd_t hd;
+ unsigned char *outbuf;
+ size_t reallen;
+
+ blklen = gcry_cipher_get_algo_blklen (prot_cipher);
+ if (is_ocb)
+ {
+ /* OCB does not require a multiple of the block length but we
+ * check that it is long enough for the 128 bit tag and that we
+ * have the 96 bit nonce. */
+ if (protectedlen < (4 + 16) || ivlen != 12)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ }
+ else
+ {
+ if (protectedlen < 4 || (protectedlen%blklen))
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ }
+
+ rc = gcry_cipher_open (&hd, prot_cipher,
+ is_ocb? GCRY_CIPHER_MODE_OCB :
+ GCRY_CIPHER_MODE_CBC,
+ GCRY_CIPHER_SECURE);
+ if (rc)
+ return rc;
+
+ outbuf = gcry_malloc_secure (protectedlen);
+ if (!outbuf)
+ rc = out_of_core ();
+
+ /* Hash the passphrase and set the key. */
+ if (!rc)
+ {
+ unsigned char *key;
+
+ key = gcry_malloc_secure (prot_cipher_keylen);
+ if (!key)
+ rc = out_of_core ();
+ else
+ {
+ rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
+ 3, s2ksalt, s2kcount, key, prot_cipher_keylen);
+ if (!rc)
+ rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen);
+ xfree (key);
+ }
+ }
+
+ /* Set the IV/nonce. */
+ if (!rc)
+ {
+ rc = gcry_cipher_setiv (hd, iv, ivlen);
+ }
+
+ /* Decrypt. */
+ if (!rc)
+ {
+ if (is_ocb)
+ {
+ rc = gcry_cipher_authenticate (hd, aad_begin,
+ aadhole_begin - aad_begin);
+ if (!rc)
+ rc = gcry_cipher_authenticate
+ (hd, aadhole_begin + aadhole_len,
+ aad_len - (aadhole_begin+aadhole_len - aad_begin));
+
+ if (!rc)
+ {
+ gcry_cipher_final (hd);
+ rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16,
+ protected, protectedlen - 16);
+ }
+ if (!rc)
+ {
+ rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16);
+ if (gpg_err_code (rc) == GPG_ERR_CHECKSUM)
+ {
+ /* Return Bad Passphrase instead of checksum error */
+ rc = gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+ }
+ }
+ else
+ {
+ rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
+ protected, protectedlen);
+ }
+ }
+
+ /* Release cipher handle and check for errors. */
+ gcry_cipher_close (hd);
+ if (rc)
+ {
+ xfree (outbuf);
+ return rc;
+ }
+
+ /* Do a quick check on the data structure. */
+ if (*outbuf != '(' && outbuf[1] != '(')
+ {
+ xfree (outbuf);
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+
+ /* Check that we have a consistent S-Exp. */
+ reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
+ if (!reallen || (reallen + blklen < protectedlen) )
+ {
+ xfree (outbuf);
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+ *result = outbuf;
+ return 0;
+}
+
+
+/* Merge the parameter list contained in CLEARTEXT with the original
+ * protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
+ * Return the new list in RESULT and the MIC value in the 20 byte
+ * buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed.
+ * CUTOFF and CUTLEN will receive the offset and the length of the
+ * resulting list which should go into the MIC calculation but then be
+ * removed. */
+static gpg_error_t
+merge_lists (const unsigned char *protectedkey,
+ size_t replacepos,
+ const unsigned char *cleartext,
+ unsigned char *sha1hash,
+ unsigned char **result, size_t *resultlen,
+ size_t *cutoff, size_t *cutlen)
+{
+ size_t n, newlistlen;
+ unsigned char *newlist, *p;
+ const unsigned char *s;
+ const unsigned char *startpos, *endpos;
+ int i, rc;
+
+ *result = NULL;
+ *resultlen = 0;
+ *cutoff = 0;
+ *cutlen = 0;
+
+ if (replacepos < 26)
+ return gpg_error (GPG_ERR_BUG);
+
+ /* Estimate the required size of the resulting list. We have a large
+ safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the
+ removed "protected-" */
+ newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
+ if (!newlistlen)
+ return gpg_error (GPG_ERR_BUG);
+ n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
+ if (!n)
+ return gpg_error (GPG_ERR_BUG);
+ newlistlen += n;
+ newlist = gcry_malloc_secure (newlistlen);
+ if (!newlist)
+ return out_of_core ();
+
+ /* Copy the initial segment */
+ strcpy ((char*)newlist, "(11:private-key");
+ p = newlist + 15;
+ memcpy (p, protectedkey+15+10, replacepos-15-10);
+ p += replacepos-15-10;
+
+ /* Copy the cleartext. */
+ s = cleartext;
+ if (*s != '(' && s[1] != '(')
+ return gpg_error (GPG_ERR_BUG); /*we already checked this */
+ s += 2;
+ startpos = s;
+ while ( *s == '(' )
+ {
+ s++;
+ n = snext (&s);
+ if (!n)
+ goto invalid_sexp;
+ s += n;
+ n = snext (&s);
+ if (!n)
+ goto invalid_sexp;
+ s += n;
+ if ( *s != ')' )
+ goto invalid_sexp;
+ s++;
+ }
+ if ( *s != ')' )
+ goto invalid_sexp;
+ endpos = s;
+ s++;
+
+ /* Intermezzo: Get the MIC if requested. */
+ if (sha1hash)
+ {
+ if (*s != '(')
+ goto invalid_sexp;
+ s++;
+ n = snext (&s);
+ if (!smatch (&s, n, "hash"))
+ goto invalid_sexp;
+ n = snext (&s);
+ if (!smatch (&s, n, "sha1"))
+ goto invalid_sexp;
+ n = snext (&s);
+ if (n != 20)
+ goto invalid_sexp;
+ memcpy (sha1hash, s, 20);
+ s += n;
+ if (*s != ')')
+ goto invalid_sexp;
+ }
+
+ /* Append the parameter list. */
+ memcpy (p, startpos, endpos - startpos);
+ p += endpos - startpos;
+
+ /* Skip over the protected list element in the original list. */
+ s = protectedkey + replacepos;
+ assert (*s == '(');
+ s++;
+ i = 1;
+ rc = sskip (&s, &i);
+ if (rc)
+ goto failure;
+ /* Record the position of the optional protected-at expression. */
+ if (*s == '(')
+ {
+ const unsigned char *save_s = s;
+ s++;
+ n = snext (&s);
+ if (smatch (&s, n, "protected-at"))
+ {
+ i = 1;
+ rc = sskip (&s, &i);
+ if (rc)
+ goto failure;
+ *cutlen = s - save_s;
+ }
+ s = save_s;
+ }
+ startpos = s;
+ i = 2; /* we are inside this level */
+ rc = sskip (&s, &i);
+ if (rc)
+ goto failure;
+ assert (s[-1] == ')');
+ endpos = s; /* one behind the end of the list */
+
+ /* Append the rest. */
+ if (*cutlen)
+ *cutoff = p - newlist;
+ memcpy (p, startpos, endpos - startpos);
+ p += endpos - startpos;
+
+
+ /* ready */
+ *result = newlist;
+ *resultlen = newlistlen;
+ return 0;
+
+ failure:
+ wipememory (newlist, newlistlen);
+ xfree (newlist);
+ return rc;
+
+ invalid_sexp:
+ wipememory (newlist, newlistlen);
+ xfree (newlist);
+ return gpg_error (GPG_ERR_INV_SEXP);
+}
+
+
+
+/* Unprotect the key encoded in canonical format. We assume a valid
+ S-Exp here. If a protected-at item is available, its value will
+ be stored at protected_at unless this is NULL. */
+gpg_error_t
+agent_unprotect (ctrl_t ctrl,
+ const unsigned char *protectedkey, const char *passphrase,
+ gnupg_isotime_t protected_at,
+ unsigned char **result, size_t *resultlen)
+{
+ static const struct {
+ const char *name; /* Name of the protection method. */
+ int algo; /* (A zero indicates the "openpgp-native" hack.) */
+ int keylen; /* Used key length in bytes. */
+ unsigned int is_ocb:1;
+ } algotable[] = {
+ { "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)},
+ { "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)},
+ { "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1},
+ { "openpgp-native", 0, 0 }
+ };
+ int rc;
+ const unsigned char *s;
+ const unsigned char *protect_list;
+ size_t n;
+ int infidx, i;
+ unsigned char sha1hash[20], sha1hash2[20];
+ const unsigned char *s2ksalt;
+ unsigned long s2kcount;
+ const unsigned char *iv;
+ int prot_cipher, prot_cipher_keylen;
+ int is_ocb;
+ const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end;
+ const unsigned char *prot_begin;
+ unsigned char *cleartext;
+ unsigned char *final;
+ size_t finallen;
+ size_t cutoff, cutlen;
+
+ if (protected_at)
+ *protected_at = 0;
+
+ s = protectedkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "protected-private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ {
+ aad_begin = aad_end = s;
+ aad_end++;
+ i = 1;
+ rc = sskip (&aad_end, &i);
+ if (rc)
+ return rc;
+ }
+
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ for (infidx=0; protect_info[infidx].algo
+ && !smatch (&s, n, protect_info[infidx].algo); infidx++)
+ ;
+ if (!protect_info[infidx].algo)
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+ /* See wether we have a protected-at timestamp. */
+ protect_list = s; /* Save for later. */
+ if (protected_at)
+ {
+ while (*s == '(')
+ {
+ prot_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "protected-at"))
+ {
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (n != 15)
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ memcpy (protected_at, s, 15);
+ protected_at[15] = 0;
+ break;
+ }
+ s += n;
+ i = 1;
+ rc = sskip (&s, &i);
+ if (rc)
+ return rc;
+ }
+ }
+
+ /* Now find the list with the protected information. Here is an
+ example for such a list:
+ (protected openpgp-s2k3-sha1-aes-cbc
+ ((sha1 <salt> <count>) <Initialization_Vector>)
+ <encrypted_data>)
+ */
+ s = protect_list;
+ for (;;)
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ prot_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "protected"))
+ break;
+ s += n;
+ i = 1;
+ rc = sskip (&s, &i);
+ if (rc)
+ return rc;
+ }
+ /* found */
+ {
+ aadhole_begin = aadhole_end = prot_begin;
+ aadhole_end++;
+ i = 1;
+ rc = sskip (&aadhole_end, &i);
+ if (rc)
+ return rc;
+ }
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ /* Lookup the protection algo. */
+ prot_cipher = 0; /* (avoid gcc warning) */
+ prot_cipher_keylen = 0; /* (avoid gcc warning) */
+ is_ocb = 0;
+ for (i=0; i < DIM (algotable); i++)
+ if (smatch (&s, n, algotable[i].name))
+ {
+ prot_cipher = algotable[i].algo;
+ prot_cipher_keylen = algotable[i].keylen;
+ is_ocb = algotable[i].is_ocb;
+ break;
+ }
+ if (i == DIM (algotable))
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
+
+ if (!prot_cipher) /* This is "openpgp-native". */
+ {
+ gcry_sexp_t s_prot_begin;
+
+ rc = gcry_sexp_sscan (&s_prot_begin, NULL,
+ prot_begin,
+ gcry_sexp_canon_len (prot_begin, 0,NULL,NULL));
+ if (rc)
+ return rc;
+
+ rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final);
+ gcry_sexp_release (s_prot_begin);
+ if (!rc)
+ {
+ *result = final;
+ *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
+ }
+ return rc;
+ }
+
+ if (*s != '(' || s[1] != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += 2;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "sha1"))
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
+ n = snext (&s);
+ if (n != 8)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ s2ksalt = s;
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ /* We expect a list close as next, so we can simply use strtoul()
+ here. We might want to check that we only have digits - but this
+ is nothing we should worry about */
+ if (s[n] != ')' )
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ /* Old versions of gpg-agent used the funny floating point number in
+ a byte encoding as specified by OpenPGP. However this is not
+ needed and thus we now store it as a plain unsigned integer. We
+ can easily distinguish the old format by looking at its value:
+ Less than 256 is an old-style encoded number; other values are
+ plain integers. In any case we check that they are at least
+ 65536 because we never used a lower value in the past and we
+ should have a lower limit. */
+ s2kcount = strtoul ((const char*)s, NULL, 10);
+ if (!s2kcount)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ if (s2kcount < 256)
+ s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
+ if (s2kcount < 65536)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+
+ s += n;
+ s++; /* skip list end */
+
+ n = snext (&s);
+ if (is_ocb)
+ {
+ if (n != 12) /* Wrong size of the nonce. */
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ }
+ else
+ {
+ if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ }
+ iv = s;
+ s += n;
+ if (*s != ')' )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ cleartext = NULL; /* Avoid cc warning. */
+ rc = do_decryption (aad_begin, aad_end - aad_begin,
+ aadhole_begin, aadhole_end - aadhole_begin,
+ s, n,
+ passphrase, s2ksalt, s2kcount,
+ iv, is_ocb? 12:16,
+ prot_cipher, prot_cipher_keylen, is_ocb,
+ &cleartext);
+ if (rc)
+ return rc;
+
+ rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
+ is_ocb? NULL : sha1hash,
+ &final, &finallen, &cutoff, &cutlen);
+ /* Albeit cleartext has been allocated in secure memory and thus
+ xfree will wipe it out, we do an extra wipe just in case
+ somethings goes badly wrong. */
+ wipememory (cleartext, n);
+ xfree (cleartext);
+ if (rc)
+ return rc;
+
+ if (!is_ocb)
+ {
+ rc = calculate_mic (final, sha1hash2);
+ if (!rc && memcmp (sha1hash, sha1hash2, 20))
+ rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ if (rc)
+ {
+ wipememory (final, finallen);
+ xfree (final);
+ return rc;
+ }
+ }
+
+ /* Now remove the part which is included in the MIC but should not
+ go into the final thing. */
+ if (cutlen)
+ {
+ memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen);
+ finallen -= cutlen;
+ }
+
+ *result = final;
+ *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
+ return 0;
+}
+
+
+/* Check the type of the private key, this is one of the constants:
+ PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
+ value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
+ PRIVATE_KEY_PROTECTED for an protected private key or
+ PRIVATE_KEY_SHADOWED for a sub key where the secret parts are
+ stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned
+ is the key is still in the openpgp-native format but without
+ protection. */
+int
+agent_private_key_type (const unsigned char *privatekey)
+{
+ const unsigned char *s;
+ size_t n;
+ int i;
+
+ s = privatekey;
+ if (*s != '(')
+ return PRIVATE_KEY_UNKNOWN;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN;
+ if (smatch (&s, n, "protected-private-key"))
+ {
+ /* We need to check whether this is openpgp-native protected
+ with the protection method "none". In that case we return a
+ different key type so that the caller knows that there is no
+ need to ask for a passphrase. */
+ if (*s != '(')
+ return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */
+ s++;
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ s += n; /* Skip over the algo */
+
+ /* Find the (protected ...) list. */
+ for (;;)
+ {
+ if (*s != '(')
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ s++;
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ if (smatch (&s, n, "protected"))
+ break;
+ s += n;
+ i = 1;
+ if (sskip (&s, &i))
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ }
+ /* Found - Is this openpgp-native? */
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ if (smatch (&s, n, "openpgp-native")) /* Yes. */
+ {
+ if (*s != '(')
+ return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */
+ s++;
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ s += n; /* Skip over "openpgp-private-key". */
+ /* Find the (protection ...) list. */
+ for (;;)
+ {
+ if (*s != '(')
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ s++;
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ if (smatch (&s, n, "protection"))
+ break;
+ s += n;
+ i = 1;
+ if (sskip (&s, &i))
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ }
+ /* Found - Is the mode "none"? */
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
+ if (smatch (&s, n, "none"))
+ return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */
+ }
+
+ return PRIVATE_KEY_PROTECTED;
+ }
+ if (smatch (&s, n, "shadowed-private-key"))
+ return PRIVATE_KEY_SHADOWED;
+ if (smatch (&s, n, "private-key"))
+ return PRIVATE_KEY_CLEAR;
+ return PRIVATE_KEY_UNKNOWN;
+}
+
+
+
+/* Transform a passphrase into a suitable key of length KEYLEN and
+ store this key in the caller provided buffer KEY. The caller must
+ provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
+ that mode an S2KSALT of 8 random bytes and an S2KCOUNT.
+
+ Returns an error code on failure. */
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt,
+ unsigned long s2kcount,
+ unsigned char *key, size_t keylen)
+{
+ /* The key derive function does not support a zero length string for
+ the passphrase in the S2K modes. Return a better suited error
+ code than GPG_ERR_INV_DATA. */
+ if (!passphrase || !*passphrase)
+ return gpg_error (GPG_ERR_NO_PASSPHRASE);
+ return gcry_kdf_derive (passphrase, strlen (passphrase),
+ s2kmode == 3? GCRY_KDF_ITERSALTED_S2K :
+ s2kmode == 1? GCRY_KDF_SALTED_S2K :
+ s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE,
+ hashalgo, s2ksalt, 8, s2kcount,
+ keylen, key);
+}
+
+
+gpg_error_t
+s2k_hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt,
+ unsigned int s2kcount,
+ unsigned char *key, size_t keylen)
+{
+ return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt,
+ S2K_DECODE_COUNT (s2kcount),
+ key, keylen);
+}
+
+
+
+
+/* Create an canonical encoded S-expression with the shadow info from
+ a card's SERIALNO and the IDSTRING. */
+unsigned char *
+make_shadow_info (const char *serialno, const char *idstring)
+{
+ const char *s;
+ char *info, *p;
+ char numbuf[20];
+ size_t n;
+
+ for (s=serialno, n=0; *s && s[1]; s += 2)
+ n++;
+
+ info = p = xtrymalloc (1 + sizeof numbuf + n
+ + sizeof numbuf + strlen (idstring) + 1 + 1);
+ if (!info)
+ return NULL;
+ *p++ = '(';
+ p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL));
+ for (s=serialno; *s && s[1]; s += 2)
+ *(unsigned char *)p++ = xtoi_2 (s);
+ p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL));
+ p = stpcpy (p, idstring);
+ *p++ = ')';
+ *p = 0;
+ return (unsigned char *)info;
+}
+
+
+
+/* Create a shadow key from a public key. We use the shadow protocol
+ "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting
+ S-expression is returned in an allocated buffer RESULT will point
+ to. The input parameters are expected to be valid canonicalized
+ S-expressions */
+int
+agent_shadow_key (const unsigned char *pubkey,
+ const unsigned char *shadow_info,
+ unsigned char **result)
+{
+ const unsigned char *s;
+ const unsigned char *point;
+ size_t n;
+ int depth = 0;
+ char *p;
+ size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL);
+ size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL);
+
+ if (!pubkey_len || !shadow_info_len)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ s = pubkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "public-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* skip over the algorithm name */
+
+ while (*s != ')')
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s +=n; /* skip value */
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ s++;
+ }
+ point = s; /* insert right before the point */
+ depth--;
+ s++;
+ assert (depth == 1);
+
+ /* Calculate required length by taking in account: the "shadowed-"
+ prefix, the "shadowed", "t1-v1" as well as some parenthesis */
+ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1;
+ *result = xtrymalloc (n);
+ p = (char*)*result;
+ if (!p)
+ return out_of_core ();
+ p = stpcpy (p, "(20:shadowed-private-key");
+ /* (10:public-key ...)*/
+ memcpy (p, pubkey+14, point - (pubkey+14));
+ p += point - (pubkey+14);
+ p = stpcpy (p, "(8:shadowed5:t1-v1");
+ memcpy (p, shadow_info, shadow_info_len);
+ p += shadow_info_len;
+ *p++ = ')';
+ memcpy (p, point, pubkey_len - (point - pubkey));
+ p += pubkey_len - (point - pubkey);
+
+ return 0;
+}
+
+/* Parse a canonical encoded shadowed key and return a pointer to the
+ inner list with the shadow_info */
+gpg_error_t
+agent_get_shadow_info (const unsigned char *shadowkey,
+ unsigned char const **shadow_info)
+{
+ const unsigned char *s;
+ size_t n;
+ int depth = 0;
+
+ s = shadowkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "shadowed-private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* skip over the algorithm name */
+
+ for (;;)
+ {
+ if (*s == ')')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "shadowed"))
+ break;
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s +=n; /* skip value */
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ s++;
+ }
+ /* Found the shadowed list, S points to the protocol */
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "t1-v1"))
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ *shadow_info = s;
+ }
+ else
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
+ return 0;
+}
+
+
+/* Parse the canonical encoded SHADOW_INFO S-expression. On success
+ the hex encoded serial number is returned as a malloced strings at
+ R_HEXSN and the Id string as a malloced string at R_IDSTR. On
+ error an error code is returned and NULL is stored at the result
+ parameters addresses. If the serial number or the ID string is not
+ required, NULL may be passed for them. */
+gpg_error_t
+parse_shadow_info (const unsigned char *shadow_info,
+ char **r_hexsn, char **r_idstr, int *r_pinlen)
+{
+ const unsigned char *s;
+ size_t n;
+
+ if (r_hexsn)
+ *r_hexsn = NULL;
+ if (r_idstr)
+ *r_idstr = NULL;
+ if (r_pinlen)
+ *r_pinlen = 0;
+
+ s = shadow_info;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ if (r_hexsn)
+ {
+ *r_hexsn = bin2hex (s, n, NULL);
+ if (!*r_hexsn)
+ return gpg_error_from_syserror ();
+ }
+ s += n;
+
+ n = snext (&s);
+ if (!n)
+ {
+ if (r_hexsn)
+ {
+ xfree (*r_hexsn);
+ *r_hexsn = NULL;
+ }
+ return gpg_error (GPG_ERR_INV_SEXP);
+ }
+
+ if (r_idstr)
+ {
+ *r_idstr = xtrymalloc (n+1);
+ if (!*r_idstr)
+ {
+ if (r_hexsn)
+ {
+ xfree (*r_hexsn);
+ *r_hexsn = NULL;
+ }
+ return gpg_error_from_syserror ();
+ }
+ memcpy (*r_idstr, s, n);
+ (*r_idstr)[n] = 0;
+ trim_spaces (*r_idstr);
+ }
+
+ /* Parse the optional PINLEN. */
+ n = snext (&s);
+ if (!n)
+ return 0;
+
+ if (r_pinlen)
+ {
+ char *tmpstr = xtrymalloc (n+1);
+ if (!tmpstr)
+ {
+ if (r_hexsn)
+ {
+ xfree (*r_hexsn);
+ *r_hexsn = NULL;
+ }
+ if (r_idstr)
+ {
+ xfree (*r_idstr);
+ *r_idstr = NULL;
+ }
+ return gpg_error_from_syserror ();
+ }
+ memcpy (tmpstr, s, n);
+ tmpstr[n] = 0;
+
+ *r_pinlen = (int)strtol (tmpstr, NULL, 10);
+ xfree (tmpstr);
+ }
+
+ return 0;
+}
diff --git a/agent/t-protect.c b/agent/t-protect.c
new file mode 100644
index 0000000..88b5525
--- /dev/null
+++ b/agent/t-protect.c
@@ -0,0 +1,351 @@
+/* t-protect.c - Module tests for protect.c
+ * Copyright (C) 2005 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define INCLUDED_BY_MAIN_MODULE 1
+#include "agent.h"
+
+
+#define pass() do { ; } while(0)
+#define fail() do { fprintf (stderr, "%s:%d: test failed\n",\
+ __FILE__,__LINE__); \
+ exit (1); \
+ } while(0)
+
+
+static void
+test_agent_protect (void)
+{
+ /* Protect the key encoded in canonical format in PLAINKEY. We assume
+ a valid S-Exp here. */
+
+ unsigned int i;
+ int ret;
+ struct key_spec
+ {
+ const char *string;
+ };
+ /* Valid RSA key. */
+ struct key_spec key_rsa_valid =
+ {
+ "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73"
+ "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92"
+ "\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04"
+ "\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D"
+ "\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49"
+ "\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F"
+ "\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77"
+ "\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28"
+ "\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3"
+ "\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A"
+ "\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9"
+ "\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77"
+ "\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7"
+ "\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD"
+ "\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34"
+ "\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B"
+ "\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A"
+ "\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85"
+ "\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28"
+ "\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F"
+ "\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2"
+ "\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21"
+ "\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33"
+ "\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72"
+ "\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B"
+ "\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17"
+ "\xD0\x68\x0B\x29\x29\x29\x00"
+ };
+ /* This RSA key is missing the last closing brace. */
+ struct key_spec key_rsa_bogus_0 =
+ {
+ "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73"
+ "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92"
+ "\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04"
+ "\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D"
+ "\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49"
+ "\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F"
+ "\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77"
+ "\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28"
+ "\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3"
+ "\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A"
+ "\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9"
+ "\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77"
+ "\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7"
+ "\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD"
+ "\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34"
+ "\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B"
+ "\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A"
+ "\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85"
+ "\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28"
+ "\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F"
+ "\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2"
+ "\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21"
+ "\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33"
+ "\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72"
+ "\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B"
+ "\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17"
+ "\xD0\x68\x0B\x29\x29\x00"
+ };
+ /* This RSA key is the 'e' value. */
+ struct key_spec key_rsa_bogus_1 =
+ {
+ "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73"
+ "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xA8\x80\xB6\x71\xF4\x95\x9F\x49\x84\xED"
+ "\xC1\x1D\x5F\xFF\xED\x14\x7B\x9C\x6A\x62\x0B\x7B\xE2\x3E\x41\x48\x49\x85\xF5\x64"
+ "\x50\x04\x9D\x30\xFC\x84\x1F\x01\xC3\xC3\x15\x03\x48\x6D\xFE\x59\x0B\xB0\xD0\x3E"
+ "\x68\x8A\x05\x7A\x62\xB0\xB9\x6E\xC5\xD2\xA8\xEE\x0C\x6B\xDE\x5E\x3D\x8E\xE8\x8F"
+ "\xB3\xAE\x86\x99\x7E\xDE\x2B\xC2\x4D\x60\x51\xDB\xB1\x2C\xD0\x38\xEC\x88\x62\x3E"
+ "\xA9\xDD\x11\x53\x04\x17\xE4\xF2\x07\x50\xDC\x44\xED\x14\xF5\x0B\xAB\x9C\xBC\x24"
+ "\xC6\xCB\xAD\x0F\x05\x25\x94\xE2\x73\xEB\x14\xD5\xEE\x5E\x18\xF0\x40\x31\x29\x28"
+ "\x31\x3A\x64\x31\x32\x38\x3A\x40\xD0\x55\x9D\x2A\xA7\xBC\xBF\xE2\x3E\x33\x98\x71"
+ "\x7B\x37\x3D\xB8\x38\x57\xA1\x43\xEA\x90\x81\x42\xCA\x23\xE1\xBF\x9C\xA8\xBC\xC5"
+ "\x9B\xF8\x9D\x77\x71\xCD\xD3\x85\x8B\x20\x3A\x92\xE9\xBC\x79\xF3\xF7\xF5\x6D\x15"
+ "\xA3\x58\x3F\xC2\xEB\xED\x72\xD4\xE0\xCF\xEC\xB3\xEC\xEB\x09\xEA\x1E\x72\x6A\xBA"
+ "\x95\x82\x2C\x7E\x30\x95\x66\x3F\xA8\x2D\x40\x0F\x7A\x12\x4E\xF0\x71\x0F\x97\xDB"
+ "\x81\xE4\x39\x6D\x24\x58\xFA\xAB\x3A\x36\x73\x63\x01\x77\x42\xC7\x9A\xEA\x87\xDA"
+ "\x93\x8F\x6C\x64\xAD\x9E\xF0\xCA\xA2\x89\xA4\x0E\xB3\x25\x73\x29\x28\x31\x3A\x70"
+ "\x36\x35\x3A\x00\xC3\xF7\x37\x3F\x9D\x93\xEC\xC7\x5E\x4C\xB5\x73\x29\x62\x35\x80"
+ "\xC6\x7C\x1B\x1E\x68\x5F\x92\x56\x77\x0A\xE2\x8E\x95\x74\x87\xA5\x2F\x83\x2D\xF7"
+ "\xA1\xC2\x78\x54\x18\x6E\xDE\x35\xF0\x9F\x7A\xCA\x80\x5C\x83\x5C\x44\xAD\x8B\xE7"
+ "\x5B\xE2\x63\x7D\x6A\xC7\x98\x97\x29\x28\x31\x3A\x71\x36\x35\x3A\x00\xDC\x1F\xB1"
+ "\xB3\xD8\x13\xE0\x09\x19\xFD\x1C\x58\xA1\x2B\x02\xB4\xC8\xF2\x1C\xE7\xF9\xC6\x3B"
+ "\x68\xB9\x72\x43\x86\xEF\xA9\x94\x68\x02\xEF\x7D\x77\xE0\x0A\xD1\xD7\x48\xFD\xCD"
+ "\x98\xDA\x13\x8A\x76\x48\xD4\x0F\x63\x28\xFA\x01\x1B\xF3\xC7\x15\xB8\x53\x22\x7E"
+ "\x77\x29\x28\x31\x3A\x75\x36\x35\x3A\x00\xB3\xBB\x4D\xEE\x5A\xAF\xD0\xF2\x56\x8A"
+ "\x10\x2D\x6F\x4B\x2D\x76\x49\x9B\xE9\xA8\x60\x5D\x9E\x7E\x50\x86\xF1\xA1\x0F\x28"
+ "\x9B\x7B\xE8\xDD\x1F\x87\x4E\x79\x7B\x50\x12\xA7\xB4\x8B\x52\x38\xEC\x7C\xBB\xB9"
+ "\x55\x87\x11\x1C\x74\xE7\x7F\xA0\xBA\xE3\x34\x5D\x61\xBF\x29\x29\x29\x00"
+ };
+
+ struct key_spec key_ecdsa_valid =
+ {
+ "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28"
+ "\x35\x3A\x65\x63\x64\x73\x61\x28\x35\x3A\x63\x75\x72\x76\x65\x31"
+ "\x30\x3A\x4E\x49\x53\x54\x20\x50\x2D\x32\x35\x36\x29\x28\x31\x3A"
+ "\x71\x36\x35\x3A\x04\x64\x5A\x12\x6F\x86\x7C\x43\x87\x2B\x7C\xAF"
+ "\x77\xFE\xD8\x22\x31\xEA\xE6\x89\x9F\xAA\xEA\x63\x26\xBC\x49\xED"
+ "\x85\xC6\xD2\xC9\x8B\x38\xD2\x78\x75\xE6\x1C\x27\x57\x01\xC5\xA1"
+ "\xE3\xF9\x1F\xBE\xCF\xC1\x72\x73\xFE\xA4\x58\xB6\x6A\x92\x7D\x33"
+ "\x1D\x02\xC9\xCB\x12\x29\x28\x31\x3A\x64\x33\x33\x3A\x00\x81\x2D"
+ "\x69\x9A\x5F\x5B\x6F\x2C\x99\x61\x36\x15\x6B\x44\xD8\x06\xC1\x54"
+ "\xC1\x4C\xFB\x70\x6A\xB6\x64\x81\x78\xF3\x94\x2F\x30\x5D\x29\x29"
+ "\x28\x37\x3A\x63\x6F\x6D\x6D\x65\x6E\x74\x32\x32\x3A\x2F\x68\x6F"
+ "\x6D\x65\x2F\x77\x6B\x2F\x2E\x73\x73\x68\x2F\x69\x64\x5F\x65\x63"
+ "\x64\x73\x61\x29\x29"
+ };
+
+
+ struct
+ {
+ const char *key;
+ const char *passphrase;
+ int no_result_expected;
+ int compare_results;
+ unsigned char *result_expected;
+ size_t resultlen_expected;
+ int ret_expected;
+ unsigned char *result;
+ size_t resultlen;
+ } specs[] =
+ {
+ /* Invalid S-Expressions */
+ /* - non-NULL */
+ { "",
+ "passphrase", 1, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
+ /* - NULL; disabled, this segfaults */
+ //{ NULL,
+ // "passphrase", 1, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
+
+ /* Valid and invalid keys. */
+ { key_rsa_valid.string,
+ "passphrase", 0, 0, NULL, 0, 0, NULL, 0 },
+ { key_rsa_bogus_0.string,
+ "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
+ { key_rsa_bogus_1.string,
+ "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
+
+ { key_ecdsa_valid.string,
+ "passphrase", 0, 0, NULL, 0, 0, NULL, 0 },
+
+ /* FIXME: add more test data. */
+ };
+
+ for (i = 0; i < DIM (specs); i++)
+ {
+ ret = agent_protect ((const unsigned char*)specs[i].key,
+ specs[i].passphrase,
+ &specs[i].result, &specs[i].resultlen, 0, -1);
+ if (gpg_err_code (ret) != specs[i].ret_expected)
+ {
+ printf ("agent_protect(%d) returned '%i/%s'; expected '%i/%s'\n",
+ i, ret, gpg_strerror (ret),
+ specs[i].ret_expected, gpg_strerror (specs[i].ret_expected));
+ abort ();
+ }
+
+ if (specs[i].no_result_expected)
+ {
+ assert (! specs[i].result);
+ assert (! specs[i].resultlen);
+ }
+ else
+ {
+ if (specs[i].compare_results)
+ {
+ assert (specs[i].resultlen == specs[i].resultlen_expected);
+ if (specs[i].result_expected)
+ assert (! memcmp (specs[i].result, specs[i].result_expected,
+ specs[i].resultlen));
+ else
+ assert (! specs[i].result);
+ }
+ xfree (specs[i].result);
+ }
+ }
+}
+
+
+static void
+test_agent_unprotect (void)
+{
+ /* Unprotect the key encoded in canonical format. We assume a valid
+ S-Exp here. */
+/* int */
+/* agent_unprotect (const unsigned char *protectedkey, const char *passphrase, */
+/* unsigned char **result, size_t *resultlen) */
+}
+
+
+static void
+test_agent_private_key_type (void)
+{
+/* Check the type of the private key, this is one of the constants:
+ PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
+ value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
+ PRIVATE_KEY_PROTECTED for an protected private key or
+ PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored
+ elsewhere. */
+/* int */
+/* agent_private_key_type (const unsigned char *privatekey) */
+}
+
+
+static void
+test_make_shadow_info (void)
+{
+#if 0
+ static struct
+ {
+ const char *snstr;
+ const char *idstr;
+ const char *expected;
+ } data[] = {
+ { "", "", NULL },
+
+ };
+ int i;
+ unsigned char *result;
+
+ for (i=0; i < DIM(data); i++)
+ {
+ result = make_shadow_info (data[i].snstr, data[i].idstr);
+ if (!result && !data[i].expected)
+ pass ();
+ else if (!result && data[i].expected)
+ fail ();
+ else if (!data[i].expected)
+ fail ();
+ /* fixme: Need to compare the result but also need to check
+ proper S-expression syntax. */
+ }
+#endif
+}
+
+
+
+static void
+test_agent_shadow_key (void)
+{
+/* Create a shadow key from a public key. We use the shadow protocol
+ "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting
+ S-expression is returned in an allocated buffer RESULT will point
+ to. The input parameters are expected to be valid canonicalized
+ S-expressions */
+/* int */
+/* agent_shadow_key (const unsigned char *pubkey, */
+/* const unsigned char *shadow_info, */
+/* unsigned char **result) */
+}
+
+
+static void
+test_agent_get_shadow_info (void)
+{
+/* Parse a canonical encoded shadowed key and return a pointer to the
+ inner list with the shadow_info */
+/* int */
+/* agent_get_shadow_info (const unsigned char *shadowkey, */
+/* unsigned char const **shadow_info) */
+}
+
+
+static void
+test_agent_protect_shared_secret (void)
+{
+
+}
+
+
+
+
+int
+main (int argc, char **argv)
+{
+ (void)argv;
+
+ opt.verbose = argc - 1; /* We can do "./t-protect -v -v" */
+ gcry_control (GCRYCTL_DISABLE_SECMEM);
+
+ test_agent_protect ();
+ test_agent_unprotect ();
+ test_agent_private_key_type ();
+ test_make_shadow_info ();
+ test_agent_shadow_key ();
+ test_agent_get_shadow_info ();
+ test_agent_protect_shared_secret ();
+
+ return 0;
+}
+
+/* Stub function. */
+gpg_error_t
+convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase,
+ unsigned char **r_key)
+{
+ (void)s_pgp;
+ (void)passphrase;
+ (void)r_key;
+ return gpg_error (GPG_ERR_BUG);
+}
diff --git a/agent/trans.c b/agent/trans.c
new file mode 100644
index 0000000..ff1a34e
--- /dev/null
+++ b/agent/trans.c
@@ -0,0 +1,41 @@
+/* trans.c - translatable strings
+ * Copyright (C) 2001 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+/* To avoid any problems with the gettext implementation (there used
+ to be some vulnerabilities in the last years and the use of
+ external files is a minor security problem in itself), we use our
+ own simple translation stuff */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+
+const char *
+trans (const char *text)
+{
+ return text;
+}
diff --git a/agent/trustlist.c b/agent/trustlist.c
new file mode 100644
index 0000000..086d8ae
--- /dev/null
+++ b/agent/trustlist.c
@@ -0,0 +1,852 @@
+/* trustlist.c - Maintain the list of trusted keys
+ * Copyright (C) 2002, 2004, 2006, 2007, 2009,
+ * 2012 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <npth.h>
+
+#include "agent.h"
+#include <assuan.h> /* fixme: need a way to avoid assuan calls here */
+#include "../common/i18n.h"
+
+
+/* A structure to store the information from the trust file. */
+struct trustitem_s
+{
+ struct
+ {
+ int disabled:1; /* This entry is disabled. */
+ int for_pgp:1; /* Set by '*' or 'P' as first flag. */
+ int for_smime:1; /* Set by '*' or 'S' as first flag. */
+ int relax:1; /* Relax checking of root certificate
+ constraints. */
+ int cm:1; /* Use chain model for validation. */
+ } flags;
+ unsigned char fpr[20]; /* The binary fingerprint. */
+};
+typedef struct trustitem_s trustitem_t;
+
+/* Malloced table and its allocated size with all trust items. */
+static trustitem_t *trusttable;
+static size_t trusttablesize;
+/* A mutex used to protect the table. */
+static npth_mutex_t trusttable_lock;
+
+
+static const char headerblurb[] =
+"# This is the list of trusted keys. Comment lines, like this one, as\n"
+"# well as empty lines are ignored. Lines have a length limit but this\n"
+"# is not a serious limitation as the format of the entries is fixed and\n"
+"# checked by gpg-agent. A non-comment line starts with optional white\n"
+"# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n"
+"# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n"
+"# other flags. The fingerprint may be prefixed with a '!' to mark the\n"
+"# key as not trusted. You should give the gpg-agent a HUP or run the\n"
+"# command \"gpgconf --reload gpg-agent\" after changing this file.\n"
+"\n\n"
+"# Include the default trust list\n"
+"include-default\n"
+"\n";
+
+
+/* This function must be called once to initialize this module. This
+ has to be done before a second thread is spawned. We can't do the
+ static initialization because Pth emulation code might not be able
+ to do a static init; in particular, it is not possible for W32. */
+void
+initialize_module_trustlist (void)
+{
+ static int initialized;
+ int err;
+
+ if (!initialized)
+ {
+ err = npth_mutex_init (&trusttable_lock, NULL);
+ if (err)
+ log_fatal ("failed to init mutex in %s: %s\n", __FILE__,strerror (err));
+ initialized = 1;
+ }
+}
+
+
+
+
+static void
+lock_trusttable (void)
+{
+ int err;
+
+ err = npth_mutex_lock (&trusttable_lock);
+ if (err)
+ log_fatal ("failed to acquire mutex in %s: %s\n", __FILE__, strerror (err));
+}
+
+
+static void
+unlock_trusttable (void)
+{
+ int err;
+
+ err = npth_mutex_unlock (&trusttable_lock);
+ if (err)
+ log_fatal ("failed to release mutex in %s: %s\n", __FILE__, strerror (err));
+}
+
+
+/* Clear the trusttable. The caller needs to make sure that the
+ trusttable is locked. */
+static inline void
+clear_trusttable (void)
+{
+ xfree (trusttable);
+ trusttable = NULL;
+ trusttablesize = 0;
+}
+
+
+/* Return the name of the system trustlist. Caller must free. */
+static char *
+make_sys_trustlist_name (void)
+{
+ if (opt.sys_trustlist_name
+ && (strchr (opt.sys_trustlist_name, '/')
+ || strchr (opt.sys_trustlist_name, '\\')
+ || (*opt.sys_trustlist_name == '~'
+ && opt.sys_trustlist_name[1] == '/')))
+ return make_absfilename (opt.sys_trustlist_name, NULL);
+ else
+ return make_filename (gnupg_sysconfdir (),
+ (opt.sys_trustlist_name ?
+ opt.sys_trustlist_name : "trustlist.txt"),
+ NULL);
+}
+
+
+static gpg_error_t
+read_one_trustfile (const char *fname, int systrust,
+ trustitem_t **addr_of_table,
+ size_t *addr_of_tablesize,
+ int *addr_of_tableidx)
+{
+ gpg_error_t err = 0;
+ estream_t fp;
+ int n, c;
+ char *p, line[256];
+ trustitem_t *table, *ti;
+ int tableidx;
+ size_t tablesize;
+ int lnr = 0;
+
+ table = *addr_of_table;
+ tablesize = *addr_of_tablesize;
+ tableidx = *addr_of_tableidx;
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ while (es_fgets (line, DIM(line)-1, fp))
+ {
+ lnr++;
+
+ n = strlen (line);
+ if (!n || line[n-1] != '\n')
+ {
+ /* Eat until end of line. */
+ while ( (c=es_getc (fp)) != EOF && c != '\n')
+ ;
+ err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+ : GPG_ERR_INCOMPLETE_LINE);
+ log_error (_("file '%s', line %d: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ continue;
+ }
+ line[--n] = 0; /* Chop the LF. */
+ if (n && line[n-1] == '\r')
+ line[--n] = 0; /* Chop an optional CR. */
+
+ /* Allow for empty lines and spaces */
+ for (p=line; spacep (p); p++)
+ ;
+ if (!*p || *p == '#')
+ continue;
+
+ if (!strncmp (p, "include-default", 15)
+ && (!p[15] || spacep (p+15)))
+ {
+ char *etcname;
+ gpg_error_t err2;
+ gpg_err_code_t ec;
+
+ if (systrust)
+ {
+ log_error (_("statement \"%s\" ignored in '%s', line %d\n"),
+ "include-default", fname, lnr);
+ continue;
+ }
+ /* fixme: Should check for trailing garbage. */
+
+ etcname = make_sys_trustlist_name ();
+ if ( !strcmp (etcname, fname) ) /* Same file. */
+ log_info (_("statement \"%s\" ignored in '%s', line %d\n"),
+ "include-default", fname, lnr);
+ else if ((ec=gnupg_access (etcname, F_OK)) && ec == GPG_ERR_ENOENT)
+ {
+ /* A non existent system trustlist is not an error.
+ Just print a note. */
+ log_info (_("system trustlist '%s' not available\n"), etcname);
+ }
+ else
+ {
+ err2 = read_one_trustfile (etcname, 1,
+ &table, &tablesize, &tableidx);
+ if (err2)
+ err = err2;
+ }
+ xfree (etcname);
+
+ continue;
+ }
+
+ if (tableidx == tablesize) /* Need more space. */
+ {
+ trustitem_t *tmp;
+ size_t tmplen;
+
+ tmplen = tablesize + 20;
+ tmp = xtryrealloc (table, tmplen * sizeof *table);
+ if (!tmp)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ table = tmp;
+ tablesize = tmplen;
+ }
+
+ ti = table + tableidx;
+
+ memset (&ti->flags, 0, sizeof ti->flags);
+ if (*p == '!')
+ {
+ ti->flags.disabled = 1;
+ p++;
+ while (spacep (p))
+ p++;
+ }
+
+ n = hexcolon2bin (p, ti->fpr, 20);
+ if (n < 0)
+ {
+ log_error (_("bad fingerprint in '%s', line %d\n"), fname, lnr);
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ continue;
+ }
+ p += n;
+ for (; spacep (p); p++)
+ ;
+
+ /* Process the first flag which needs to be the first for
+ backward compatibility. */
+ if (!*p || *p == '*' )
+ {
+ ti->flags.for_smime = 1;
+ ti->flags.for_pgp = 1;
+ }
+ else if ( *p == 'P' || *p == 'p')
+ {
+ ti->flags.for_pgp = 1;
+ }
+ else if ( *p == 'S' || *p == 's')
+ {
+ ti->flags.for_smime = 1;
+ }
+ else
+ {
+ log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr);
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ continue;
+ }
+ p++;
+ if ( *p && !spacep (p) )
+ {
+ log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr);
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ continue;
+ }
+
+ /* Now check for more key-value pairs of the form NAME[=VALUE]. */
+ while (*p)
+ {
+ for (; spacep (p); p++)
+ ;
+ if (!*p)
+ break;
+ n = strcspn (p, "= \t");
+ if (p[n] == '=')
+ {
+ log_error ("assigning a value to a flag is not yet supported; "
+ "in '%s', line %d\n", fname, lnr);
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ p++;
+ }
+ else if (n == 5 && !memcmp (p, "relax", 5))
+ ti->flags.relax = 1;
+ else if (n == 2 && !memcmp (p, "cm", 2))
+ ti->flags.cm = 1;
+ else
+ log_error ("flag '%.*s' in '%s', line %d ignored\n",
+ n, p, fname, lnr);
+ p += n;
+ }
+ tableidx++;
+ }
+ if ( !err && !es_feof (fp) )
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error reading '%s', line %d: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ }
+
+ leave:
+ es_fclose (fp);
+ *addr_of_table = table;
+ *addr_of_tablesize = tablesize;
+ *addr_of_tableidx = tableidx;
+ return err;
+}
+
+
+/* Read the trust files and update the global table on success. The
+ trusttable is assumed to be locked. */
+static gpg_error_t
+read_trustfiles (void)
+{
+ gpg_error_t err;
+ trustitem_t *table, *ti;
+ int tableidx;
+ size_t tablesize;
+ char *fname;
+ int systrust = 0;
+ gpg_err_code_t ec;
+
+ tablesize = 20;
+ table = xtrycalloc (tablesize, sizeof *table);
+ if (!table)
+ return gpg_error_from_syserror ();
+ tableidx = 0;
+
+ if (opt.no_user_trustlist)
+ fname = NULL;
+ else
+ {
+ fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
+ if (!fname)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (table);
+ return err;
+ }
+ }
+
+ if (!fname || (ec = gnupg_access (fname, F_OK)))
+ {
+ if (!fname)
+ ; /* --no-user-trustlist active. */
+ else if ( ec == GPG_ERR_ENOENT )
+ ; /* Silently ignore a non-existing trustfile. */
+ else
+ {
+ err = gpg_error (ec);
+ log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
+ }
+ xfree (fname);
+ fname = make_sys_trustlist_name ();
+ systrust = 1;
+ }
+ err = read_one_trustfile (fname, systrust, &table, &tablesize, &tableidx);
+ xfree (fname);
+
+ if (err)
+ {
+ xfree (table);
+ if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ {
+ /* Take a missing trustlist as an empty one. */
+ clear_trusttable ();
+ err = 0;
+ }
+ return err;
+ }
+
+ /* Fixme: we should drop duplicates and sort the table. */
+ ti = xtryrealloc (table, (tableidx?tableidx:1) * sizeof *table);
+ if (!ti)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (table);
+ return err;
+ }
+
+ /* Replace the trusttable. */
+ xfree (trusttable);
+ trusttable = ti;
+ trusttablesize = tableidx;
+ return 0;
+}
+
+
+/* Check whether the given fpr is in our trustdb. We expect FPR to be
+ an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is
+ true the function assumes that the trusttable is already locked. */
+static gpg_error_t
+istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled,
+ int already_locked)
+{
+ gpg_error_t err = 0;
+ int locked = already_locked;
+ trustitem_t *ti;
+ size_t len;
+ unsigned char fprbin[20];
+
+ if (r_disabled)
+ *r_disabled = 0;
+
+ if ( hexcolon2bin (fpr, fprbin, 20) < 0 )
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ goto leave;
+ }
+
+ if (!already_locked)
+ {
+ lock_trusttable ();
+ locked = 1;
+ }
+
+ if (!trusttable)
+ {
+ err = read_trustfiles ();
+ if (err)
+ {
+ log_error (_("error reading list of trusted root certificates\n"));
+ goto leave;
+ }
+ }
+
+ if (trusttable)
+ {
+ for (ti=trusttable, len = trusttablesize; len; ti++, len--)
+ if (!memcmp (ti->fpr, fprbin, 20))
+ {
+ if (ti->flags.disabled && r_disabled)
+ *r_disabled = 1;
+
+ /* Print status messages only if we have not been called
+ in a locked state. */
+ if (already_locked)
+ ;
+ else if (ti->flags.relax)
+ {
+ unlock_trusttable ();
+ locked = 0;
+ err = agent_write_status (ctrl, "TRUSTLISTFLAG", "relax", NULL);
+ }
+ else if (ti->flags.cm)
+ {
+ unlock_trusttable ();
+ locked = 0;
+ err = agent_write_status (ctrl, "TRUSTLISTFLAG", "cm", NULL);
+ }
+
+ if (!err)
+ err = ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
+ goto leave;
+ }
+ }
+ err = gpg_error (GPG_ERR_NOT_TRUSTED);
+
+ leave:
+ if (locked && !already_locked)
+ unlock_trusttable ();
+ return err;
+}
+
+
+/* Check whether the given fpr is in our trustdb. We expect FPR to be
+ an all uppercase hexstring of 40 characters. */
+gpg_error_t
+agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled)
+{
+ return istrusted_internal (ctrl, fpr, r_disabled, 0);
+}
+
+
+/* Write all trust entries to FP. */
+gpg_error_t
+agent_listtrusted (void *assuan_context)
+{
+ trustitem_t *ti;
+ char key[51];
+ gpg_error_t err;
+ size_t len;
+
+ lock_trusttable ();
+ if (!trusttable)
+ {
+ err = read_trustfiles ();
+ if (err)
+ {
+ unlock_trusttable ();
+ log_error (_("error reading list of trusted root certificates\n"));
+ return err;
+ }
+ }
+
+ if (trusttable)
+ {
+ for (ti=trusttable, len = trusttablesize; len; ti++, len--)
+ {
+ if (ti->flags.disabled)
+ continue;
+ bin2hex (ti->fpr, 20, key);
+ key[40] = ' ';
+ key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*'
+ : ti->flags.for_smime? 'S': ti->flags.for_pgp? 'P':' ');
+ key[42] = '\n';
+ assuan_send_data (assuan_context, key, 43);
+ assuan_send_data (assuan_context, NULL, 0); /* flush */
+ }
+ }
+
+ unlock_trusttable ();
+ return 0;
+}
+
+
+/* Create a copy of string with colons inserted after each two bytes.
+ Caller needs to release the string. In case of a memory failure,
+ NULL is returned. */
+static char *
+insert_colons (const char *string)
+{
+ char *buffer, *p;
+ size_t n = strlen (string);
+ size_t nnew = n + (n+1)/2;
+
+ p = buffer = xtrymalloc ( nnew + 1 );
+ if (!buffer)
+ return NULL;
+ while (*string)
+ {
+ *p++ = *string++;
+ if (*string)
+ {
+ *p++ = *string++;
+ if (*string)
+ *p++ = ':';
+ }
+ }
+ *p = 0;
+ assert (strlen (buffer) <= nnew);
+
+ return buffer;
+}
+
+
+/* To pretty print DNs in the Pinentry, we replace slashes by
+ REPLSTRING. The caller needs to free the returned string. NULL is
+ returned on error with ERRNO set. */
+static char *
+reformat_name (const char *name, const char *replstring)
+{
+ const char *s;
+ char *newname;
+ char *d;
+ size_t count;
+ size_t replstringlen = strlen (replstring);
+
+ /* If the name does not start with a slash it is not a preformatted
+ DN and thus we don't bother to reformat it. */
+ if (*name != '/')
+ return xtrystrdup (name);
+
+ /* Count the names. Note that a slash contained in a DN part is
+ expected to be C style escaped and thus the slashes we see here
+ are the actual part delimiters. */
+ for (s=name+1, count=0; *s; s++)
+ if (*s == '/')
+ count++;
+ newname = xtrymalloc (strlen (name) + count*replstringlen + 1);
+ if (!newname)
+ return NULL;
+ for (s=name+1, d=newname; *s; s++)
+ if (*s == '/')
+ d = stpcpy (d, replstring);
+ else
+ *d++ = *s;
+ *d = 0;
+ return newname;
+}
+
+
+/* Insert the given fpr into our trustdb. We expect FPR to be an all
+ uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'.
+ This function does first check whether that key has already been
+ put into the trustdb and returns success in this case. Before a
+ FPR actually gets inserted, the user is asked by means of the
+ Pinentry whether this is actual what he wants to do. */
+gpg_error_t
+agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
+{
+ gpg_error_t err = 0;
+ gpg_err_code_t ec;
+ char *desc;
+ char *fname;
+ estream_t fp;
+ char *fprformatted;
+ char *nameformatted;
+ int is_disabled;
+ int yes_i_trust;
+
+ /* Check whether we are at all allowed to modify the trustlist.
+ This is useful so that the trustlist may be a symlink to a global
+ trustlist with only admin privileges to modify it. Of course
+ this is not a secure way of denying access, but it avoids the
+ usual clicking on an Okay button most users are used to. */
+ fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
+ if (!fname)
+ return gpg_error_from_syserror ();
+
+ if ((ec = access (fname, W_OK)) && ec != GPG_ERR_ENOENT)
+ {
+ xfree (fname);
+ return gpg_error (GPG_ERR_EPERM);
+ }
+ xfree (fname);
+
+ if (!agent_istrusted (ctrl, fpr, &is_disabled))
+ {
+ return 0; /* We already got this fingerprint. Silently return
+ success. */
+ }
+
+ /* This feature must explicitly been enabled. */
+ if (!opt.allow_mark_trusted)
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ if (is_disabled)
+ {
+ /* There is an disabled entry in the trustlist. Return an error
+ so that the user won't be asked again for that one. Changing
+ this flag with the integrated marktrusted feature is and will
+ not be made possible. */
+ return gpg_error (GPG_ERR_NOT_TRUSTED);
+ }
+
+
+ /* Insert a new one. */
+ nameformatted = reformat_name (name, "%0A ");
+ if (!nameformatted)
+ return gpg_error_from_syserror ();
+
+ /* First a general question whether this is trusted. */
+ desc = xtryasprintf (
+ /* TRANSLATORS: This prompt is shown by the Pinentry
+ and has one special property: A "%%0A" is used by
+ Pinentry to insert a line break. The double
+ percent sign is actually needed because it is also
+ a printf format string. If you need to insert a
+ plain % sign, you need to encode it as "%%25". The
+ "%s" gets replaced by the name as stored in the
+ certificate. */
+ L_("Do you ultimately trust%%0A"
+ " \"%s\"%%0A"
+ "to correctly certify user certificates?"),
+ nameformatted);
+ if (!desc)
+ {
+ xfree (nameformatted);
+ return out_of_core ();
+ }
+ err = agent_get_confirmation (ctrl, desc, L_("Yes"), L_("No"), 1);
+ xfree (desc);
+ if (!err)
+ yes_i_trust = 1;
+ else if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
+ yes_i_trust = 0;
+ else
+ {
+ xfree (nameformatted);
+ return err;
+ }
+
+
+ fprformatted = insert_colons (fpr);
+ if (!fprformatted)
+ {
+ xfree (nameformatted);
+ return out_of_core ();
+ }
+
+ /* If the user trusts this certificate he has to verify the
+ fingerprint of course. */
+ if (yes_i_trust)
+ {
+ desc = xtryasprintf
+ (
+ /* TRANSLATORS: This prompt is shown by the Pinentry and has
+ one special property: A "%%0A" is used by Pinentry to
+ insert a line break. The double percent sign is actually
+ needed because it is also a printf format string. If you
+ need to insert a plain % sign, you need to encode it as
+ "%%25". The second "%s" gets replaced by a hexdecimal
+ fingerprint string whereas the first one receives the name
+ as stored in the certificate. */
+ L_("Please verify that the certificate identified as:%%0A"
+ " \"%s\"%%0A"
+ "has the fingerprint:%%0A"
+ " %s"), nameformatted, fprformatted);
+ if (!desc)
+ {
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return out_of_core ();
+ }
+
+ /* TRANSLATORS: "Correct" is the label of a button and intended
+ to be hit if the fingerprint matches the one of the CA. The
+ other button is "the default "Cancel" of the Pinentry. */
+ err = agent_get_confirmation (ctrl, desc, L_("Correct"), L_("Wrong"), 1);
+ xfree (desc);
+ if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
+ yes_i_trust = 0;
+ else if (err)
+ {
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return err;
+ }
+ }
+
+
+ /* Now check again to avoid duplicates. We take the lock to make
+ sure that nobody else plays with our file and force a reread. */
+ lock_trusttable ();
+ clear_trusttable ();
+ if (!istrusted_internal (ctrl, fpr, &is_disabled, 1) || is_disabled)
+ {
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
+ }
+
+ fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
+ if (!fname)
+ {
+ err = gpg_error_from_syserror ();
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return err;
+ }
+ if ((ec = access (fname, F_OK)) && ec == GPG_ERR_ENOENT)
+ {
+ fp = es_fopen (fname, "wx,mode=-rw-r");
+ if (!fp)
+ {
+ err = gpg_error (ec);
+ log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
+ xfree (fname);
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return err;
+ }
+ es_fputs (headerblurb, fp);
+ es_fclose (fp);
+ }
+ fp = es_fopen (fname, "a+,mode=-rw-r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
+ xfree (fname);
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return err;
+ }
+
+ /* Append the key. */
+ es_fputs ("\n# ", fp);
+ xfree (nameformatted);
+ nameformatted = reformat_name (name, "\n# ");
+ if (!nameformatted || strchr (name, '\n'))
+ {
+ /* Note that there should never be a LF in NAME but we better
+ play safe and print a sanitized version in this case. */
+ es_write_sanitized (fp, name, strlen (name), NULL, NULL);
+ }
+ else
+ es_fputs (nameformatted, fp);
+ es_fprintf (fp, "\n%s%s %c%s\n", yes_i_trust?"":"!", fprformatted, flag,
+ flag == 'S'? " relax":"");
+ if (es_ferror (fp))
+ err = gpg_error_from_syserror ();
+
+ if (es_fclose (fp))
+ err = gpg_error_from_syserror ();
+
+ clear_trusttable ();
+ xfree (fname);
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ if (!err)
+ bump_key_eventcounter ();
+ return err;
+}
+
+
+/* This function may be called to force reloading of the
+ trustlist. */
+void
+agent_reload_trustlist (void)
+{
+ /* All we need to do is to delete the trusttable. At the next
+ access it will get re-read. */
+ lock_trusttable ();
+ clear_trusttable ();
+ unlock_trusttable ();
+ bump_key_eventcounter ();
+}