summaryrefslogtreecommitdiff
path: root/agent
diff options
context:
space:
mode:
Diffstat (limited to 'agent')
-rw-r--r--agent/ChangeLog-20112617
-rw-r--r--agent/Makefile.am110
-rw-r--r--agent/Makefile.in1164
-rw-r--r--agent/agent.h373
-rw-r--r--agent/cache.c340
-rw-r--r--agent/call-pinentry.c1178
-rw-r--r--agent/call-scd.c1167
-rw-r--r--agent/command-ssh.c3089
-rw-r--r--agent/command.c2073
-rw-r--r--agent/divert-scd.c451
-rw-r--r--agent/findkey.c952
-rw-r--r--agent/genkey.c484
-rw-r--r--agent/gpg-agent.c2291
-rw-r--r--agent/learncard.c472
-rw-r--r--agent/minip12.c2360
-rw-r--r--agent/minip12.h36
-rw-r--r--agent/pkdecrypt.c151
-rw-r--r--agent/pksign.c261
-rw-r--r--agent/preset-passphrase.c270
-rw-r--r--agent/protect-tool.c1271
-rw-r--r--agent/protect.c1321
-rw-r--r--agent/t-protect.c310
-rw-r--r--agent/trans.c41
-rw-r--r--agent/trustlist.c763
24 files changed, 23545 insertions, 0 deletions
diff --git a/agent/ChangeLog-2011 b/agent/ChangeLog-2011
new file mode 100644
index 0000000..f543f27
--- /dev/null
+++ b/agent/ChangeLog-2011
@@ -0,0 +1,2617 @@
+2011-12-02 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-08-04 Werner Koch <wk@g10code.com>
+
+ * command.c (cmd_keyinfo, do_one_keyinfo): Support options --data
+ and --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-08-04 Werner Koch <wk@g10code.com>
+
+ * genkey.c (check_passphrase_pattern): Use gpg_strerror.
+
+ * command-ssh.c (ssh_receive_mpint_list): Remove set but unused
+ var ELEMS_PUBLIC_N.
+
+ * gpg-agent.c (main): Remove set but unused var MAY_COREDUMP.
+
+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-04-29 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c: Include estream.h
+ (main): s/pth_kill/es_pth_kill/.
+
+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-09-30 Werner Koch <wk@g10code.com>
+
+ * findkey.c (unprotect): Do not put the passphrase into the cache
+ if it has been changed.
+
+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-08-11 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (agent_askpin, agent_get_passphrase): 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-05-12 Werner Koch <wk@g10code.com>
+
+ * preset-passphrase.c (forget_passphrase): Actually implement
+ this. Fixes bug#1198.
+
+ * gpg-agent.c (handle_tick): Do not print die message with option -q.
+
+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-04 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (main): Add command --use-standard-socket-p.
+
+2010-05-03 Werner Koch <wk@g10code.com>
+
+ * gpg-agent.c (check_own_socket_thread): Do not release SOCKNAME
+ too early.
+
+2010-03-17 Werner Koch <wk@g10code.com>
+
+ * call-scd.c (unlock_scd): Send a BYE under certain conditions.
+
+2010-02-19 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (start_pinentry): Remove a translation prefix.
+
+2010-02-18 Werner Koch <wk@g10code.com>
+
+ * protect.c (agent_unprotect): Initialize CLEARTEXT.
+
+ * command.c (register_commands): Unconditionally use
+ assuan_register_post_cmd_notify.
+ (start_command_handler): Undocumented use assuan_set_io_monitor.
+
+2010-02-17 Werner Koch <wk@g10code.com>
+
+ * call-pinentry.c (start_pinentry): Always free OPTSTR. Send
+ default-xxx strings.
+
+2010-02-11 Marcus Brinkmann <marcus@g10code.de>
+
+ From trunk 2009-09-23, 2009-11-02, 2009-11-04, 2009-11-05, 2009-11-25,
+ 2009-12-08:
+
+ * Makefile.am (gpg_agent_CFLAGS, gpg_agent_LDADD): Use libassuan
+ instead of libassuan-pth.
+ * gpg-agent.c: Invoke ASSUAN_SYSTEM_PTH_IMPL.
+ (main): Update to new API. Call assuan_set_system_hooks and
+ assuan_sock_init. Fix invocation of assuan_socket_connect.
+ Call assuan_set_assuan_log_stream here.
+ (parse_rereadable_options): Don't set global assuan log
+ file (there ain't one anymore).
+ (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. Update use of
+ assuan_socket_connect.
+ * 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.
+ Use assuan_handler_t type. Add NULL arg to assuan_register_command.
+ Add help arg to assuan_register_command. Convert all command
+ comments to help strings.
+ (start_command_handler): Allocate assuan context before starting
+ server. Change assuan_init_socket_server_ext into
+ assuan_init_socket_server. Use assuan_fd_t and assuan_fdopen on fds.
+ Do not call assuan_set_log_stream anymore.
+ (reset_notify): Take LINE arg and return error.
+ * 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 assuan_pipe_connect, notassuan_pipe_connect_ext.
+ Convert posix fd to assuan fd.
+ * 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.
+ Update use of assuan_socket_connect and assuan_pipe_connect.
+ Convert posix fd to assuan fd.
+
+2010-01-26 Werner Koch <wk@g10code.com>
+
+ * protect.c (do_encryption): Encode the s2kcount and do 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-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-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 tehre 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 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.
diff --git a/agent/Makefile.am b/agent/Makefile.am
new file mode 100644
index 0000000..cc8a22a
--- /dev/null
+++ b/agent/Makefile.am
@@ -0,0 +1,110 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+## Process this file with automake to produce Makefile.in
+
+bin_PROGRAMS = gpg-agent
+libexec_PROGRAMS = gpg-protect-tool gpg-preset-passphrase
+noinst_PROGRAMS = $(TESTS)
+
+# EXTRA_DIST = gpg-agent.ico gpg-agent-resource.rc
+EXTRA_DIST = ChangeLog-2011
+
+AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common -I$(top_srcdir)/intl
+
+include $(top_srcdir)/am/cmacros.am
+
+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 \
+ call-scd.c \
+ learncard.c
+
+common_libs = $(libcommon) ../jnlib/libjnlib.a ../gl/libgnu.a
+commonpth_libs = $(libcommonpth) ../jnlib/libjnlib.a ../gl/libgnu.a
+pwquery_libs = ../common/libsimple-pwquery.a
+
+#if HAVE_W32_SYSTEM
+#.rc.o:
+# $(WINDRES) `echo $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) | \
+# sed -e 's/-I/--include-dir /g;s/-D/--define /g'` -i $< -o $@
+#
+#gpg_agent_res_ldflags = -Wl,gpg-agent-resource.o -Wl,--subsystem,windows
+#gpg_agent_res_deps = gpg-agent-resource.o
+#else
+gpg_agent_res_ldflags =
+gpg_agent_res_deps =
+#endif
+
+
+gpg_agent_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS)
+gpg_agent_LDADD = $(commonpth_libs) \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(PTH_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+gpg_agent_LDFLAGS = $(gpg_agent_res_ldflags)
+gpg_agent_DEPENDENCIES = $(gpg_agent_res_deps)
+
+gpg_protect_tool_SOURCES = \
+ protect-tool.c \
+ protect.c \
+ minip12.c minip12.h
+
+gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS)
+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) \
+ $(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
+#
+TESTS = t-protect
+
+t_common_ldadd = $(common_libs) \
+ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
+
+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..e8aa48f
--- /dev/null
+++ b/agent/Makefile.in
@@ -0,0 +1,1164 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 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 <http://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 <http://www.gnu.org/licenses/>.
+
+VPATH = @srcdir@
+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) \
+ gpg-preset-passphrase$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \
+ $(top_srcdir)/am/cmacros.am
+@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -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@\""
+
+
+# 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_2 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\""
+@GNUPG_PINENTRY_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\""
+@GNUPG_SCDAEMON_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\""
+@GNUPG_DIRMNGR_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\""
+@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\""
+TESTS = t-protect$(EXEEXT)
+subdir = agent
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/gl/m4/absolute-header.m4 \
+ $(top_srcdir)/gl/m4/alloca.m4 $(top_srcdir)/gl/m4/allocsa.m4 \
+ $(top_srcdir)/gl/m4/eealloc.m4 \
+ $(top_srcdir)/gl/m4/gnulib-comp.m4 \
+ $(top_srcdir)/gl/m4/gnulib-tool.m4 \
+ $(top_srcdir)/gl/m4/mkdtemp.m4 $(top_srcdir)/gl/m4/setenv.m4 \
+ $(top_srcdir)/gl/m4/stdint.m4 $(top_srcdir)/gl/m4/strpbrk.m4 \
+ $(top_srcdir)/gl/m4/unistd_h.m4 $(top_srcdir)/m4/autobuild.m4 \
+ $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/estream.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/gnupg-pth.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/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/longdouble.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/size_max.m4 \
+ $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \
+ $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/m4/xsize.m4 \
+ $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/scripts/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)"
+am__EXEEXT_1 = 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-call-scd.$(OBJEXT) gpg_agent-learncard.$(OBJEXT)
+gpg_agent_OBJECTS = $(am_gpg_agent_OBJECTS)
+am__DEPENDENCIES_1 =
+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)
+gpg_preset_passphrase_DEPENDENCIES = $(pwquery_libs) $(common_libs) \
+ $(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-minip12.$(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_2 = $(common_libs) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+t_protect_DEPENDENCIES = $(am__DEPENDENCIES_2)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/scripts/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+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)
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors = \
+red=; grn=; lgn=; blu=; std=
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ABSOLUTE_STDINT_H = @ABSOLUTE_STDINT_H@
+ACLOCAL = @ACLOCAL@
+ADNSLIBS = @ADNSLIBS@
+ALLOCA = @ALLOCA@
+ALLOCA_H = @ALLOCA_H@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@
+BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@
+BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@
+BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@
+BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@
+BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@
+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@
+EXEEXT = @EXEEXT@
+FAQPROG = @FAQPROG@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GNUPG_AGENT_PGM = @GNUPG_AGENT_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_CURL = @GPGKEYS_CURL@
+GPGKEYS_FINGER = @GPGKEYS_FINGER@
+GPGKEYS_HKP = @GPGKEYS_HKP@
+GPGKEYS_KDNS = @GPGKEYS_KDNS@
+GPGKEYS_LDAP = @GPGKEYS_LDAP@
+GPGKEYS_MAILTO = @GPGKEYS_MAILTO@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GREP = @GREP@
+HAVE_INTTYPES_H = @HAVE_INTTYPES_H@
+HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@
+HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@
+HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@
+HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@
+HAVE_STDINT_H = @HAVE_STDINT_H@
+HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@
+HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@
+HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@
+HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@
+HAVE_WCHAR_H = @HAVE_WCHAR_H@
+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@
+LDAPLIBS = @LDAPLIBS@
+LDAP_CPPFLAGS = @LDAP_CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBGNU_LIBDEPS = @LIBGNU_LIBDEPS@
+LIBGNU_LTLIBDEPS = @LIBGNU_LTLIBDEPS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBREADLINE = @LIBREADLINE@
+LIBS = @LIBS@
+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@
+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@
+POSUB = @POSUB@
+PTH_CFLAGS = @PTH_CFLAGS@
+PTH_CONFIG = @PTH_CONFIG@
+PTH_LIBS = @PTH_LIBS@
+PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@
+RANLIB = @RANLIB@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SHRED = @SHRED@
+SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@
+SIZE_T_SUFFIX = @SIZE_T_SUFFIX@
+STDINT_H = @STDINT_H@
+STRIP = @STRIP@
+SYS_SOCKET_H = @SYS_SOCKET_H@
+TAR = @TAR@
+UNISTD_H = @UNISTD_H@
+USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+W32SOCKLIBS = @W32SOCKLIBS@
+WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@
+WINDRES = @WINDRES@
+WINT_T_SUFFIX = @WINT_T_SUFFIX@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+ZLIBS = @ZLIBS@
+_libcurl_config = @_libcurl_config@
+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@
+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 = gpg-agent.ico gpg-agent-resource.rc
+EXTRA_DIST = ChangeLog-2011
+AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common \
+ -I$(top_srcdir)/intl -DLOCALEDIR=\"$(localedir)\" \
+ $(am__append_1) $(am__append_2) $(am__append_3) \
+ $(am__append_4) $(am__append_5) $(am__append_6)
+
+# Convenience macros
+libcommon = ../common/libcommon.a
+libcommonpth = ../common/libcommonpth.a
+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 \
+ call-scd.c \
+ learncard.c
+
+common_libs = $(libcommon) ../jnlib/libjnlib.a ../gl/libgnu.a
+commonpth_libs = $(libcommonpth) ../jnlib/libjnlib.a ../gl/libgnu.a
+pwquery_libs = ../common/libsimple-pwquery.a
+
+#if HAVE_W32_SYSTEM
+#.rc.o:
+# $(WINDRES) `echo $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) | \
+# sed -e 's/-I/--include-dir /g;s/-D/--define /g'` -i $< -o $@
+#
+#gpg_agent_res_ldflags = -Wl,gpg-agent-resource.o -Wl,--subsystem,windows
+#gpg_agent_res_deps = gpg-agent-resource.o
+#else
+gpg_agent_res_ldflags =
+gpg_agent_res_deps =
+#endif
+gpg_agent_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS)
+gpg_agent_LDADD = $(commonpth_libs) \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(PTH_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
+gpg_agent_LDFLAGS = $(gpg_agent_res_ldflags)
+gpg_agent_DEPENDENCIES = $(gpg_agent_res_deps)
+gpg_protect_tool_SOURCES = \
+ protect-tool.c \
+ protect.c \
+ minip12.c minip12.h
+
+gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS)
+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) \
+ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
+t_common_ldadd = $(common_libs) \
+ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
+
+t_protect_SOURCES = t-protect.c protect.c
+t_protect_LDADD = $(t_common_ldadd)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(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
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(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):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ 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)
+ test -z "$(libexecdir)" || $(MKDIR_P) "$(DESTDIR)$(libexecdir)"
+ @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \
+ 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)
+ @rm -f gpg-agent$(EXEEXT)
+ $(gpg_agent_LINK) $(gpg_agent_OBJECTS) $(gpg_agent_LDADD) $(LIBS)
+gpg-preset-passphrase$(EXEEXT): $(gpg_preset_passphrase_OBJECTS) $(gpg_preset_passphrase_DEPENDENCIES)
+ @rm -f gpg-preset-passphrase$(EXEEXT)
+ $(LINK) $(gpg_preset_passphrase_OBJECTS) $(gpg_preset_passphrase_LDADD) $(LIBS)
+gpg-protect-tool$(EXEEXT): $(gpg_protect_tool_OBJECTS) $(gpg_protect_tool_DEPENDENCIES)
+ @rm -f gpg-protect-tool$(EXEEXT)
+ $(gpg_protect_tool_LINK) $(gpg_protect_tool_OBJECTS) $(gpg_protect_tool_LDADD) $(LIBS)
+t-protect$(EXEEXT): $(t_protect_OBJECTS) $(t_protect_DEPENDENCIES)
+ @rm -f t-protect$(EXEEXT)
+ $(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@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-call-pinentry.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-call-scd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-command-ssh.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-command.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-divert-scd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-findkey.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-genkey.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-gpg-agent.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-learncard.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-pkdecrypt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-pksign.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-protect.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-trans.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-trustlist.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-minip12.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-protect-tool.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-protect.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/preset-passphrase.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protect.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-protect.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+gpg_agent-gpg-agent.o: gpg-agent.c
+@am__fastdepCC_TRUE@ $(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__mv) $(DEPDIR)/gpg_agent-gpg-agent.Tpo $(DEPDIR)/gpg_agent-gpg-agent.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-gpg-agent.Tpo $(DEPDIR)/gpg_agent-gpg-agent.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-command.Tpo $(DEPDIR)/gpg_agent-command.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='command.c' object='gpg_agent-command.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-command.Tpo $(DEPDIR)/gpg_agent-command.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='command.c' object='gpg_agent-command.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-command-ssh.Tpo $(DEPDIR)/gpg_agent-command-ssh.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-command-ssh.Tpo $(DEPDIR)/gpg_agent-command-ssh.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-call-pinentry.Tpo $(DEPDIR)/gpg_agent-call-pinentry.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-call-pinentry.Tpo $(DEPDIR)/gpg_agent-call-pinentry.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-cache.Tpo $(DEPDIR)/gpg_agent-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='cache.c' object='gpg_agent-cache.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-cache.Tpo $(DEPDIR)/gpg_agent-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='cache.c' object='gpg_agent-cache.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-trans.Tpo $(DEPDIR)/gpg_agent-trans.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='trans.c' object='gpg_agent-trans.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-trans.Tpo $(DEPDIR)/gpg_agent-trans.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='trans.c' object='gpg_agent-trans.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-findkey.Tpo $(DEPDIR)/gpg_agent-findkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='findkey.c' object='gpg_agent-findkey.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-findkey.Tpo $(DEPDIR)/gpg_agent-findkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='findkey.c' object='gpg_agent-findkey.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-pksign.Tpo $(DEPDIR)/gpg_agent-pksign.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='pksign.c' object='gpg_agent-pksign.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-pksign.Tpo $(DEPDIR)/gpg_agent-pksign.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='pksign.c' object='gpg_agent-pksign.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-pkdecrypt.Tpo $(DEPDIR)/gpg_agent-pkdecrypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='pkdecrypt.c' object='gpg_agent-pkdecrypt.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-pkdecrypt.Tpo $(DEPDIR)/gpg_agent-pkdecrypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='pkdecrypt.c' object='gpg_agent-pkdecrypt.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-genkey.Tpo $(DEPDIR)/gpg_agent-genkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='genkey.c' object='gpg_agent-genkey.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-genkey.Tpo $(DEPDIR)/gpg_agent-genkey.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='genkey.c' object='gpg_agent-genkey.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-protect.Tpo $(DEPDIR)/gpg_agent-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='protect.c' object='gpg_agent-protect.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-protect.Tpo $(DEPDIR)/gpg_agent-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='protect.c' object='gpg_agent-protect.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-trustlist.Tpo $(DEPDIR)/gpg_agent-trustlist.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='trustlist.c' object='gpg_agent-trustlist.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-trustlist.Tpo $(DEPDIR)/gpg_agent-trustlist.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='trustlist.c' object='gpg_agent-trustlist.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-divert-scd.Tpo $(DEPDIR)/gpg_agent-divert-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-divert-scd.Tpo $(DEPDIR)/gpg_agent-divert-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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-call-scd.o: call-scd.c
+@am__fastdepCC_TRUE@ $(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__mv) $(DEPDIR)/gpg_agent-call-scd.Tpo $(DEPDIR)/gpg_agent-call-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-call-scd.Tpo $(DEPDIR)/gpg_agent-call-scd.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-learncard.Tpo $(DEPDIR)/gpg_agent-learncard.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='learncard.c' object='gpg_agent-learncard.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_agent-learncard.Tpo $(DEPDIR)/gpg_agent-learncard.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='learncard.c' object='gpg_agent-learncard.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(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@ $(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__mv) $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo $(DEPDIR)/gpg_protect_tool-protect-tool.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo $(DEPDIR)/gpg_protect_tool-protect-tool.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_protect_tool-protect.Tpo $(DEPDIR)/gpg_protect_tool-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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@ $(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__mv) $(DEPDIR)/gpg_protect_tool-protect.Tpo $(DEPDIR)/gpg_protect_tool-protect.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ 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@ $(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-minip12.o: minip12.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-minip12.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-minip12.Tpo -c -o gpg_protect_tool-minip12.o `test -f 'minip12.c' || echo '$(srcdir)/'`minip12.c
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/gpg_protect_tool-minip12.Tpo $(DEPDIR)/gpg_protect_tool-minip12.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='minip12.c' object='gpg_protect_tool-minip12.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-minip12.o `test -f 'minip12.c' || echo '$(srcdir)/'`minip12.c
+
+gpg_protect_tool-minip12.obj: minip12.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-minip12.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-minip12.Tpo -c -o gpg_protect_tool-minip12.obj `if test -f 'minip12.c'; then $(CYGPATH_W) 'minip12.c'; else $(CYGPATH_W) '$(srcdir)/minip12.c'; fi`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/gpg_protect_tool-minip12.Tpo $(DEPDIR)/gpg_protect_tool-minip12.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='minip12.c' object='gpg_protect_tool-minip12.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-minip12.obj `if test -f 'minip12.c'; then $(CYGPATH_W) 'minip12.c'; else $(CYGPATH_W) '$(srcdir)/minip12.c'; fi`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ 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
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ 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"
+
+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; 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 \
+ echo "$$grn$$dashes"; \
+ else \
+ echo "$$red$$dashes"; \
+ fi; \
+ echo "$$banner"; \
+ test -z "$$skipped" || echo "$$skipped"; \
+ test -z "$$report" || echo "$$report"; \
+ echo "$$dashes$$std"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(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:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+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 -rf ./$(DEPDIR)
+ -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 -rf ./$(DEPDIR)
+ -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 all all-am check check-TESTS check-am clean \
+ clean-binPROGRAMS clean-generic clean-libexecPROGRAMS \
+ clean-noinstPROGRAMS ctags 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 uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-libexecPROGRAMS
+
+
+# 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..15cf8bf
--- /dev/null
+++ b/agent/agent.h
@@ -0,0 +1,373 @@
+/* agent.h - Global definitions for the agent
+ * Copyright (C) 2001, 2002, 2003, 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 <http://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"
+
+/* 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
+
+/* A large struct name "opt" to keep global flags */
+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 */
+ const char *homedir; /* Configuration directory name */
+
+ /* Environment setting gathered at program start or changed using the
+ Assuan command UPDATESTARTUPTTY. */
+ session_env_t startup_env;
+ char *startup_lc_ctype;
+ char *startup_lc_messages;
+
+ /* True if we are listening on the standard socket. */
+ int use_standard_socket;
+
+ /* True if we handle sigusr2. */
+ int sigusr2_enabled;
+
+ const char *pinentry_program; /* Filename of the program to start as
+ pinentry. */
+ const char *scdaemon_program; /* Filename of the program to handle
+ smartcard tasks. */
+ 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 tocuh before exiting. If
+ this is not set the filoe name of the standard socket is used. */
+ const char *pinentry_touch_file;
+
+ /* 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. */
+ const char *check_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_passhrase_history;
+
+ int running_detached; /* We are running detached from the tty. */
+
+ int ignore_cache_for_signing;
+ int allow_mark_trusted;
+ int allow_preset_passphrase;
+ int keep_tty; /* Don't switch the TTY (for pinentry) on request */
+ int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */
+ int ssh_support; /* Enable ssh-agent emulation. */
+} opt;
+
+
+#define DBG_COMMAND_VALUE 1 /* debug commands i/o */
+#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_ASSUAN_VALUE 1024
+
+#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE)
+#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_ASSUAN (opt.debug & DBG_ASSUAN_VALUE)
+
+struct server_local_s;
+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 just a few bytes. */
+ struct {
+ gnupg_fd_t fd;
+ } thread_startup;
+
+ /* 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;
+
+ session_env_t session_env;
+ char *lc_ctype;
+ char *lc_messages;
+
+ struct {
+ int algo;
+ unsigned char value[MAX_DIGEST_LEN];
+ int valuelen;
+ int raw_value: 1;
+ } digest;
+ unsigned char keygrip[20];
+ int have_keygrip;
+
+ int use_auth_call; /* Hack to send the PKAUTH command instead of the
+ PKSIGN command to the scdaemon. */
+ int in_passwd; /* Hack to inhibit enforced passphrase change
+ during an explicit passwd command. */
+};
+
+
+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;
+ int failed_tries;
+ int with_qualitybar; /* Set if the quality bar should be displayed. */
+ int (*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 displaye a specific error */
+ size_t max_length; /* allocated length of the buffer */
+ char pin[1];
+};
+
+
+enum
+ {
+ PRIVATE_KEY_UNKNOWN = 0,
+ PRIVATE_KEY_CLEAR = 1,
+ PRIVATE_KEY_PROTECTED = 2,
+ PRIVATE_KEY_SHADOWED = 3
+ };
+
+
+/* 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_t;
+
+
+/* The type of a function to lookup a TTL by a keygrip. */
+typedef int (*lookup_ttl_t)(const char *hexgrip);
+
+
+/*-- gpg-agent.c --*/
+void agent_exit (int rc) JNLIB_GCC_A_NR; /* Also implemented in other tools */
+const char *get_agent_socket_name (void);
+const char *get_agent_ssh_socket_name (void);
+#ifdef HAVE_W32_SYSTEM
+void *get_agent_scd_notify_event (void);
+#endif
+void agent_sighup_action (void);
+
+/*-- command.c --*/
+gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid);
+gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...)
+ GNUPG_GCC_A_SENTINEL(0);
+void bump_key_eventcounter (void);
+void bump_card_eventcounter (void);
+void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
+
+/*-- command-ssh.c --*/
+void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
+
+/*-- findkey.c --*/
+int agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force);
+gpg_error_t agent_key_from_file (ctrl_t ctrl,
+ 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);
+gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+ gcry_sexp_t *result);
+gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result);
+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);
+
+/*-- 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);
+int agent_askpin (ctrl_t ctrl,
+ const char *desc_text, const char *prompt_text,
+ const char *inital_errtext,
+ struct pin_entry_info_s *pininfo);
+int agent_get_passphrase (ctrl_t ctrl, char **retpass,
+ const char *desc, const char *prompt,
+ const char *errtext, int with_qualitybar);
+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);
+
+
+/*-- cache.c --*/
+void agent_flush_cache (void);
+int agent_put_cache (const char *key, cache_mode_t cache_mode,
+ const char *data, int ttl);
+const char *agent_get_cache (const char *key, cache_mode_t cache_mode,
+ void **cache_id);
+void agent_unlock_cache_entry (void **cache_id);
+
+
+/*-- pksign.c --*/
+int agent_pksign_do (ctrl_t ctrl, const char *desc_text,
+ gcry_sexp_t *signature_sexp,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl);
+int agent_pksign (ctrl_t ctrl, 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);
+
+/*-- genkey.c --*/
+int check_passphrase_constraints (ctrl_t ctrl, const char *pw, int silent);
+int agent_genkey (ctrl_t ctrl,
+ const char *keyparam, size_t keyparmlen, membuf_t *outbuf);
+int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey);
+
+/*-- protect.c --*/
+unsigned long get_standard_s2k_count (void);
+int agent_protect (const unsigned char *plainkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen);
+int agent_unprotect (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);
+int 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);
+
+
+/*-- 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 unsigned char *digest, size_t digestlen, int algo,
+ const unsigned char *shadow_info, unsigned char **r_sig);
+int divert_pkdecrypt (ctrl_t ctrl,
+ const unsigned char *cipher,
+ const unsigned char *shadow_info,
+ char **r_buf, size_t *r_len);
+int divert_generic_cmd (ctrl_t ctrl,
+ const char *cmdline, void *assuan_context);
+
+
+/*-- 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);
+int agent_card_pksign (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg,
+ 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 *, char*,size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen);
+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);
+gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result);
+int agent_card_scd (ctrl_t ctrl, const char *cmdline,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg, void *assuan_context);
+
+
+/*-- learncard.c --*/
+int agent_handle_learn (ctrl_t ctrl, void *assuan_context);
+
+
+#endif /*AGENT_H*/
diff --git a/agent/cache.c b/agent/cache.c
new file mode 100644
index 0000000..10f9ef6
--- /dev/null
+++ b/agent/cache.c
@@ -0,0 +1,340 @@
+/* cache.c - keep a cache of passphrases
+ * Copyright (C) 2002 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+#include "agent.h"
+
+struct secret_data_s {
+ int totallen; /* this includes the padding */
+ int datalen; /* actual data length */
+ char data[1];
+};
+
+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 */
+ int lockcount;
+ struct secret_data_s *pw;
+ cache_mode_t cache_mode;
+ char key[1];
+};
+
+
+static ITEM thecache;
+
+
+static void
+release_data (struct secret_data_s *data)
+{
+ xfree (data);
+}
+
+static struct secret_data_s *
+new_data (const void *data, size_t length)
+{
+ struct secret_data_s *d;
+ int total;
+
+ /* we pad the data to 32 bytes so that it get more complicated
+ finding something out by watching allocation patterns. This is
+ usally not possible but we better assume nothing about our
+ secure storage provider*/
+ total = length + 32 - (length % 32);
+
+ d = gcry_malloc_secure (sizeof *d + total - 1);
+ if (d)
+ {
+ d->totallen = total;
+ d->datalen = length;
+ memcpy (d->data, data, length);
+ }
+ return d;
+}
+
+
+
+/* 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->lockcount && r->pw
+ && r->ttl >= 0 && r->accessed + r->ttl < current)
+ {
+ if (DBG_CACHE)
+ log_debug (" expired `%s' (%ds after last access)\n",
+ r->key, 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->lockcount && r->pw && r->created + maxttl < current)
+ {
+ if (DBG_CACHE)
+ log_debug (" expired `%s' (%lus after creation)\n",
+ r->key, 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)
+ {
+ if (r->lockcount)
+ {
+ log_error ("can't remove unused cache entry `%s' due to"
+ " lockcount=%d\n",
+ r->key, r->lockcount);
+ r->accessed += 60*10; /* next error message in 10 minutes */
+ rprev = r;
+ r = r->next;
+ }
+ else
+ {
+ ITEM r2 = r->next;
+ if (DBG_CACHE)
+ log_debug (" removed `%s' (slot not used for 30m)\n", r->key);
+ xfree (r);
+ if (!rprev)
+ thecache = r2;
+ else
+ rprev->next = r2;
+ r = r2;
+ }
+ }
+ else
+ {
+ rprev = r;
+ r = r->next;
+ }
+ }
+}
+
+
+void
+agent_flush_cache (void)
+{
+ ITEM r;
+
+ if (DBG_CACHE)
+ log_debug ("agent_flush_cache\n");
+
+ for (r=thecache; r; r = r->next)
+ {
+ if (!r->lockcount && r->pw)
+ {
+ if (DBG_CACHE)
+ log_debug (" flushing `%s'\n", r->key);
+ release_data (r->pw);
+ r->pw = NULL;
+ r->accessed = 0;
+ }
+ else if (r->lockcount && r->pw)
+ {
+ if (DBG_CACHE)
+ log_debug (" marked `%s' for flushing\n", r->key);
+ r->accessed = 0;
+ r->ttl = 0;
+ }
+ }
+}
+
+
+
+/* Store DATA of length DATALEN 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 (const char *key, cache_mode_t cache_mode,
+ const char *data, int ttl)
+{
+ ITEM r;
+
+ if (DBG_CACHE)
+ log_debug ("agent_put_cache `%s' requested ttl=%d mode=%d\n",
+ key, ttl, cache_mode);
+ 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 || cache_mode == CACHE_MODE_IGNORE)
+ return 0;
+
+ for (r=thecache; r; r = r->next)
+ {
+ if (!r->lockcount && !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;
+ r->pw = new_data (data, strlen (data)+1);
+ if (!r->pw)
+ log_error ("out of core while allocating new cache item\n");
+ }
+ }
+ else if (data)
+ { /* simply insert */
+ r = xtrycalloc (1, sizeof *r + strlen (key));
+ if (!r)
+ log_error ("out of core while allocating new cache control\n");
+ else
+ {
+ strcpy (r->key, key);
+ r->created = r->accessed = gnupg_get_time ();
+ r->ttl = ttl;
+ r->cache_mode = cache_mode;
+ r->pw = new_data (data, strlen (data)+1);
+ if (!r->pw)
+ {
+ log_error ("out of core while allocating new cache item\n");
+ xfree (r);
+ }
+ else
+ {
+ r->next = thecache;
+ thecache = r;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/* Try to find an item in the cache. Note that we currently don't
+ make use of CACHE_MODE. */
+const char *
+agent_get_cache (const char *key, cache_mode_t cache_mode, void **cache_id)
+{
+ ITEM r;
+
+ if (cache_mode == CACHE_MODE_IGNORE)
+ return NULL;
+
+ if (DBG_CACHE)
+ log_debug ("agent_get_cache `%s'...\n", key);
+ housekeeping ();
+
+ /* first try to find one with no locks - this is an updated cache
+ entry: We might have entries with a lockcount and without a
+ lockcount. */
+ for (r=thecache; r; r = r->next)
+ {
+ if (!r->lockcount && r->pw && !strcmp (r->key, key))
+ {
+ /* put_cache does only put strings into the cache, so we
+ don't need the lengths */
+ r->accessed = gnupg_get_time ();
+ if (DBG_CACHE)
+ log_debug ("... hit\n");
+ r->lockcount++;
+ *cache_id = r;
+ return r->pw->data;
+ }
+ }
+ /* again, but this time get even one with a lockcount set */
+ for (r=thecache; r; r = r->next)
+ {
+ if (r->pw && !strcmp (r->key, key))
+ {
+ r->accessed = gnupg_get_time ();
+ if (DBG_CACHE)
+ log_debug ("... hit (locked)\n");
+ r->lockcount++;
+ *cache_id = r;
+ return r->pw->data;
+ }
+ }
+ if (DBG_CACHE)
+ log_debug ("... miss\n");
+
+ *cache_id = NULL;
+ return NULL;
+}
+
+
+void
+agent_unlock_cache_entry (void **cache_id)
+{
+ ITEM r;
+
+ for (r=thecache; r; r = r->next)
+ {
+ if (r == *cache_id)
+ {
+ if (!r->lockcount)
+ log_error ("trying to unlock non-locked cache entry `%s'\n",
+ r->key);
+ else
+ r->lockcount--;
+ return;
+ }
+ }
+}
diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c
new file mode 100644
index 0000000..ad1bd03
--- /dev/null
+++ b/agent/call-pinentry.c
@@ -0,0 +1,1178 @@
+/* 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 <http://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>
+#ifndef HAVE_W32_SYSTEM
+# include <sys/wait.h>
+# include <sys/types.h>
+# include <signal.h>
+#endif
+#include <pth.h>
+
+#include "agent.h"
+#include <assuan.h>
+#include "setenv.h"
+#include "i18n.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 mutual dialog) we should better timeout further
+ requests after some time. 2 minutes seem to be a reasonable
+ time. */
+#define LOCK_TIMEOUT (1*60)
+
+/* The assuan context of the current pinentry. */
+static assuan_context_t entry_ctx;
+
+/* The control variable of the connection owning the current pinentry.
+ This is only valid if ENTRY_CTX is not NULL. Note, that we care
+ only about the value of the pointer and that it should never be
+ dereferenced. */
+static ctrl_t entry_owner;
+
+/* A mutex used to serialize access to the pinentry. */
+static pth_mutex_t entry_lock;
+
+/* The thread ID of the popup working thread. */
+static pth_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;
+};
+
+
+
+
+/* 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;
+
+ if (!initialized)
+ {
+ if (pth_mutex_init (&entry_lock))
+ initialized = 1;
+ }
+}
+
+
+
+static void
+dump_mutex_state (pth_mutex_t *m)
+{
+#ifdef _W32_PTH_H
+ (void)m;
+ log_printf ("unknown under W32");
+#else
+ if (!(m->mx_state & PTH_MUTEX_INITIALIZED))
+ log_printf ("not_initialized");
+ else if (!(m->mx_state & PTH_MUTEX_LOCKED))
+ log_printf ("not_locked");
+ else
+ log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count);
+#endif
+}
+
+
+/* This function may be called to print infromation pertaining to the
+ current state of this module to the log. */
+void
+agent_query_dump_state (void)
+{
+ log_info ("agent_query_dump_state: entry_lock=");
+ dump_mutex_state (&entry_lock);
+ log_printf ("\n");
+ log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n",
+ entry_ctx, (long)assuan_get_pid (entry_ctx), 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 && entry_owner == ctrl)
+ {
+ 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 int
+unlock_pinentry (int rc)
+{
+ assuan_context_t ctx = entry_ctx;
+
+ entry_ctx = NULL;
+ if (!pth_mutex_release (&entry_lock))
+ {
+ log_error ("failed to release the entry lock\n");
+ if (!rc)
+ rc = gpg_error (GPG_ERR_INTERNAL);
+ }
+ assuan_release (ctx);
+ return rc;
+}
+
+
+/* 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)
+ {
+ int iterator = 0;
+ const char *name, *assname, *value;
+
+ gcry_control (GCRYCTL_TERM_SECMEM);
+
+ 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. */
+ if (!assname
+ || !strcmp (name, "XAUTHORITY")
+ || !strcmp (name, "PINENTRY_USER_DATA"))
+ {
+ value = session_env_getenv (ctrl->session_env, name);
+ if (value)
+ setenv (name, value, 1);
+ }
+ }
+ }
+}
+
+
+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 aquire the lock for the
+ pinentry - we will serialize _all_ pinentry calls.
+ */
+static int
+start_pinentry (ctrl_t ctrl)
+{
+ int rc;
+ const char *pgmname;
+ assuan_context_t ctx;
+ const char *argv[5];
+ int no_close_list[3];
+ int i;
+ pth_event_t evt;
+ const char *tmpstr;
+ unsigned long pinentry_pid;
+ const char *value;
+
+ evt = pth_event (PTH_EVENT_TIME, pth_timeout (LOCK_TIMEOUT, 0));
+ if (!pth_mutex_acquire (&entry_lock, 0, evt))
+ {
+ if (pth_event_occurred (evt))
+ rc = gpg_error (GPG_ERR_TIMEOUT);
+ else
+ rc = gpg_error (GPG_ERR_INTERNAL);
+ pth_event_free (evt, PTH_FREE_THIS);
+ log_error (_("failed to acquire the pinentry lock: %s\n"),
+ gpg_strerror (rc));
+ return rc;
+ }
+ pth_event_free (evt, PTH_FREE_THIS);
+
+ entry_owner = ctrl;
+
+ 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 Wime implementaion does not flush stdin,stdout and stderr
+ - see above. Lets try to ignore the error. */
+#ifndef HAVE_W32_SYSTEM
+ return unlock_pinentry (tmperr);
+#endif
+ }
+
+ if (!opt.pinentry_program || !*opt.pinentry_program)
+ opt.pinentry_program = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
+ pgmname = opt.pinentry_program;
+ if ( !(pgmname = strrchr (opt.pinentry_program, '/')))
+ pgmname = opt.pinentry_program;
+ 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] = opt.pinentry_program;
+#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] = -1;
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ /* 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 (ctx, opt.pinentry_program, argv,
+ no_close_list, atfork_cb, ctrl,
+ ASSUAN_PIPE_CONNECT_DETACHED);
+ if (rc)
+ {
+ log_error ("can't connect to the PIN entry module: %s\n",
+ gpg_strerror (rc));
+ assuan_release (ctx);
+ return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY));
+ }
+ entry_ctx = ctx;
+
+ if (DBG_ASSUAN)
+ log_debug ("connection to PIN entry established\n");
+
+ rc = assuan_transact (entry_ctx,
+ opt.no_grab? "OPTION no-grab":"OPTION grab",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (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 (out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+ value = session_env_getenv (ctrl->session_env, "TERM");
+ if (value)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 )
+ return unlock_pinentry (out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+ if (ctrl->lc_ctype)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 )
+ return unlock_pinentry (out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+ if (ctrl->lc_messages)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 )
+ return unlock_pinentry (out_of_core ());
+ rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ xfree (optstr);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ {
+ /* Provide a few default strings for use by the pinentries. This
+ may help a pinentry to avoid implementing localization code. */
+ static struct { const char *key, *value; } 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. */
+ { "ok", N_("|pinentry-label|_OK") },
+ { "cancel", N_("|pinentry-label|_Cancel") },
+ { "prompt", N_("|pinentry-label|PIN:") },
+ { NULL, NULL}
+ };
+ char *optstr;
+ int idx;
+ const char *s, *s2;
+
+ for (idx=0; tbl[idx].key; idx++)
+ {
+ s = _(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 (out_of_core ());
+ assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ 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);
+ }
+ }
+
+
+ /* 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);
+ if (gpg_err_code (rc) == GPG_ERR_CANCELED)
+ return unlock_pinentry (gpg_error (GPG_ERR_CANCELED));
+ rc = 0;
+ }
+
+ return 0;
+}
+
+
+/* Returns True is 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)
+{
+ (void)ctrl;
+
+ if (waitseconds > 0)
+ {
+ pth_event_t evt;
+ int rc;
+
+ evt = pth_event (PTH_EVENT_TIME, pth_timeout (waitseconds, 0));
+ if (!pth_mutex_acquire (&entry_lock, 0, evt))
+ {
+ if (pth_event_occurred (evt))
+ rc = gpg_error (GPG_ERR_TIMEOUT);
+ else
+ rc = gpg_error (GPG_ERR_INTERNAL);
+ pth_event_free (evt, PTH_FREE_THIS);
+ return rc;
+ }
+ pth_event_free (evt, PTH_FREE_THIS);
+ }
+ else
+ {
+ if (!pth_mutex_acquire (&entry_lock, 1, NULL))
+ return gpg_error (GPG_ERR_LOCKED);
+ }
+
+ if (!pth_mutex_release (&entry_lock))
+ log_error ("failed to release the entry lock at %d\n", __LINE__);
+ 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;
+}
+
+
+/* Handle the QUALITY inquiry. */
+static gpg_error_t
+inq_quality (void *opaque, const char *line)
+{
+ assuan_context_t ctx = opaque;
+ char *pin;
+ int rc;
+ int percent;
+ char numbuf[20];
+
+ if (!strncmp (line, "QUALITY", 7) && (line[7] == ' ' || !line[7]))
+ {
+ line += 7;
+ while (*line == ' ')
+ line++;
+
+ pin = unescape_passphrase_string (line);
+ if (!pin)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ percent = estimate_passphrase_quality (pin);
+ if (check_passphrase_constraints (NULL, pin, 1))
+ percent = -percent;
+ snprintf (numbuf, sizeof numbuf, "%d", percent);
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ xfree (pin);
+ }
+ }
+ else
+ {
+ log_error ("unsupported inquiry `%s' from pinentry\n", line);
+ rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
+ }
+
+ return rc;
+}
+
+
+/* Helper for agent_askpin and agent_get_passphrase. */
+static int
+setup_qualitybar (void)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ char *tmpstr, *tmpstr2;
+ const char *tooltip;
+
+ /* TRANSLATORS: This string is displayed by Pinentry as the label
+ for the quality bar. */
+ tmpstr = try_percent_escape (_("Quality:"), "\t\r\n\f\v");
+ snprintf (line, DIM(line)-1, "SETQUALITYBAR %s", tmpstr? tmpstr:"");
+ line[DIM(line)-1] = 0;
+ 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 = _("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)-1, "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
+ line[DIM(line)-1] = 0;
+ 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;
+}
+
+
+
+/* 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. */
+int
+agent_askpin (ctrl_t ctrl,
+ const char *desc_text, const char *prompt_text,
+ const char *initial_errtext,
+ struct pin_entry_info_s *pininfo)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct entry_parm_s parm;
+ const char *errtext = NULL;
+ int is_pin = 0;
+ int saveflag;
+
+ if (opt.batch)
+ return 0; /* fixme: we should return BAD PIN */
+
+ if (!pininfo || pininfo->max_length < 1)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!desc_text && pininfo->min_digits)
+ desc_text = _("Please enter your PIN, so that the secret key "
+ "can be unlocked for this session");
+ else if (!desc_text)
+ desc_text = _("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;
+
+ snprintf (line, DIM(line)-1, "SETDESC %s", desc_text);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+
+ snprintf (line, DIM(line)-1, "SETPROMPT %s",
+ prompt_text? prompt_text : is_pin? "PIN:" : "Passphrase:");
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (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 ();
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ if (initial_errtext)
+ {
+ snprintf (line, DIM(line)-1, "SETERROR %s", initial_errtext);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ 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;
+
+ if (errtext)
+ {
+ /* TRANLATORS: 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)-1, _("SETERROR %s (try %d of %d)"),
+ errtext, pininfo->failed_tries+1, pininfo->max_tries);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+ errtext = NULL;
+ }
+
+ saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
+ assuan_begin_confidential (entry_ctx);
+ rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
+ inq_quality, entry_ctx, NULL, NULL);
+ assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag);
+ /* 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 (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
+ errtext = is_pin? _("PIN too long")
+ : _("Passphrase too long");
+ else if (rc)
+ return unlock_pinentry (rc);
+
+ if (!errtext && pininfo->min_digits)
+ {
+ /* do some basic checks on the entered PIN. */
+ if (!all_digitsp (pininfo->pin))
+ errtext = _("Invalid characters in PIN");
+ else if (pininfo->max_digits
+ && strlen (pininfo->pin) > pininfo->max_digits)
+ errtext = _("PIN too long");
+ else if (strlen (pininfo->pin) < pininfo->min_digits)
+ errtext = _("PIN too short");
+ }
+
+ if (!errtext && pininfo->check_cb)
+ {
+ /* More checks by utilizing the optional callback. */
+ pininfo->cb_errtext = NULL;
+ rc = pininfo->check_cb (pininfo);
+ if (rc == -1 && 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? _("Bad PIN")
+ : _("Bad Passphrase"));
+ else if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ if (!errtext)
+ return unlock_pinentry (0); /* okay, got a PIN or passphrase */
+ }
+
+ return unlock_pinentry (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. */
+int
+agent_get_passphrase (ctrl_t ctrl,
+ char **retpass, const char *desc, const char *prompt,
+ const char *errtext, int with_qualitybar)
+{
+
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct entry_parm_s parm;
+ int saveflag;
+
+ *retpass = NULL;
+ if (opt.batch)
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (!prompt)
+ prompt = desc && strstr (desc, "PIN")? "PIN": _("Passphrase");
+
+
+ if (desc)
+ snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+ else
+ snprintf (line, DIM(line)-1, "RESET");
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+
+ snprintf (line, DIM(line)-1, "SETPROMPT %s", prompt);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+
+ if (with_qualitybar && opt.min_passphrase_len)
+ {
+ rc = setup_qualitybar ();
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ if (errtext)
+ {
+ snprintf (line, DIM(line)-1, "SETERROR %s", errtext);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ 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 (out_of_core ());
+
+ saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
+ assuan_begin_confidential (entry_ctx);
+ rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
+ inq_quality, entry_ctx, NULL, NULL);
+ assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag);
+ /* 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)
+ xfree (parm.buffer);
+ else
+ *retpass = parm.buffer;
+ return unlock_pinentry (rc);
+}
+
+
+
+/* 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];
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (desc)
+ snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+ else
+ snprintf (line, DIM(line)-1, "RESET");
+ line[DIM(line)-1] = 0;
+ 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 (rc);
+
+ if (ok)
+ {
+ snprintf (line, DIM(line)-1, "SETOK %s", ok);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx,
+ line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (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)-1, "SETNOTOK %s", notok);
+ line[DIM(line)-1] = 0;
+ 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)-1, "SETCANCEL %s", notok);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+ if (rc)
+ return unlock_pinentry (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 (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 waut
+ 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];
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (desc)
+ snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+ else
+ snprintf (line, DIM(line)-1, "RESET");
+ line[DIM(line)-1] = 0;
+ 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 (rc);
+
+ if (ok_btn)
+ {
+ snprintf (line, DIM(line)-1, "SETOK %s", ok_btn);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (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 (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];
+ pth_attr_t tattr;
+
+ rc = start_pinentry (ctrl);
+ if (rc)
+ return rc;
+
+ if (desc)
+ snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+ else
+ snprintf (line, DIM(line)-1, "RESET");
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+
+ if (ok_btn)
+ {
+ snprintf (line, DIM(line)-1, "SETOK %s", ok_btn);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL);
+ if (rc)
+ return unlock_pinentry (rc);
+ }
+
+ tattr = pth_attr_new();
+ pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1);
+ pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+ pth_attr_set (tattr, PTH_ATTR_NAME, "popup-message");
+
+ popup_finished = 0;
+ popup_tid = pth_spawn (tattr, popup_message_thread, NULL);
+ if (!popup_tid)
+ {
+ rc = gpg_error_from_syserror ();
+ log_error ("error spawning popup message handler: %s\n",
+ strerror (errno) );
+ pth_attr_destroy (tattr);
+ return unlock_pinentry (rc);
+ }
+ pth_attr_destroy (tattr);
+
+ 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, SIGKILL); /* Need to use SIGKILL due to bad
+ interaction of SIGINT with Pth. */
+#endif
+
+ /* Now wait for the thread to terminate. */
+ rc = pth_join (popup_tid, NULL);
+ if (!rc)
+ log_debug ("agent_popup_message_stop: pth_join failed: %s\n",
+ strerror (errno));
+ popup_tid = NULL;
+ entry_owner = NULL;
+
+ /* Now we can close the connection. */
+ unlock_pinentry (0);
+}
+
+
diff --git a/agent/call-scd.c b/agent/call-scd.c
new file mode 100644
index 0000000..5a43377
--- /dev/null
+++ b/agent/call-scd.c
@@ -0,0 +1,1167 @@
+/* call-scd.c - fork of the scdaemon to do SC operations
+ * Copyright (C) 2001, 2002, 2005, 2007 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 <http://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 <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/wait.h>
+#endif
+#include <pth.h>
+
+#include "agent.h"
+#include <assuan.h>
+
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+/* This Assuan flag is only available since libassuan 2.0.2. Because
+ comments lines are comments anyway we can use a replacement which
+ might not do anything. assuan_{g,s}et_flag don't return an error
+ thus there won't be any ABI problem. */
+#ifndef ASSUAN_CONVEY_COMMENTS
+#define ASSUAN_CONVEY_COMMENTS 4
+#endif
+
+
+/* Definition of module local data of the CTRL structure. */
+struct scd_local_s
+{
+ /* We keep a list of all allocated context with a an achnor 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 enumerint the lcoal
+ contects. 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;
+};
+
+struct inq_needpin_s
+{
+ assuan_context_t ctx;
+ int (*getpin_cb)(void *, const char *, char*, size_t);
+ void *getpin_cb_arg;
+ assuan_context_t passthru; /* If not NULL, pass unknown inquiries
+ up to the caller. */
+};
+
+
+/* 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 pth_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. */
+static gpg_error_t membuf_data_cb (void *opaque,
+ const void *buffer, size_t length);
+
+
+
+
+/* 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_scd (void)
+{
+ static int initialized;
+
+ if (!initialized)
+ {
+ if (!pth_mutex_init (&start_scd_lock))
+ log_fatal ("error initializing mutex: %s\n", strerror (errno));
+ initialized = 1;
+ }
+}
+
+
+static void
+dump_mutex_state (pth_mutex_t *m)
+{
+#ifdef _W32_PTH_H
+ (void)m;
+ log_printf ("unknown under W32");
+#else
+ if (!(m->mx_state & PTH_MUTEX_INITIALIZED))
+ log_printf ("not_initialized");
+ else if (!(m->mx_state & PTH_MUTEX_LOCKED))
+ log_printf ("not_locked");
+ else
+ log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count);
+#endif
+}
+
+
+/* This function may be called to print infromation pertaining to the
+ current state of this module to the log. */
+void
+agent_scd_dump_state (void)
+{
+ log_info ("agent_scd_dump_state: scd_lock=");
+ dump_mutex_state (&start_scd_lock);
+ log_printf ("\n");
+ 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 (gpg_err_code (rc) == GPG_ERR_NOT_OPERATIONAL
+ && gpg_err_source (rc) == GPG_ERR_SOURCE_SCD)
+ {
+ /* If the SCdaemon returned this error, it detected a major
+ problem, like no reader connected. To finish this we need to
+ stop the connection. This simulates an explicit killing of
+ the SCdaemon. */
+ assuan_transact (primary_scd_ctx, "BYE",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ }
+
+ 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 fucntion 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[3];
+ int no_close_list[3];
+ int i;
+ int rc;
+
+ 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. */
+ if (!pth_mutex_acquire (&start_scd_lock, 0, NULL))
+ {
+ log_error ("failed to acquire the start_scd lock: %s\n",
+ strerror (errno));
+ 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 implementaion 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";
+ argv[2] = 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] = -1;
+
+ /* Connect to the pinentry and perform initial handshaking. Use
+ detached flag (128) so that under W32 SCDAEMON does not show up a
+ new window. */
+ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv,
+ no_close_list, atfork_cb, NULL, 128);
+ 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");
+
+ if (DBG_ASSUAN)
+ assuan_set_log_stream (ctx, log_get_stream ());
+
+ /* 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",
+ membuf_data_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_ASSUAN)
+ log_debug ("additional connections at `%s'\n", socket_name);
+ }
+ }
+ xfree (databuf);
+ }
+
+ /* Tell the scdaemon we want him to send us an event signal. */
+ if (opt.sigusr2_enabled)
+ {
+ char buf[100];
+
+#ifdef HAVE_W32_SYSTEM
+ snprintf (buf, sizeof buf, "OPTION event-signal=%lx",
+ (unsigned long)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);
+ }
+
+ primary_scd_ctx = ctx;
+ primary_scd_ctx_reusable = 0;
+
+ leave:
+ if (err)
+ {
+ unlock_scd (ctrl, err);
+ if (ctx)
+ assuan_release (ctx);
+ }
+ else
+ {
+ ctrl->scd_local->ctx = ctx;
+ }
+ if (!pth_mutex_release (&start_scd_lock))
+ log_error ("failed to release the start_scd lock: %s\n", strerror (errno));
+ 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)
+{
+ pth_event_t evt;
+ pid_t pid;
+#ifdef HAVE_W32_SYSTEM
+ DWORD rc;
+#else
+ int rc;
+#endif
+
+ if (!primary_scd_ctx)
+ return; /* No scdaemon running. */
+
+ /* This is not a critical function so we use a short timeout while
+ acquiring the lock. */
+ evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0));
+ if (!pth_mutex_acquire (&start_scd_lock, 0, evt))
+ {
+ if (pth_event_occurred (evt))
+ {
+ if (opt.verbose > 1)
+ log_info ("failed to acquire the start_scd lock while"
+ " doing an aliveness check: %s\n", "timeout");
+ }
+ else
+ log_error ("failed to acquire the start_scd lock while"
+ " doing an aliveness check: %s\n", strerror (errno));
+ pth_event_free (evt, PTH_FREE_THIS);
+ return;
+ }
+ pth_event_free (evt, PTH_FREE_THIS);
+
+ 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;
+ }
+ }
+
+ if (!pth_mutex_release (&start_scd_lock))
+ log_error ("failed to release the start_scd lock while"
+ " doing the aliveness check: %s\n", strerror (errno));
+}
+
+
+
+/* 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)
+{
+ int rc;
+ char *serialno = NULL;
+
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ rc = assuan_transact (ctrl->scd_local->ctx, "SERIALNO",
+ 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);
+}
+
+
+
+
+static gpg_error_t
+membuf_data_cb (void *opaque, const void *buffer, size_t length)
+{
+ membuf_t *data = opaque;
+
+ if (buffer)
+ put_membuf (data, buffer, length);
+ return 0;
+}
+
+/* Handle the NEEDPIN inquiry. */
+static gpg_error_t
+inq_needpin (void *opaque, const char *line)
+{
+ struct inq_needpin_s *parm = opaque;
+ char *pin;
+ size_t pinlen;
+ int rc;
+
+ if (!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7]))
+ {
+ line += 7;
+ while (*line == ' ')
+ line++;
+
+ pinlen = 90;
+ pin = gcry_malloc_secure (pinlen);
+ if (!pin)
+ return out_of_core ();
+
+ rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen);
+ if (!rc)
+ rc = assuan_send_data (parm->ctx, pin, pinlen);
+ xfree (pin);
+ }
+ else if (!strncmp (line, "POPUPKEYPADPROMPT", 17)
+ && (line[17] == ' ' || !line[17]))
+ {
+ line += 17;
+ while (*line == ' ')
+ line++;
+
+ rc = parm->getpin_cb (parm->getpin_cb_arg, line, NULL, 1);
+ }
+ else if (!strncmp (line, "DISMISSKEYPADPROMPT", 19)
+ && (line[19] == ' ' || !line[19]))
+ {
+ rc = parm->getpin_cb (parm->getpin_cb_arg, "", 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;
+}
+
+
+
+/* Create a signature using the current card */
+int
+agent_card_pksign (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ unsigned char **r_buf, size_t *r_buflen)
+{
+ int rc, i;
+ char *p, line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ struct inq_needpin_s inqparm;
+ size_t len;
+ unsigned char *sigbuf;
+ size_t sigbuflen;
+
+ *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));
+
+ sprintf (line, "SETDATA ");
+ p = line + strlen (line);
+ for (i=0; i < indatalen ; i++, p += 2 )
+ sprintf (p, "%02X", indata[i]);
+ 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.passthru = 0;
+ snprintf (line, DIM(line)-1,
+ ctrl->use_auth_call? "PKAUTH %s":"PKSIGN %s", keyid);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ membuf_data_cb, &data,
+ inq_needpin, &inqparm,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (ctrl, rc);
+ }
+ sigbuf = get_membuf (&data, &sigbuflen);
+
+ /* Create an S-expression from it which is formatted like this:
+ "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" */
+ *r_buflen = 21 + 11 + sigbuflen + 4;
+ p = xtrymalloc (*r_buflen);
+ *r_buf = (unsigned char*)p;
+ if (!p)
+ return unlock_scd (ctrl, out_of_core ());
+ p = stpcpy (p, "(7:sig-val(3:rsa(1:s" );
+ sprintf (p, "%u:", (unsigned int)sigbuflen);
+ p += strlen (p);
+ memcpy (p, sigbuf, sigbuflen);
+ p += sigbuflen;
+ strcpy (p, ")))");
+ xfree (sigbuf);
+
+ assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL));
+ return unlock_scd (ctrl, 0);
+}
+
+/* Decipher INDATA using the current card. Note that the returned value is */
+int
+agent_card_pkdecrypt (ctrl_t ctrl,
+ const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen)
+{
+ int rc, i;
+ char *p, line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ struct inq_needpin_s inqparm;
+ size_t len;
+
+ *r_buf = NULL;
+ rc = start_scd (ctrl);
+ if (rc)
+ return rc;
+
+ /* FIXME: use secure memory where appropriate */
+ if (indatalen*2 + 50 > DIM(line))
+ return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL));
+
+ sprintf (line, "SETDATA ");
+ p = line + strlen (line);
+ for (i=0; i < indatalen ; i++, p += 2 )
+ sprintf (p, "%02X", indata[i]);
+ 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.passthru = 0;
+ snprintf (line, DIM(line)-1, "PKDECRYPT %s", keyid);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ membuf_data_cb, &data,
+ inq_needpin, &inqparm,
+ 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 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)-1, "READCERT %s", id);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ membuf_data_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)-1, "READKEY %s", id);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (ctrl->scd_local->ctx, line,
+ membuf_data_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);
+}
+
+
+
+/* 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);
+}
+
+
+
+
+static gpg_error_t
+pass_status_thru (void *opaque, const char *line)
+{
+ assuan_context_t ctx = opaque;
+ char keyword[200];
+ int i;
+
+ 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 *, char*, size_t),
+ void *getpin_cb_arg, void *assuan_context)
+{
+ int rc;
+ struct inq_needpin_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.passthru = assuan_context;
+ 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);
+}
+
+
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
new file mode 100644
index 0000000..2f96ef5
--- /dev/null
+++ b/agent/command-ssh.c
@@ -0,0 +1,3089 @@
+/* command-ssh.c - gpg-agent's ssh-agent emulation layer
+ * Copyright (C) 2004, 2005, 2006, 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* Only v2 of the ssh-agent protocol is implemented. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <assert.h>
+
+#include "agent.h"
+
+#include "estream.h"
+#include "i18n.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 SPEC_FLAG_USE_PKCS1V2 (1 << 0)
+
+
+/* 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"
+"# the 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);
+
+/* 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) (estream_t signature_blob,
+ gcry_mpi_t *mpis);
+
+/* Type, which is used for boundling all the algorithm specific
+ information together in a single object. */
+typedef struct ssh_key_type_spec
+{
+ /* Algorithm identifier as used by OpenSSH. */
+ const char *ssh_identifier;
+
+ /* Algorithm identifier as used by GnuPG. */
+ const char *identifier;
+
+ /* 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;
+
+ /* Misc flags. */
+ unsigned int flags;
+} ssh_key_type_spec_t;
+
+
+/* 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 (estream_t signature_blob,
+ gcry_mpi_t *mpis);
+static gpg_error_t ssh_signature_encoder_dsa (estream_t signature_blob,
+ gcry_mpi_t *mpis);
+
+
+
+/* Global variables. */
+
+
+/* Associating request types with the corresponding request
+ handlers. */
+
+static 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 ssh_key_type_spec_t ssh_key_types[] =
+ {
+ {
+ "ssh-rsa", "rsa", "nedupq", "en", "s", "nedpqu",
+ ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
+ SPEC_FLAG_USE_PKCS1V2
+ },
+ {
+ "ssh-dss", "dsa", "pqgyx", "pqgy", "rs", "pqgyx",
+ NULL, ssh_signature_encoder_dsa,
+ 0
+ },
+ };
+
+
+
+
+
+/*
+ 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;
+}
+
+
+/* Create and return a new C-string from DATA/DATA_N (i.e.: add
+ NUL-termination); return NULL on OOM. */
+static char *
+make_cstring (const char *data, size_t data_n)
+{
+ char *s;
+
+ s = xtrymalloc (data_n + 1);
+ if (s)
+ {
+ memcpy (s, data, data_n);
+ s[data_n] = 0;
+ }
+
+ return s;
+}
+
+
+
+
+/*
+ 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;
+}
+
+/* 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; depending on SECURE use secure memory for
+ string. */
+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;
+
+ /* Read string length. */
+ err = stream_read_uint32 (stream, &length);
+ if (err)
+ goto out;
+
+ /* 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 = stream_read_data (stream, buffer, length);
+ if (err)
+ goto out;
+
+ /* Finalize string object. */
+ buffer[length] = 0;
+ *string = buffer;
+ if (string_size)
+ *string_size = length;
+
+ out:
+
+ if (err)
+ 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)
+{
+ unsigned char *buffer;
+ gpg_error_t err;
+
+ err = stream_read_string (stream, 0, &buffer, NULL);
+ if (err)
+ goto out;
+
+ *string = (char *) buffer;
+
+ out:
+
+ return err;
+}
+
+
+/* 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;
+}
+
+
+/* Read the content of the file specified by FILENAME into a newly
+ create buffer, which is to be stored in BUFFER; store length of
+ buffer in BUFFER_N. */
+static gpg_error_t
+file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n)
+{
+ unsigned char *buffer_new;
+ struct stat statbuf;
+ estream_t stream;
+ gpg_error_t err;
+ int ret;
+
+ *buffer = NULL;
+ *buffer_n = 0;
+
+ buffer_new = NULL;
+ err = 0;
+
+ stream = es_fopen (filename, "r");
+ if (! stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ ret = fstat (es_fileno (stream), &statbuf);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ buffer_new = xtrymalloc (statbuf.st_size);
+ if (! buffer_new)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_read_data (stream, buffer_new, statbuf.st_size);
+ if (err)
+ goto out;
+
+ *buffer = buffer_new;
+ *buffer_n = statbuf.st_size;
+
+ out:
+
+ if (stream)
+ es_fclose (stream);
+
+ if (err)
+ xfree (buffer_new);
+
+ 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 a file pointer is stored
+ at the address of R_FP. */
+static gpg_error_t
+open_control_file (FILE **r_fp, int append)
+{
+ gpg_error_t err;
+ char *fname;
+ FILE *fp;
+
+ /* 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. */
+ *r_fp = NULL;
+ fname = make_filename (opt.homedir, "sshcontrol", NULL);
+ /* FIXME: With "a+" we are not able to check whether this will will
+ be created and thus the blurb needs to be written first. */
+ fp = fopen (fname, append? "a+":"r");
+ if (!fp && errno == ENOENT)
+ {
+ /* Fixme: "x" is a GNU extension. We might want to use the es_
+ functions here. */
+ fp = fopen (fname, "wx");
+ if (!fp)
+ {
+ err = gpg_error (gpg_err_code_from_errno (errno));
+ log_error (_("can't create `%s': %s\n"), fname, gpg_strerror (err));
+ xfree (fname);
+ return err;
+ }
+ fputs (sshcontrolblurb, fp);
+ fclose (fp);
+ fp = fopen (fname, append? "a+":"r");
+ }
+
+ if (!fp)
+ {
+ err = gpg_error (gpg_err_code_from_errno (errno));
+ log_error (_("can't open `%s': %s\n"), fname, gpg_strerror (err));
+ xfree (fname);
+ return err;
+ }
+
+ *r_fp = fp;
+
+ return 0;
+}
+
+
+/* Search the file at stream FP 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 (FILE *fp, const char *hexgrip,
+ int *r_disabled, int *r_ttl, int *r_confirm)
+{
+ int c, i, n;
+ char *p, *pend, line[256];
+ long ttl;
+ int lnr = 0;
+ const char fname[] = "sshcontrol";
+
+ assert (strlen (hexgrip) == 40 );
+
+ if (r_confirm)
+ *r_confirm = 0;
+
+ fseek (fp, 0, SEEK_SET);
+ clearerr (fp);
+ *r_disabled = 0;
+ next_line:
+ do
+ {
+ if (!fgets (line, DIM(line)-1, fp) )
+ {
+ if (feof (fp))
+ return gpg_error (GPG_ERR_EOF);
+ return gpg_error (gpg_err_code_from_errno (errno));
+ }
+ lnr++;
+
+ if (!*line || line[strlen(line)-1] != '\n')
+ {
+ /* Eat until end of line */
+ while ( (c=getc (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 == '#');
+
+ *r_disabled = 0;
+ if (*p == '!')
+ {
+ *r_disabled = 1;
+ for (p++; spacep (p); p++)
+ ;
+ }
+
+ for (i=0; hexdigitp (p) && i < 40; p++, i++)
+ if (hexgrip[i] != (*p >= 'a'? (*p & 0xdf): *p))
+ goto next_line;
+ if (i != 40 || !(spacep (p) || *p == '\n'))
+ {
+ log_error ("invalid formatted line in `%s', line %d\n", fname, lnr);
+ return gpg_error (GPG_ERR_BAD_DATA);
+ }
+
+ ttl = strtol (p, &pend, 10);
+ p = pend;
+ if (!(spacep (p) || *p == '\n') || ttl < -1)
+ {
+ log_error ("invalid TTL value in `%s', line %d; assuming 0\n",
+ fname, lnr);
+ ttl = 0;
+ }
+ if (r_ttl)
+ *r_ttl = ttl;
+
+ /* Now check for key-value pairs of the form NAME[=VALUE]. */
+ while (*p)
+ {
+ for (; spacep (p) && *p != '\n'; p++)
+ ;
+ if (!*p || *p == '\n')
+ break;
+ n = strcspn (p, "= \t\n");
+ if (p[n] == '=')
+ {
+ log_error ("assigning a value to a flag is not yet supported; "
+ "in `%s', line %d; flag ignored\n", fname, lnr);
+ p++;
+ }
+ else if (n == 7 && !memcmp (p, "confirm", 7))
+ {
+ if (r_confirm)
+ *r_confirm = 1;
+ }
+ else
+ log_error ("invalid flag `%.*s' in `%s', line %d; ignored\n",
+ n, p, fname, lnr);
+ p += n;
+ }
+
+ return 0; /* Okay: found it. */
+}
+
+
+
+/* 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, const char *hexgrip, const char *fmtfpr,
+ int ttl, int confirm)
+{
+ gpg_error_t err;
+ FILE *fp;
+ int disabled;
+
+ (void)ctrl;
+
+ err = open_control_file (&fp, 1);
+ if (err)
+ return err;
+
+ err = search_control_file (fp, hexgrip, &disabled, NULL, NULL);
+ if (err && gpg_err_code(err) == GPG_ERR_EOF)
+ {
+ struct tm *tp;
+ time_t atime = time (NULL);
+
+ /* 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);
+ fprintf (fp, ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
+ "# Fingerprint: %s\n"
+ "%s %d%s\n"),
+ 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
+ tp->tm_hour, tp->tm_min, tp->tm_sec,
+ fmtfpr, hexgrip, ttl, confirm? " confirm":"");
+
+ }
+ fclose (fp);
+ return 0;
+}
+
+
+/* Scan the sshcontrol file and return the TTL. */
+static int
+ttl_from_sshcontrol (const char *hexgrip)
+{
+ FILE *fp;
+ int disabled, ttl;
+
+ if (!hexgrip || strlen (hexgrip) != 40)
+ return 0; /* Wrong input: Use global default. */
+
+ if (open_control_file (&fp, 0))
+ return 0; /* Error: Use the global default TTL. */
+
+ if (search_control_file (fp, hexgrip, &disabled, &ttl, NULL)
+ || disabled)
+ ttl = 0; /* Use the global default if not found or disabled. */
+
+ fclose (fp);
+
+ return ttl;
+}
+
+
+/* Scan the sshcontrol file and return the confirm flag. */
+static int
+confirm_flag_from_sshcontrol (const char *hexgrip)
+{
+ FILE *fp;
+ int disabled, confirm;
+
+ if (!hexgrip || strlen (hexgrip) != 40)
+ return 1; /* Wrong input: Better ask for confirmation. */
+
+ if (open_control_file (&fp, 0))
+ return 1; /* Error: Better ask for confirmation. */
+
+ if (search_control_file (fp, hexgrip, &disabled, NULL, &confirm)
+ || disabled)
+ confirm = 0; /* If not found or disabled, there is no reason to
+ ask for confirmation. */
+
+ fclose (fp);
+
+ return confirm;
+}
+
+
+
+
+
+/*
+
+ 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. 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 key_spec, gcry_mpi_t **mpi_list)
+{
+ const char *elems_public;
+ unsigned int elems_n;
+ const char *elems;
+ int elem_is_secret;
+ gcry_mpi_t *mpis;
+ gpg_error_t err;
+ unsigned int i;
+
+ mpis = NULL;
+ err = 0;
+
+ if (secret)
+ elems = key_spec.elems_key_secret;
+ else
+ elems = key_spec.elems_key_public;
+ elems_n = strlen (elems);
+
+ elems_public = key_spec.elems_key_public;
+
+ 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]);
+ err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
+ if (err)
+ break;
+ }
+ if (err)
+ goto out;
+
+ *mpi_list = mpis;
+
+ 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 (estream_t signature_blob, gcry_mpi_t *mpis)
+{
+ unsigned char *data;
+ size_t data_n;
+ gpg_error_t err;
+ gcry_mpi_t s;
+
+ 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:
+
+ return err;
+}
+
+
+/* Signature encoder function for DSA. */
+static gpg_error_t
+ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis)
+{
+ unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
+ unsigned char *data;
+ size_t data_n;
+ gpg_error_t err;
+ int i;
+
+ data = NULL;
+
+ 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);
+
+ return err;
+}
+
+/*
+ S-Expressions.
+ */
+
+
+/* This function constructs a new S-Expression for the key identified
+ by the KEY_SPEC, SECRET, MPIS and COMMENT, which is to be stored in
+ *SEXP. Returns usual error code. */
+static gpg_error_t
+sexp_key_construct (gcry_sexp_t *sexp,
+ ssh_key_type_spec_t key_spec, int secret,
+ gcry_mpi_t *mpis, const char *comment)
+{
+ const char *key_identifier[] = { "public-key", "private-key" };
+ gcry_sexp_t sexp_new;
+ char *sexp_template;
+ size_t sexp_template_n;
+ gpg_error_t err;
+ const char *elems;
+ size_t elems_n;
+ unsigned int i;
+ unsigned int j;
+ void **arg_list;
+
+ err = 0;
+ sexp_new = NULL;
+ arg_list = NULL;
+ if (secret)
+ elems = key_spec.elems_sexp_order;
+ else
+ elems = key_spec.elems_key_public;
+ elems_n = strlen (elems);
+
+ /*
+ Calculate size for sexp_template_n:
+
+ "(%s(%s<mpis>)(comment%s))" -> 20 + sizeof (<mpis>).
+
+ mpi: (X%m) -> 5.
+
+ */
+ sexp_template_n = 20 + (elems_n * 5);
+ sexp_template = xtrymalloc (sexp_template_n);
+ if (! sexp_template)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* Key identifier, algorithm identifier, mpis, comment. */
+ arg_list = xtrymalloc (sizeof (*arg_list) * (2 + elems_n + 1));
+ if (! arg_list)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ i = 0;
+ arg_list[i++] = &key_identifier[secret];
+ arg_list[i++] = &key_spec.identifier;
+
+ *sexp_template = 0;
+ sexp_template_n = 0;
+ sexp_template_n = sprintf (sexp_template + sexp_template_n, "(%%s(%%s");
+ for (i = 0; i < elems_n; i++)
+ {
+ sexp_template_n += sprintf (sexp_template + sexp_template_n, "(%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[i + 2] = &mpis[j];
+ }
+ sexp_template_n += sprintf (sexp_template + sexp_template_n,
+ ")(comment%%s))");
+
+ arg_list[i + 2] = &comment;
+
+ err = gcry_sexp_build_array (&sexp_new, NULL, sexp_template, arg_list);
+ if (err)
+ goto out;
+
+ *sexp = sexp_new;
+
+ out:
+
+ xfree (arg_list);
+ xfree (sexp_template);
+
+ return err;
+}
+
+/* This functions breaks up the key contained in the S-Expression SEXP
+ according to KEY_SPEC. The MPIs are bundled in a newly create
+ list, which is to be stored in MPIS; a newly allocated string
+ holding the comment will be stored in COMMENT; SECRET will be
+ filled with a boolean flag specifying what kind of key it is.
+ Returns usual error code. */
+static gpg_error_t
+sexp_key_extract (gcry_sexp_t sexp,
+ ssh_key_type_spec_t key_spec, int *secret,
+ gcry_mpi_t **mpis, char **comment)
+{
+ gpg_error_t err;
+ gcry_sexp_t value_list;
+ gcry_sexp_t value_pair;
+ gcry_sexp_t comment_list;
+ unsigned int i;
+ char *comment_new;
+ const char *data;
+ size_t data_n;
+ int is_secret;
+ size_t elems_n;
+ const char *elems;
+ gcry_mpi_t *mpis_new;
+ gcry_mpi_t mpi;
+
+ err = 0;
+ value_list = NULL;
+ value_pair = NULL;
+ comment_list = NULL;
+ comment_new = NULL;
+ mpis_new = NULL;
+
+ data = gcry_sexp_nth_data (sexp, 0, &data_n);
+ if (! data)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ if ((data_n == 10 && !strncmp (data, "public-key", 10))
+ || (data_n == 21 && !strncmp (data, "protected-private-key", 21))
+ || (data_n == 20 && !strncmp (data, "shadowed-private-key", 20)))
+ {
+ is_secret = 0;
+ elems = key_spec.elems_key_public;
+ }
+ else if (data_n == 11 && !strncmp (data, "private-key", 11))
+ {
+ is_secret = 1;
+ elems = key_spec.elems_key_secret;
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ elems_n = strlen (elems);
+ mpis_new = xtrycalloc (elems_n + 1, sizeof *mpis_new );
+ if (!mpis_new)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ value_list = gcry_sexp_find_token (sexp, key_spec.identifier, 0);
+ if (! value_list)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ for (i = 0; i < elems_n; i++)
+ {
+ value_pair = gcry_sexp_find_token (value_list, elems + i, 1);
+ if (! value_pair)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ break;
+ }
+
+ /* 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);
+ break;
+ }
+ mpis_new[i] = mpi;
+ gcry_sexp_release (value_pair);
+ value_pair = NULL;
+ }
+ if (err)
+ goto out;
+
+ /* We do not require a comment sublist to be present here. */
+ data = NULL;
+ data_n = 0;
+
+ comment_list = gcry_sexp_find_token (sexp, "comment", 0);
+ if (comment_list)
+ data = gcry_sexp_nth_data (comment_list, 1, &data_n);
+ if (! data)
+ {
+ data = "(none)";
+ data_n = 6;
+ }
+
+ comment_new = make_cstring (data, data_n);
+ if (! comment_new)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ if (secret)
+ *secret = is_secret;
+ *mpis = mpis_new;
+ *comment = comment_new;
+
+ out:
+
+ gcry_sexp_release (value_list);
+ gcry_sexp_release (value_pair);
+ gcry_sexp_release (comment_list);
+
+ if (err)
+ {
+ xfree (comment_new);
+ mpint_list_free (mpis_new);
+ }
+
+ return err;
+}
+
+/* Extract the car from SEXP, and create a newly created C-string
+ which is to be stored in IDENTIFIER. */
+static gpg_error_t
+sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
+{
+ char *identifier_new;
+ gcry_sexp_t sublist;
+ const char *data;
+ size_t data_n;
+ gpg_error_t err;
+
+ identifier_new = NULL;
+ err = 0;
+
+ sublist = gcry_sexp_nth (sexp, 1);
+ if (! sublist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ data = gcry_sexp_nth_data (sublist, 0, &data_n);
+ if (! data)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ identifier_new = make_cstring (data, data_n);
+ if (! identifier_new)
+ {
+ err = gpg_err_code_from_errno (errno);
+ goto out;
+ }
+
+ *identifier = identifier_new;
+
+ out:
+
+ gcry_sexp_release (sublist);
+
+ 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 "name" is equal to NAME.
+ Store found entry in SPEC on success, return error otherwise. */
+static gpg_error_t
+ssh_key_type_lookup (const char *ssh_name, const char *name,
+ 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)))
+ || (name && (! strcmp (name, ssh_key_types[i].identifier))))
+ 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;
+ gcry_sexp_t key = NULL;
+ ssh_key_type_spec_t spec;
+ gcry_mpi_t *mpi_list = NULL;
+ const char *elems;
+
+
+ err = stream_read_cstring (stream, &key_type);
+ if (err)
+ goto out;
+
+ err = ssh_key_type_lookup (key_type, NULL, &spec);
+ if (err)
+ goto out;
+
+ err = ssh_receive_mpint_list (stream, secret, spec, &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;
+ }
+
+ err = sexp_key_construct (&key, spec, secret, mpi_list, comment? comment:"");
+ if (err)
+ goto out;
+
+ if (key_spec)
+ *key_spec = spec;
+ *key_new = key;
+
+ out:
+
+ mpint_list_free (mpi_list);
+ xfree (key_type);
+ xfree (comment);
+
+ return err;
+}
+
+/* Converts a key of type TYPE, whose key material is given in MPIS,
+ into a newly created binary blob, which is to be stored in
+ BLOB/BLOB_SIZE. Returns zero on success or an error code. */
+static gpg_error_t
+ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size,
+ const char *type, gcry_mpi_t *mpis)
+{
+ unsigned char *blob_new;
+ long int blob_size_new;
+ estream_t stream;
+ gpg_error_t err;
+ unsigned int i;
+
+ *blob = NULL;
+ *blob_size = 0;
+
+ blob_new = NULL;
+ stream = NULL;
+ err = 0;
+
+ stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+ if (! stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_write_cstring (stream, type);
+ if (err)
+ goto out;
+
+ for (i = 0; mpis[i] && (! err); i++)
+ err = stream_write_mpi (stream, mpis[i]);
+ if (err)
+ goto out;
+
+ blob_size_new = es_ftell (stream);
+ if (blob_size_new == -1)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = es_fseek (stream, 0, SEEK_SET);
+ if (err)
+ goto out;
+
+ blob_new = xtrymalloc (blob_size_new);
+ if (! blob_new)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_read_data (stream, blob_new, blob_size_new);
+ if (err)
+ goto out;
+
+ *blob = blob_new;
+ *blob_size = blob_size_new;
+
+ out:
+
+ if (stream)
+ es_fclose (stream);
+ if (err)
+ xfree (blob_new);
+
+ return err;
+}
+
+
+/* Write the public key KEY_PUBLIC 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_public,
+ const char *override_comment)
+{
+ ssh_key_type_spec_t spec;
+ gcry_mpi_t *mpi_list;
+ char *key_type;
+ char *comment;
+ unsigned char *blob;
+ size_t blob_n;
+ gpg_error_t err;
+
+ key_type = NULL;
+ mpi_list = NULL;
+ comment = NULL;
+ blob = NULL;
+
+ err = sexp_extract_identifier (key_public, &key_type);
+ if (err)
+ goto out;
+
+ err = ssh_key_type_lookup (NULL, key_type, &spec);
+ if (err)
+ goto out;
+
+ err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &comment);
+ if (err)
+ goto out;
+
+ err = ssh_convert_key_to_blob (&blob, &blob_n,
+ spec.ssh_identifier, mpi_list);
+ if (err)
+ goto out;
+
+ err = stream_write_string (stream, blob, blob_n);
+ if (err)
+ goto out;
+
+ err = stream_write_cstring (stream,
+ override_comment? override_comment : comment);
+
+ out:
+
+ mpint_list_free (mpi_list);
+ xfree (key_type);
+ xfree (comment);
+ xfree (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)
+{
+ estream_t blob_stream;
+ gpg_error_t err;
+
+ err = 0;
+
+ blob_stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+ 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:
+
+ if (blob_stream)
+ 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))
+ return gpg_error (GPG_ERR_INTERNAL);
+
+ return 0;
+}
+
+
+/* Converts the secret key KEY_SECRET into a public key, storing it in
+ KEY_PUBLIC. SPEC is the according key specification. Returns zero
+ on success or an error code. */
+static gpg_error_t
+key_secret_to_public (gcry_sexp_t *key_public,
+ ssh_key_type_spec_t spec, gcry_sexp_t key_secret)
+{
+ char *comment;
+ gcry_mpi_t *mpis;
+ gpg_error_t err;
+ int is_secret;
+
+ comment = NULL;
+ mpis = NULL;
+
+ err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, &comment);
+ if (err)
+ goto out;
+
+ err = sexp_key_construct (key_public, spec, 0, mpis, comment);
+
+ out:
+
+ mpint_list_free (mpis);
+ xfree (comment);
+
+ 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);
+ 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 (_("error getting default authentication keyID of 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) )
+ {
+ /* (Shadow)-key is not available in our key storage. */
+ unsigned char *shadow_info;
+ unsigned char *tmp;
+
+ shadow_info = make_shadow_info (serialno, authkeyid);
+ if (!shadow_info)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (pkbuf);
+ gcry_sexp_release (s_pk);
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+ err = agent_shadow_key (pkbuf, shadow_info, &tmp);
+ xfree (shadow_info);
+ if (err)
+ {
+ log_error (_("shadowing the key failed: %s\n"), gpg_strerror (err));
+ xfree (pkbuf);
+ gcry_sexp_release (s_pk);
+ xfree (serialno);
+ xfree (authkeyid);
+ return err;
+ }
+ xfree (pkbuf);
+ pkbuf = tmp;
+ pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
+ assert (pkbuflen);
+
+ err = agent_write_private_key (grip, pkbuf, pkbuflen, 0);
+ if (err)
+ {
+ log_error (_("error writing key: %s\n"), gpg_strerror (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)
+{
+ char *key_type;
+ ssh_key_type_spec_t spec;
+ struct dirent *dir_entry;
+ char *key_directory;
+ size_t key_directory_n;
+ char *key_path;
+ unsigned char *buffer;
+ size_t buffer_n;
+ u32 key_counter;
+ estream_t key_blobs;
+ gcry_sexp_t key_secret;
+ gcry_sexp_t key_public;
+ DIR *dir;
+ gpg_error_t err;
+ int ret;
+ FILE *ctrl_fp = NULL;
+ char *cardsn;
+ gpg_error_t ret_err;
+
+ (void)request;
+
+ /* Prepare buffer stream. */
+
+ key_directory = NULL;
+ key_secret = NULL;
+ key_public = NULL;
+ key_type = NULL;
+ key_path = NULL;
+ key_counter = 0;
+ buffer = NULL;
+ dir = NULL;
+ err = 0;
+
+ key_blobs = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+ if (! key_blobs)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ /* Open key directory. */
+ key_directory = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
+ if (! key_directory)
+ {
+ err = gpg_err_code_from_errno (errno);
+ goto out;
+ }
+ key_directory_n = strlen (key_directory);
+
+ key_path = xtrymalloc (key_directory_n + 46);
+ if (! key_path)
+ {
+ err = gpg_err_code_from_errno (errno);
+ goto out;
+ }
+
+ sprintf (key_path, "%s/", key_directory);
+ sprintf (key_path + key_directory_n + 41, ".key");
+
+ dir = opendir (key_directory);
+ if (! dir)
+ {
+ err = gpg_err_code_from_errno (errno);
+ 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 (!card_key_available (ctrl, &key_public, &cardsn))
+ {
+ err = ssh_send_key_public (key_blobs, key_public, cardsn);
+ gcry_sexp_release (key_public);
+ key_public = NULL;
+ xfree (cardsn);
+ if (err)
+ goto out;
+
+ key_counter++;
+ }
+
+
+ /* Then look at all the registered an allowed keys. */
+
+
+ /* Fixme: We should better iterate over the control file and check
+ whether the key file is there. This is better in resepct to
+ performance if tehre are a lot of key sin our key storage. */
+ /* FIXME: make sure that buffer gets deallocated properly. */
+ err = open_control_file (&ctrl_fp, 0);
+ if (err)
+ goto out;
+
+ while ( (dir_entry = readdir (dir)) )
+ {
+ if ((strlen (dir_entry->d_name) == 44)
+ && (! strncmp (dir_entry->d_name + 40, ".key", 4)))
+ {
+ char hexgrip[41];
+ int disabled;
+
+ /* We do only want to return keys listed in our control
+ file. */
+ strncpy (hexgrip, dir_entry->d_name, 40);
+ hexgrip[40] = 0;
+ if ( strlen (hexgrip) != 40 )
+ continue;
+ if (search_control_file (ctrl_fp, hexgrip, &disabled, NULL, NULL)
+ || disabled)
+ continue;
+
+ strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40);
+
+ /* Read file content. */
+ err = file_to_buffer (key_path, &buffer, &buffer_n);
+ if (err)
+ goto out;
+
+ err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n);
+ if (err)
+ goto out;
+
+ xfree (buffer);
+ buffer = NULL;
+
+ err = sexp_extract_identifier (key_secret, &key_type);
+ if (err)
+ goto out;
+
+ err = ssh_key_type_lookup (NULL, key_type, &spec);
+ if (err)
+ goto out;
+
+ xfree (key_type);
+ key_type = NULL;
+
+ err = key_secret_to_public (&key_public, spec, key_secret);
+ if (err)
+ goto out;
+
+ gcry_sexp_release (key_secret);
+ key_secret = NULL;
+
+ err = ssh_send_key_public (key_blobs, key_public, NULL);
+ if (err)
+ goto out;
+
+ gcry_sexp_release (key_public);
+ key_public = NULL;
+
+ key_counter++;
+ }
+ }
+
+ ret = es_fseek (key_blobs, 0, SEEK_SET);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ out:
+
+ /* Send response. */
+
+ gcry_sexp_release (key_secret);
+ gcry_sexp_release (key_public);
+
+ if (! err)
+ {
+ ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
+ if (ret_err)
+ goto leave;
+ ret_err = stream_write_uint32 (response, key_counter);
+ if (ret_err)
+ goto leave;
+ ret_err = stream_copy (response, key_blobs);
+ if (ret_err)
+ goto leave;
+ }
+ else
+ {
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+ goto leave;
+ };
+
+ leave:
+
+ if (key_blobs)
+ es_fclose (key_blobs);
+ if (dir)
+ closedir (dir);
+
+ if (ctrl_fp)
+ fclose (ctrl_fp);
+
+ xfree (key_directory);
+ xfree (key_path);
+ xfree (buffer);
+ xfree (key_type);
+
+ 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 contained in CTRL, stores the created
+ signature in newly allocated memory in SIG and it's size in SIG_N;
+ SIG_ENCODER is the signature encoder to use. */
+static gpg_error_t
+data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder,
+ unsigned char **sig, size_t *sig_n)
+{
+ gpg_error_t err;
+ gcry_sexp_t signature_sexp = NULL;
+ estream_t stream = NULL;
+ gcry_sexp_t valuelist = NULL;
+ gcry_sexp_t sublist = NULL;
+ gcry_mpi_t sig_value = NULL;
+ unsigned char *sig_blob = NULL;
+ size_t sig_blob_n = 0;
+ char *identifier = NULL;
+ const char *identifier_raw;
+ size_t identifier_n;
+ ssh_key_type_spec_t spec;
+ int ret;
+ unsigned int i;
+ const char *elems;
+ size_t elems_n;
+ gcry_mpi_t *mpis = NULL;
+ char hexgrip[40+1];
+
+ *sig = NULL;
+ *sig_n = 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, &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 (_("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, _("Allow"), _("Deny"), 0);
+ xfree (prompt);
+ if (err)
+ goto out;
+ }
+
+ /* Create signature. */
+ ctrl->use_auth_call = 1;
+ err = agent_pksign_do (ctrl,
+ _("Please enter the passphrase "
+ "for the ssh key%%0A %F%%0A (%c)"),
+ &signature_sexp,
+ CACHE_MODE_SSH, ttl_from_sshcontrol);
+ ctrl->use_auth_call = 0;
+ if (err)
+ goto out;
+
+ valuelist = gcry_sexp_nth (signature_sexp, 1);
+ if (! valuelist)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+ if (! stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ identifier_raw = gcry_sexp_nth_data (valuelist, 0, &identifier_n);
+ if (! identifier_raw)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ identifier = make_cstring (identifier_raw, identifier_n);
+ if (! identifier)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = ssh_key_type_lookup (NULL, identifier, &spec);
+ if (err)
+ goto out;
+
+ err = stream_write_cstring (stream, spec.ssh_identifier);
+ if (err)
+ 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;
+
+ err = (*sig_encoder) (stream, mpis);
+ if (err)
+ goto out;
+
+ sig_blob_n = es_ftell (stream);
+ if (sig_blob_n == -1)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ sig_blob = xtrymalloc (sig_blob_n);
+ if (! sig_blob)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ ret = es_fseek (stream, 0, SEEK_SET);
+ if (ret)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ err = stream_read_data (stream, sig_blob, sig_blob_n);
+ if (err)
+ goto out;
+
+ *sig = sig_blob;
+ *sig_n = sig_blob_n;
+
+ out:
+
+ if (err)
+ xfree (sig_blob);
+
+ if (stream)
+ es_fclose (stream);
+ gcry_sexp_release (valuelist);
+ gcry_sexp_release (signature_sexp);
+ gcry_sexp_release (sublist);
+ mpint_list_free (mpis);
+ xfree (identifier);
+
+ 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;
+ 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;
+ u32 key_blob_size;
+ unsigned char *data;
+ unsigned char *sig;
+ size_t sig_n;
+ u32 data_size;
+ u32 flags;
+ gpg_error_t err;
+ gpg_error_t ret_err;
+
+ key_blob = NULL;
+ data = NULL;
+ sig = NULL;
+ key = NULL;
+
+ /* 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;
+
+ /* FIXME? */
+ err = stream_read_uint32 (request, &flags);
+ if (err)
+ goto out;
+
+ /* Hash data. */
+ hash_n = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
+ if (! hash_n)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL);
+ goto out;
+ }
+ err = data_hash (data, data_size, GCRY_MD_SHA1, hash);
+ if (err)
+ goto out;
+
+ /* Calculate key grip. */
+ err = ssh_key_grip (key, key_grip);
+ if (err)
+ goto out;
+
+ /* Sign data. */
+
+ ctrl->digest.algo = GCRY_MD_SHA1;
+ memcpy (ctrl->digest.value, hash, hash_n);
+ ctrl->digest.valuelen = hash_n;
+ ctrl->digest.raw_value = ! (spec.flags & SPEC_FLAG_USE_PKCS1V2);
+ ctrl->have_keygrip = 1;
+ memcpy (ctrl->keygrip, key_grip, 20);
+
+ err = data_sign (ctrl, spec.signature_encoder, &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
+ {
+ ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
+ if (ret_err)
+ goto leave;
+ }
+
+ leave:
+
+ gcry_sexp_release (key);
+ xfree (key_blob);
+ xfree (data);
+ xfree (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 **comment)
+{
+ gcry_sexp_t comment_list;
+ char *comment_new;
+ const char *data;
+ size_t data_n;
+ gpg_error_t err;
+
+ comment_list = gcry_sexp_find_token (key, "comment", 0);
+ if (! comment_list)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ data = gcry_sexp_nth_data (comment_list, 1, &data_n);
+ if (! data)
+ {
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto out;
+ }
+
+ comment_new = make_cstring (data, data_n);
+ if (! comment_new)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ *comment = comment_new;
+ err = 0;
+
+ out:
+
+ gcry_sexp_release (comment_list);
+
+ return err;
+}
+
+/* This function converts the key contained in the S-Expression KEY
+ into a buffer, which is protected by the passphrase PASSPHRASE.
+ 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;
+
+ err = 0;
+ 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;
+ }
+
+ gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n);
+ /* FIXME: guarantee? */
+
+ err = agent_protect (buffer_new, passphrase, buffer, buffer_n);
+
+ out:
+
+ xfree (buffer_new);
+
+ return err;
+}
+
+
+
+/* Callback function to compare the first entered PIN with the one
+ currently being entered. */
+static int
+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 -1;
+}
+
+/* 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 new key also
+ add an entry to the sshcontrol file. */
+static gpg_error_t
+ssh_identity_register (ctrl_t ctrl, 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 = _("Please re-enter this passphrase");
+ char *comment = NULL;
+ char *key_fpr = NULL;
+ const char *initial_errtext = NULL;
+ unsigned int i;
+ struct pin_entry_info_s *pi = NULL, *pi2;
+
+ err = ssh_key_grip (key, key_grip_raw);
+ if (err)
+ goto out;
+
+ /* Check whether the key is already in our key storage. Don't do
+ anything then. */
+ if ( !agent_key_available (key_grip_raw) )
+ goto out; /* Yes, key is available. */
+
+ err = ssh_get_fingerprint_string (key, &key_fpr);
+ if (err)
+ goto out;
+
+ err = ssh_key_extract_comment (key, &comment);
+ if (err)
+ goto out;
+
+ if ( asprintf (&description,
+ _("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 (2, sizeof (*pi) + 100 + 1);
+ if (!pi)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ pi2 = pi + (sizeof *pi + 100 + 1);
+ pi->max_length = 100;
+ pi->max_tries = 1;
+ pi2->max_length = 100;
+ 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);
+ initial_errtext = NULL;
+ if (err)
+ goto out;
+
+ /* Unless the passphrase is empty, ask to confirm it. */
+ if (pi->pin && *pi->pin)
+ {
+ err = agent_askpin (ctrl, description2, NULL, NULL, pi2);
+ if (err == -1)
+ { /* The re-entered one did not match and the user did not
+ hit cancel. */
+ initial_errtext = _("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. */
+ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0);
+ if (err)
+ goto out;
+
+ /* Cache this passphrase. */
+ for (i = 0; i < 20; i++)
+ sprintf (key_grip + 2 * i, "%02X", key_grip_raw[i]);
+
+ err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl);
+ if (err)
+ goto out;
+
+ /* And add an entry to the sshcontrol file. */
+ err = add_control_entry (ctrl, key_grip, key_fpr, ttl, confirm);
+
+
+ out:
+ 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;
+ 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, NULL);
+ if (err)
+ goto out;
+
+ while (1)
+ {
+ err = stream_read_byte (request, &b);
+ 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, 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 emulation? */
+
+ 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 ssh_request_spec_t *
+request_spec_lookup (int type)
+{
+ 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)
+{
+ ssh_request_spec_t *spec;
+ estream_t response;
+ estream_t request;
+ unsigned char request_type;
+ gpg_error_t err;
+ int send_err;
+ int ret;
+ unsigned char *request_data;
+ u32 request_data_size;
+ u32 response_size;
+
+ request_data = NULL;
+ response = NULL;
+ request = NULL;
+ send_err = 0;
+
+ /* 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+");
+ else
+ request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
+ 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_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+ 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 occured 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:
+
+ if (request)
+ es_fclose (request);
+ if (response)
+ es_fclose (response);
+ xfree (request_data); /* FIXME? */
+
+ return !!err;
+}
+
+/* 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 = 0;
+ int ret;
+
+ /* Because the ssh protocol does not send us information about the
+ the current TTY setting, we resort here to use those from startup
+ or those explictly set. */
+ {
+ static const char *names[] =
+ {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL};
+ int idx;
+ const char *value;
+
+ for (idx=0; !err && names[idx]; idx++)
+ if (!session_env_getenv (ctrl->session_env, names[idx])
+ && (value = session_env_getenv (opt.startup_env, names[idx])))
+ err = session_env_setenv (ctrl->session_env, names[idx], 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));
+ goto out;
+ }
+ }
+
+
+ /* 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 wether 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);
+}
diff --git a/agent/command.c b/agent/command.c
new file mode 100644
index 0000000..97cf507
--- /dev/null
+++ b/agent/command.c
@@ -0,0 +1,2073 @@
+/* command.c - gpg-agent command handler
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005,
+ * 2006, 2008, 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 <http://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 "i18n.h"
+#include "../common/ssh-utils.h"
+
+/* maximum allowed size of the inquired ciphertext */
+#define MAXLEN_CIPHERTEXT 4096
+/* maximum allowed size of the key parameters */
+#define MAXLEN_KEYPARAM 1024
+
+#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
+
+
+#if MAX_DIGEST_LEN < 20
+#error MAX_DIGEST_LEN shorter than keygrip
+#endif
+
+/* Data used to associate an Assuan context with local server data */
+struct server_local_s
+{
+ assuan_context_t assuan_ctx;
+ int message_fd;
+ int use_cache_for_signing;
+ char *keydesc; /* Allocated description for the next key
+ operation. */
+ int pause_io_logging; /* Used to suppress I/O logging during a command */
+ int stopme; /* If set to true the agent will be terminated after
+ the end of this session. */
+ int allow_pinentry_notify; /* Set if pinentry notifications should
+ be done. */
+};
+
+
+/* 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)
+ {
+ memset (p, 0, 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;
+}
+
+
+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;
+ return 0;
+}
+
+
+/* Check whether the option NAME appears in LINE */
+static int
+has_option (const char *line, const char *name)
+{
+ const char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+/* Same as has_option but does only test for the name of the option
+ and ignores an argument, i.e. with NAME being "--hash" it would
+ return true for "--hash" as well as for "--hash=foo". */
+static int
+has_option_name (const char *line, const char *name)
+{
+ const char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ return (s && (s == line || spacep (s-1))
+ && (!s[n] || spacep (s+n) || s[n] == '='));
+}
+
+/* Return a pointer to the argument of the option with NAME. If such
+ an option is not given, it returns NULL. */
+static char *
+option_value (const char *line, const char *name)
+{
+ char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ if (s && (s == line || spacep (s-1))
+ && s[n] && (spacep (s+n) || s[n] == '='))
+ {
+ s += n + 1;
+ s += strspn (s, " ");
+ if (*s && !spacep(s))
+ return s;
+ }
+ return NULL;
+}
+
+
+/* Skip over options. It is assumed that leading spaces have been
+ removed (this is the case for lines passed to a handler from
+ assuan). Blanks after the options are also removed. */
+static char *
+skip_options (char *line)
+{
+ while ( *line == '-' && line[1] == '-' )
+ {
+ while (*line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ }
+ return line;
+}
+
+
+/* Replace all '+' by a blank. */
+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. */
+gpg_error_t
+agent_write_status (ctrl_t ctrl, const char *keyword, ...)
+{
+ gpg_error_t err = 0;
+ va_list arg_ptr;
+ const char *text;
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+ char buf[950], *p;
+ size_t n;
+
+ va_start (arg_ptr, keyword);
+
+ p = buf;
+ n = 0;
+ while ( (text = va_arg (arg_ptr, const char *)) )
+ {
+ if (n)
+ {
+ *p++ = ' ';
+ n++;
+ }
+ for ( ; *text && n < DIM (buf)-3; n++, text++)
+ {
+ if (*text == '\n')
+ {
+ *p++ = '\\';
+ *p++ = 'n';
+ }
+ else if (*text == '\r')
+ {
+ *p++ = '\\';
+ *p++ = 'r';
+ }
+ else
+ *p++ = *text;
+ }
+ }
+ *p = 0;
+ err = assuan_write_status (ctx, keyword, buf);
+
+ 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)
+{
+ char line[100];
+
+ if (!ctrl || !ctrl->server_local
+ || !ctrl->server_local->allow_pinentry_notify)
+ return 0;
+ snprintf (line, DIM(line)-1, "PINENTRY_LAUNCHED %lu", pid);
+ return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
+}
+
+
+
+static const char hlp_geteventcounter[] =
+ "GETEVENTCOUNTER\n"
+ "\n"
+ "Return a 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);
+ char any_counter[25];
+ char key_counter[25];
+ char card_counter[25];
+
+ (void)line;
+
+ snprintf (any_counter, sizeof any_counter, "%u", eventcounter.any);
+ snprintf (key_counter, sizeof key_counter, "%u", eventcounter.key);
+ snprintf (card_counter, sizeof card_counter, "%u", eventcounter.card);
+
+ return agent_write_status (ctrl, "EVENTCOUNTER",
+ any_counter,
+ key_counter,
+ card_counter,
+ NULL);
+}
+
+
+/* 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
+ {
+ log_error ("command is_trusted failed: %s\n", gpg_strerror (rc));
+ return 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)
+{
+ int rc;
+
+ (void)line;
+
+ rc = agent_listtrusted (ctx);
+ if (rc)
+ log_error ("command listtrusted failed: %s\n", gpg_strerror (rc));
+ return 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;
+
+ /* 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);
+ if (rc)
+ log_error ("command marktrusted failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+
+
+static const char hlp_havekey[] =
+ "HAVEKEY <hexstring_with_keygrip>\n"
+ "\n"
+ "Return success when the secret key is available.";
+static gpg_error_t
+cmd_havekey (assuan_context_t ctx, char *line)
+{
+ int rc;
+ unsigned char buf[20];
+
+ rc = parse_keygrip (ctx, line, buf);
+ if (rc)
+ return rc;
+
+ if (agent_key_available (buf))
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ return 0;
+}
+
+
+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 or PKDECRYPT\n"
+ "operation if this operation requires the entry of 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 or PKDECRYPT\n"
+ "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 || !*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);
+ 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;
+
+ /* 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]\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)
+{
+ int rc;
+ cache_mode_t cache_mode = CACHE_MODE_NORMAL;
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ membuf_t outbuf;
+
+ (void)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);
+
+ rc = agent_pksign (ctrl, ctrl->server_local->keydesc,
+ &outbuf, cache_mode);
+ if (rc)
+ clear_outbuf (&outbuf);
+ else
+ rc = write_and_clear_outbuf (ctx, &outbuf);
+ if (rc)
+ log_error ("command pksign failed: %s\n", gpg_strerror (rc));
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+ return rc;
+}
+
+
+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;
+
+ (void)line;
+
+ /* First inquire the data to decrypt */
+ 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);
+ xfree (value);
+ if (rc)
+ clear_outbuf (&outbuf);
+ else
+ rc = write_and_clear_outbuf (ctx, &outbuf);
+ if (rc)
+ log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc));
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+ return rc;
+}
+
+
+static const char hlp_genkey[] =
+ "GENKEY\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 1024)))\n"
+ " C: END\n"
+ " S: D (public-key\n"
+ " S: D (rsa (n 326487324683264) (e 10001)))\n"
+ " S: OK key created\n"
+ "\n";
+static gpg_error_t
+cmd_genkey (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char *value;
+ size_t valuelen;
+ membuf_t outbuf;
+
+ (void)line;
+
+ /* First inquire the parameters */
+ rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
+ if (rc)
+ return rc;
+
+ init_membuf (&outbuf, 512);
+
+ rc = agent_genkey (ctrl, (char*)value, valuelen, &outbuf);
+ xfree (value);
+ if (rc)
+ clear_outbuf (&outbuf);
+ else
+ rc = write_and_clear_outbuf (ctx, &outbuf);
+ if (rc)
+ log_error ("command genkey failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+
+
+static const char hlp_readkey[] =
+ "READKEY <hexstring_with_keygrip>\n"
+ "\n"
+ "Return the public key for the given keygrip.";
+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;
+
+ rc = parse_keygrip (ctx, line, grip);
+ if (rc)
+ return rc; /* Return immediately as this is already an Assuan error code.*/
+
+ rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
+ if (!rc)
+ {
+ size_t len;
+ unsigned char *buf;
+
+ len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xtrymalloc (len);
+ if (!buf)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ rc = assuan_send_data (ctx, buf, len);
+ xfree (buf);
+ }
+ gcry_sexp_release (s_pkey);
+ }
+
+ if (rc)
+ log_error ("command readkey failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+
+static const char hlp_keyinfo[] =
+ "KEYINFO [--list] [--data] [--ssh-fpr] <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. The information is returned as a\n"
+ "status line unless --data was specified, with this format:\n"
+ "\n"
+ " KEYINFO <keygrip> <type> <serialno> <idstr> - - <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"
+ " '-' - Unknown type.\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"
+ "FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
+ " print if the option --ssh-fpr has been used. '-' is printed if the\n"
+ " fingerprint is not available.\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)
+{
+ 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;
+
+ err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
+ if (err)
+ goto leave;
+
+ /* Reformat the grip so that we use uppercase as good style. */
+ bin2hex (grip, 20, hexgrip);
+
+ if (keytype == PRIVATE_KEY_CLEAR
+ || keytype == PRIVATE_KEY_PROTECTED)
+ keytypestr = "D";
+ else if (keytype == PRIVATE_KEY_SHADOWED)
+ keytypestr = "T";
+ else
+ keytypestr = "-";
+
+ /* 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, &fpr);
+ gcry_sexp_release (key);
+ }
+ }
+
+ if (shadow_info)
+ {
+ err = parse_shadow_info (shadow_info, &serialno, &idstr);
+ if (err)
+ goto leave;
+ }
+
+ /* Note that we don't support the CACHED and PROTECTION values as
+ gnupg 2.1 does. We print '-' instead. However we support the
+ ssh fingerprint. */
+ if (!data)
+ err = agent_write_status (ctrl, "KEYINFO",
+ hexgrip,
+ keytypestr,
+ serialno? serialno : "-",
+ idstr? idstr : "-",
+ "-",
+ "-",
+ fpr? fpr : "-",
+ NULL);
+ else
+ {
+ char *string;
+
+ string = xtryasprintf ("%s %s %s %s - - %s\n",
+ hexgrip, keytypestr,
+ serialno? serialno : "-",
+ idstr? idstr : "-",
+ fpr? fpr : "-");
+ 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;
+}
+
+
+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];
+ DIR *dir = NULL;
+ int list_mode;
+ int opt_data, opt_ssh_fpr;
+
+ list_mode = has_option (line, "--list");
+ opt_data = has_option (line, "--data");
+ opt_ssh_fpr = has_option (line, "--ssh-fpr");
+ line = skip_options (line);
+
+ if (list_mode)
+ {
+ char *dirname;
+ struct dirent *dir_entry;
+ char hexgrip[41];
+
+ dirname = make_filename_try (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
+ if (!dirname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ dir = opendir (dirname);
+ if (!dir)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (dirname);
+ goto leave;
+ }
+ xfree (dirname);
+
+ while ( (dir_entry = 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. */
+
+ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr);
+ if (err)
+ goto leave;
+ }
+ err = 0;
+ }
+ else
+ {
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr);
+ }
+
+ leave:
+ if (dir)
+ closedir (dir);
+ if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ log_error ("command keyinfo failed: %s\n", gpg_strerror (err));
+ return err;
+}
+
+
+
+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;
+}
+
+
+static const char hlp_get_passphrase[] =
+ "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
+ " [--qualitybar] <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 \"--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;
+ const char *pw;
+ char *response;
+ char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
+ const char *desc2 = _("Please re-enter this passphrase");
+ char *p;
+ void *cache_marker;
+ int opt_data, opt_check, opt_no_ask, opt_qualbar;
+ int opt_repeat = 0;
+ char *repeat_errtext = NULL;
+
+ 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");
+ 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 || !*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 (cacheid, CACHE_MODE_NORMAL, &cache_marker)
+ : NULL;
+ if (pw)
+ {
+ rc = send_back_passphrase (ctx, opt_data, pw);
+ agent_unlock_cache_entry (&cache_marker);
+ }
+ else if (opt_no_ask)
+ rc = gpg_error (GPG_ERR_NO_DATA);
+ else
+ {
+ /* 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);
+
+ next_try:
+ rc = agent_get_passphrase (ctrl, &response, desc, prompt,
+ repeat_errtext? repeat_errtext:errtext,
+ opt_qualbar);
+ xfree (repeat_errtext);
+ repeat_errtext = NULL;
+ if (!rc)
+ {
+ int i;
+
+ if (opt_check && check_passphrase_constraints (ctrl, response, 0))
+ {
+ xfree (response);
+ goto next_try;
+ }
+ for (i = 0; i < opt_repeat; i++)
+ {
+ char *response2;
+
+ rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
+ errtext, 0);
+ if (rc)
+ break;
+ if (strcmp (response2, response))
+ {
+ xfree (response2);
+ xfree (response);
+ repeat_errtext = try_percent_escape
+ (_("does not match - try again"), NULL);
+ if (!repeat_errtext)
+ {
+ rc = gpg_error_from_syserror ();
+ break;
+ }
+ goto next_try;
+ }
+ xfree (response2);
+ }
+ if (!rc)
+ {
+ if (cacheid)
+ agent_put_cache (cacheid, CACHE_MODE_USER, response, 0);
+ rc = send_back_passphrase (ctx, opt_data, response);
+ }
+ xfree (response);
+ }
+ }
+
+ if (rc)
+ log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+static const char hlp_clear_passphrase[] =
+ "CLEAR_PASSPHRASE <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.";
+static gpg_error_t
+cmd_clear_passphrase (assuan_context_t ctx, char *line)
+{
+ char *cacheid = NULL;
+ char *p;
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ cacheid = p;
+ p = strchr (cacheid, ' ');
+ if (p)
+ *p = 0; /* ignore garbage */
+ if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+ return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
+
+ agent_put_cache (cacheid, CACHE_MODE_USER, NULL, 0);
+ 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;
+
+ /* 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 || !*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);
+ if (rc)
+ log_error ("command get_confirmation failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+
+static const char hlp_learn[] =
+ "LEARN [--send]\n"
+ "\n"
+ "Learn something about the currently inserted smartcard. With\n"
+ "--send the new certificates are send back.";
+static gpg_error_t
+cmd_learn (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ rc = agent_handle_learn (ctrl, has_option (line, "--send")? ctx : NULL);
+ if (rc)
+ log_error ("command learn failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+
+static const char hlp_passwd[] =
+ "PASSWD <hexstring_with_keygrip>\n"
+ "\n"
+ "Change the passphrase/PIN for the key identified by keygrip in LINE.";
+static gpg_error_t
+cmd_passwd (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *shadow_info = NULL;
+
+ rc = parse_keygrip (ctx, line, grip);
+ if (rc)
+ goto leave;
+
+ ctrl->in_passwd++;
+ rc = agent_key_from_file (ctrl, ctrl->server_local->keydesc,
+ grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
+ &s_skey);
+ if (rc)
+ ;
+ else if (!s_skey)
+ {
+ log_error ("changing a smartcard PIN is not yet supported\n");
+ rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ }
+ else
+ rc = agent_protect_and_store (ctrl, s_skey);
+ ctrl->in_passwd--;
+
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
+
+ leave:
+ gcry_sexp_release (s_skey);
+ xfree (shadow_info);
+ if (rc)
+ log_error ("command passwd failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+static const char hlp_preset_passphrase[] =
+ "PRESET_PASSPHRASE <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.";
+static gpg_error_t
+cmd_preset_passphrase (assuan_context_t ctx, char *line)
+{
+ int rc;
+ char *grip_clear = NULL;
+ char *passphrase = NULL;
+ int ttl;
+ size_t len;
+
+ if (!opt.allow_preset_passphrase)
+ return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
+
+ 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)
+ {
+ /* Do in-place conversion. */
+ passphrase = line;
+ if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
+ rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
+ }
+ else
+ rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
+
+ if (!rc)
+ rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl);
+
+ if (rc)
+ log_error ("command preset_passphrase failed: %s\n", gpg_strerror (rc));
+
+ return 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)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ rc = divert_generic_cmd (ctrl, line, ctx);
+
+ return rc;
+}
+
+
+
+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)
+{
+ int rc = 0;
+ char *key = NULL;
+ char *p;
+ struct putval_item_s *vl;
+
+ 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 || !*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);
+
+ if (rc)
+ log_error ("command getval failed: %s\n", gpg_strerror (rc));
+ return 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 an arbitrary symbol with the same syntax rules as keys\n"
+ "for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
+ "corresponsing 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)
+{
+ int rc = 0;
+ char *key = NULL;
+ char *value = NULL;
+ size_t valuelen = 0;
+ char *p;
+ struct putval_item_s *vl, *vlprev;
+
+ 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 || !*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;
+ }
+ }
+
+ if (rc)
+ log_error ("command putval failed: %s\n", gpg_strerror (rc));
+ return 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)
+{
+ static const char *names[] =
+ { "GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ session_env_t se;
+ int idx;
+ char *lc_ctype = NULL;
+ char *lc_messages = NULL;
+
+ (void)line;
+
+ se = session_env_new ();
+ if (!se)
+ err = gpg_error_from_syserror ();
+
+ for (idx=0; !err && names[idx]; idx++)
+ {
+ const char *value = session_env_getenv (ctrl->session_env, names[idx]);
+ if (value)
+ err = session_env_setenv (se, names[idx], 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"
+ "If the agent has been started using a standard socket\n"
+ "we allow a client to 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 (!opt.use_standard_socket)
+ return set_error (GPG_ERR_NOT_SUPPORTED, "no --use-standard-socket");
+
+ ctrl->server_local->stopme = 1;
+ return gpg_error (GPG_ERR_EOF);
+}
+
+
+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)
+{
+ (void)ctx;
+ (void)line;
+
+ 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"
+ " std_session_env - List the standard session environment.\n"
+ " std_startup_env - List the standard startup environment.\n"
+ " cmd_has_option\n"
+ " - Returns OK if the command CMD implements the option OPT.";
+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 (!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_GENERAL);
+ }
+ 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, "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, "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_GENERAL);
+ }
+ }
+ }
+ }
+ else
+ rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
+ return rc;
+}
+
+
+
+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, "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
+ 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;
+
+ /* Note that we only check for the uppercase name. This allows 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;
+ }
+
+ return 0;
+}
+
+
+/* Tell the assuan library about our commands */
+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 },
+ { "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 },
+ { 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
+ intialization. */
+void
+start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
+{
+ int rc;
+ assuan_context_t ctx = NULL;
+
+ 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->message_fd = -1;
+ ctrl->server_local->use_cache_for_signing = 1;
+ ctrl->digest.raw_value = 0;
+
+ assuan_set_io_monitor (ctx, io_monitor, NULL);
+
+ for (;;)
+ {
+ 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;
+ }
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
+ continue;
+ }
+ }
+
+ /* 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);
+ if (ctrl->server_local->stopme)
+ agent_exit (0);
+ xfree (ctrl->server_local);
+ ctrl->server_local = NULL;
+}
+
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
new file mode 100644
index 0000000..bf07d07
--- /dev/null
+++ b/agent/divert-scd.c
@@ -0,0 +1,451 @@
+/* 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 <http://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 "i18n.h"
+#include "sexp-parse.h"
+
+
+static int
+ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
+{
+ int rc, i;
+ char *serialno;
+ int no_card = 0;
+ char *desc;
+ char *want_sn, *want_kid;
+ int want_sn_displen;
+
+ *r_kid = NULL;
+
+ rc = parse_shadow_info (shadow_info, &want_sn, &want_kid);
+ if (rc)
+ return rc;
+
+ /* 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_displen = strlen (want_sn);
+ if (want_sn_displen == 20 && want_sn[19] == '0')
+ want_sn_displen--;
+
+ for (;;)
+ {
+ rc = agent_card_serialno (ctrl, &serialno);
+ if (!rc)
+ {
+ log_debug ("detected card with S/N %s\n", serialno);
+ i = strcmp (serialno, want_sn);
+ xfree (serialno);
+ serialno = NULL;
+ if (!i)
+ {
+ xfree (want_sn);
+ *r_kid = want_kid;
+ return 0; /* yes, we have the correct card */
+ }
+ }
+ else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT)
+ {
+ log_debug ("no card present\n");
+ rc = 0;
+ no_card = 1;
+ }
+ else
+ {
+ log_error ("error accessing card: %s\n", gpg_strerror (rc));
+ }
+
+ if (!rc)
+ {
+ if (asprintf (&desc,
+ "%s:%%0A%%0A"
+ " \"%.*s\"",
+ no_card
+ ? _("Please insert the card with serial number")
+ : _("Please remove the current card and "
+ "insert the one with serial number"),
+ want_sn_displen, want_sn) < 0)
+ {
+ rc = out_of_core ();
+ }
+ else
+ {
+ rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+ xfree (desc);
+ }
+ }
+ if (rc)
+ {
+ xfree (want_sn);
+ xfree (want_kid);
+ 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 ("encoded hash:", frame, asnlen+digestlen);
+
+ *r_val = frame;
+ *r_len = asnlen+digestlen;
+ return 0;
+}
+
+
+/* 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.
+
+ 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 *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 = _("Admin PIN");
+ else if (*s == 'P')
+ {
+ /* TRANSLATORS: A PUK is the Personal Unblocking Code
+ used to unblock a PIN. */
+ prompt = _("PUK");
+ is_puk = 1;
+ }
+ else if (*s == 'N')
+ newpin = 1;
+ else if (*s == 'R')
+ {
+ prompt = _("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 keypad mode: The
+ callback opens the popup and immediatley 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;
+
+ if ( asprintf (&desc,
+ _("%s%%0A%%0AUse the reader's keypad for input."),
+ info) < 0 )
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ rc = agent_popup_message_start (ctrl, desc, NULL);
+ xfree (desc);
+ }
+ }
+ else
+ rc = agent_popup_message_start (ctrl, NULL, 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)
+ {
+ rc = agent_askpin (ctrl, info, prompt, again_text, pi);
+ 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?
+ _("Repeat this Reset Code"):
+ is_puk?
+ _("Repeat this PUK"):
+ _("Repeat this PIN")),
+ prompt, NULL, pi2);
+ if (!rc && strcmp (pi->pin, pi2->pin))
+ {
+ again_text = (resetcode?
+ N_("Reset Code not correctly repeated; try again"):
+ is_puk?
+ N_("PUK not correctly repeated; try again"):
+ N_("PIN not correctly repeated; try again"));
+ xfree (pi2);
+ xfree (pi);
+ goto again;
+ }
+ xfree (pi2);
+ }
+ }
+ else
+ {
+ char *desc;
+ if ( asprintf (&desc,
+ _("Please enter the PIN%s%s%s to unlock the card"),
+ info? " (`":"",
+ info? info:"",
+ info? "')":"") < 0)
+ desc = NULL;
+ rc = agent_askpin (ctrl, desc?desc:info, prompt, NULL, pi);
+ xfree (desc);
+ }
+
+ if (!rc)
+ {
+ strncpy (buf, pi->pin, maxbuf-1);
+ buf[maxbuf-1] = 0;
+ }
+ xfree (pi);
+ return rc;
+}
+
+
+
+
+int
+divert_pksign (ctrl_t ctrl,
+ const unsigned char *digest, size_t digestlen, int algo,
+ const unsigned char *shadow_info, unsigned char **r_sig)
+{
+ int rc;
+ char *kid;
+ size_t siglen;
+ unsigned char *sigval = NULL;
+
+ rc = ask_for_card (ctrl, shadow_info, &kid);
+ if (rc)
+ return rc;
+
+ 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,
+ 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,
+ data, ndata, &sigval, &siglen);
+ xfree (data);
+ }
+ }
+
+ if (!rc)
+ *r_sig = sigval;
+
+ xfree (kid);
+
+ return rc;
+}
+
+
+/* Decrypt the 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. */
+int
+divert_pkdecrypt (ctrl_t ctrl,
+ const unsigned char *cipher,
+ const unsigned char *shadow_info,
+ char **r_buf, size_t *r_len)
+{
+ int rc;
+ char *kid;
+ const unsigned char *s;
+ size_t n;
+ const unsigned char *ciphertext;
+ size_t ciphertextlen;
+ char *plaintext;
+ size_t plaintextlen;
+
+ 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);
+ if (!smatch (&s, n, "rsa"))
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+ 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);
+ if (!n)
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ ciphertext = s;
+ ciphertextlen = n;
+
+ rc = ask_for_card (ctrl, shadow_info, &kid);
+ if (rc)
+ return rc;
+
+ rc = agent_card_pkdecrypt (ctrl, kid, getpin_cb, ctrl,
+ ciphertext, ciphertextlen,
+ &plaintext, &plaintextlen);
+ if (!rc)
+ {
+ *r_buf = plaintext;
+ *r_len = plaintextlen;
+ }
+ xfree (kid);
+ return rc;
+}
+
+
+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..800db88
--- /dev/null
+++ b/agent/findkey.c
@@ -0,0 +1,952 @@
+/* findkey.c - Locate the secret key
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
+ * 2010, 2011 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 <http://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 <pth.h> /* (we use pth_sleep) */
+
+#include "agent.h"
+#include "i18n.h"
+#include "../common/ssh-utils.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 chnage the passphrase. */
+};
+
+
+/* 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. */
+int
+agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force)
+{
+ char *fname;
+ FILE *fp;
+ char hexgrip[40+4+1];
+ int fd;
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+
+ if (!force && !access (fname, F_OK))
+ {
+ log_error ("secret key file `%s' already exists\n", fname);
+ xfree (fname);
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ /* In FORCE mode we would like to create FNAME but only if it does
+ not already exist. We cannot make this guarantee just using
+ POSIX (GNU provides the "x" opentype for fopen, however, this is
+ not portable). Thus, we use the more flexible open function and
+ then use fdopen to obtain a stream. */
+ fd = open (fname, force? (O_CREAT | O_TRUNC | O_WRONLY | O_BINARY)
+ : (O_CREAT | O_EXCL | O_WRONLY | O_BINARY),
+ S_IRUSR | S_IWUSR
+#ifndef HAVE_W32_SYSTEM
+ | S_IRGRP
+#endif
+ );
+ if (fd < 0)
+ fp = NULL;
+ else
+ {
+ fp = fdopen (fd, "wb");
+ if (!fp)
+ {
+ int save_e = errno;
+ close (fd);
+ errno = save_e;
+ }
+ }
+
+ if (!fp)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("can't create `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ return tmperr;
+ }
+
+ if (fwrite (buffer, length, 1, fp) != 1)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error writing `%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+ if ( fclose (fp) )
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error closing `%s': %s\n", fname, strerror (errno));
+ remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+ bump_key_eventcounter ();
+ xfree (fname);
+ return 0;
+}
+
+
+/* Callback function to try the unprotection from the passpharse query
+ code. */
+static int
+try_unprotect_cb (struct pin_entry_info_s *pi)
+{
+ struct try_unprotect_arg_s *arg = pi->check_cb_arg;
+ 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 (arg->protected_key, pi->pin, protected_at,
+ &arg->unprotected_key, &dummy);
+ if (err)
+ return err;
+ if (!opt.max_passphrase_days || arg->ctrl->in_passwd)
+ return 0; /* No regular passphrase change required. */
+
+ if (!*protected_at)
+ {
+ /* No protection date known - must force passphrase change. */
+ desc = xtrystrdup (_("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
+ (_("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 (arg->ctrl, desc,
+ _("Change passphrase"), NULL, 0);
+ if (!err)
+ arg->change_required = 1;
+ }
+ else
+ {
+ err = agent_get_confirmation (arg->ctrl, desc,
+ _("Change passphrase"),
+ _("I'll change it later"), 0);
+ if (!err)
+ arg->change_required = 1;
+ else if (gpg_err_code (err) == GPG_ERR_CANCELED)
+ err = 0;
+ }
+ xfree (desc);
+ }
+
+ return 0;
+}
+
+
+/* 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.
+ %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.
+ */
+static gpg_error_t
+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;
+
+ comment_length = strlen (comment);
+ in_len = strlen (in);
+
+ /* First pass calculates the length, second pass does the actual
+ copying. */
+ 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 'F': /* SSH style fingerprint. */
+ if (!ssh_fpr && key)
+ ssh_get_fingerprint_string (key, &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;
+ assert (*result + out_len == out);
+ xfree (ssh_fpr);
+ 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. */
+static int
+unprotect (ctrl_t ctrl, const char *desc_text,
+ unsigned char **keybuf, const unsigned char *grip,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl)
+{
+ struct pin_entry_info_s *pi;
+ struct try_unprotect_arg_s arg;
+ int rc;
+ unsigned char *result;
+ size_t resultlen;
+ char hexgrip[40+1];
+
+ bin2hex (grip, 20, hexgrip);
+
+ /* 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)
+ {
+ void *cache_marker;
+ const char *pw;
+
+ retry:
+ pw = agent_get_cache (hexgrip, cache_mode, &cache_marker);
+ if (pw)
+ {
+ rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen);
+ agent_unlock_cache_entry (&cache_marker);
+ if (!rc)
+ {
+ xfree (*keybuf);
+ *keybuf = result;
+ return 0;
+ }
+ rc = 0;
+ }
+
+ /* 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. */
+ pth_sleep (1);
+ goto retry;
+ }
+ /* Timeout - better call pinentry now the plain way. */
+ }
+ }
+
+ pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi->max_length = 100;
+ 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);
+ if (!rc)
+ {
+ assert (arg.unprotected_key);
+ if (arg.change_required)
+ {
+ 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);
+ 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
+ agent_put_cache (hexgrip, cache_mode, pi->pin,
+ lookup_ttl? lookup_ttl (hexgrip) : 0);
+ 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. 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)
+{
+ int rc;
+ char *fname;
+ FILE *fp;
+ struct stat st;
+ unsigned char *buf;
+ size_t buflen, erroff;
+ gcry_sexp_t s_skey;
+ char hexgrip[40+4+1];
+
+ *result = NULL;
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ fp = fopen (fname, "rb");
+ if (!fp)
+ {
+ rc = gpg_error_from_syserror ();
+ if (gpg_err_code (rc) != GPG_ERR_ENOENT)
+ log_error ("can't open `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ return rc;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ rc = gpg_error_from_syserror ();
+ log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ fclose (fp);
+ return rc;
+ }
+
+ buflen = st.st_size;
+ buf = xtrymalloc (buflen+1);
+ if (!buf || fread (buf, buflen, 1, fp) != 1)
+ {
+ rc = gpg_error_from_syserror ();
+ log_error ("error reading `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ fclose (fp);
+ xfree (buf);
+ return rc;
+ }
+
+ /* Convert the file into a gcrypt S-expression object. */
+ rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
+ xfree (fname);
+ fclose (fp);
+ xfree (buf);
+ if (rc)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ return rc;
+ }
+ *result = s_skey;
+ return 0;
+}
+
+
+/* Return the secret key as an S-Exp in RESULT after locating it using
+ the GRIP. Stores NULL at RESULT if the operation shall be diverted
+ to a token; in this case an allocated S-expression with the
+ shadow_info part from the file is 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). */
+gpg_error_t
+agent_key_from_file (ctrl_t ctrl, 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)
+{
+ int rc;
+ unsigned char *buf;
+ size_t len, buflen, erroff;
+ gcry_sexp_t s_skey;
+ int got_shadow_info = 0;
+
+ *result = NULL;
+ if (shadow_info)
+ *shadow_info = NULL;
+
+ rc = read_key_file (grip, &s_skey);
+ if (rc)
+ return rc;
+
+ /* For use with the protection functions we also need the key as an
+ canonical encoded S-expression in a buffer. Create this buffer
+ now. */
+ rc = make_canon_sexp (s_skey, &buf, &len);
+ if (rc)
+ return rc;
+
+ switch (agent_private_key_type (buf))
+ {
+ case PRIVATE_KEY_CLEAR:
+ break; /* no unprotection needed */
+ 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)
+ rc = modify_description (desc_text, comment? comment:"", s_skey,
+ &desc_text_final);
+ gcry_free (comment);
+
+ if (!rc)
+ {
+ rc = unprotect (ctrl, desc_text_final, &buf, grip,
+ cache_mode, lookup_ttl);
+ if (rc)
+ log_error ("failed to unprotect the secret key: %s\n",
+ gpg_strerror (rc));
+ }
+
+ xfree (desc_text_final);
+ }
+ break;
+ case PRIVATE_KEY_SHADOWED:
+ if (shadow_info)
+ {
+ const unsigned char *s;
+ size_t n;
+
+ rc = agent_get_shadow_info (buf, &s);
+ if (!rc)
+ {
+ n = gcry_sexp_canon_len (s, 0, NULL,NULL);
+ assert (n);
+ *shadow_info = xtrymalloc (n);
+ if (!*shadow_info)
+ rc = out_of_core ();
+ else
+ {
+ memcpy (*shadow_info, s, n);
+ rc = 0;
+ got_shadow_info = 1;
+ }
+ }
+ if (rc)
+ log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+ }
+ else
+ rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ break;
+ default:
+ log_error ("invalid private key format\n");
+ rc = gpg_error (GPG_ERR_BAD_SECKEY);
+ break;
+ }
+ gcry_sexp_release (s_skey);
+ s_skey = NULL;
+ if (rc || got_shadow_info)
+ {
+ xfree (buf);
+ return rc;
+ }
+
+ buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
+ rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
+ wipememory (buf, buflen);
+ xfree (buf);
+ if (rc)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ return rc;
+ }
+
+ *result = s_skey;
+ return 0;
+}
+
+
+/* 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);
+ if (!err)
+ *result = 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)
+{
+ int i, idx, rc;
+ gcry_sexp_t s_skey;
+ const char *algoname;
+ gcry_sexp_t uri_sexp, comment_sexp;
+ const char *uri, *comment;
+ size_t uri_length, comment_length;
+ char *format, *p;
+ void *args[4+2+2+1]; /* Size is max. # of elements + 2 for uri + 2
+ for comment + end-of-list. */
+ int argidx;
+ gcry_sexp_t list, l2;
+ const char *name;
+ const char *s;
+ size_t n;
+ const char *elems;
+ gcry_mpi_t *array;
+
+ (void)ctrl;
+
+ *result = NULL;
+
+ rc = read_key_file (grip, &s_skey);
+ if (rc)
+ return rc;
+
+ list = gcry_sexp_find_token (s_skey, "shadowed-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_skey, "protected-private-key", 0 );
+ if (!list)
+ list = gcry_sexp_find_token (s_skey, "private-key", 0 );
+ if (!list)
+ {
+ log_error ("invalid private key format\n");
+ gcry_sexp_release (s_skey);
+ 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, "elg", 3))
+ {
+ algoname = "elg";
+ elems = "pgy";
+ }
+ else
+ {
+ log_error ("unknown private key algorithm\n");
+ gcry_sexp_release (list);
+ gcry_sexp_release (s_skey);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ /* Allocate an array for the parameters and copy them out of the
+ secret key. FIXME: We should have a generic copy function. */
+ array = xtrycalloc (strlen(elems) + 1, sizeof *array);
+ if (!array)
+ {
+ rc = gpg_error_from_syserror ();
+ gcry_sexp_release (list);
+ gcry_sexp_release (s_skey);
+ return rc;
+ }
+
+ for (idx=0, s=elems; *s; s++, idx++ )
+ {
+ l2 = gcry_sexp_find_token (list, s, 1);
+ if (!l2)
+ {
+ /* Required parameter not found. */
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ gcry_sexp_release (s_skey);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release (l2);
+ if (!array[idx])
+ {
+ /* Required parameter is invalid. */
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ gcry_sexp_release (s_skey);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ }
+ gcry_sexp_release (list);
+ list = NULL;
+
+ 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 suioted for
+ such a task. After all that is what we do in protect.c. Neeed
+ to find common patterns and write a straightformward API to use
+ them. */
+ assert (sizeof (size_t) <= sizeof (void*));
+
+ format = xtrymalloc (15+7*strlen (elems)+10+15+1+1);
+ if (!format)
+ {
+ rc = gpg_error_from_syserror ();
+ for (i=0; array[i]; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (uri_sexp);
+ gcry_sexp_release (comment_sexp);
+ return rc;
+ }
+
+ argidx = 0;
+ p = stpcpy (stpcpy (format, "(public-key("), algoname);
+ for (idx=0, s=elems; *s; s++, 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));
+ args[argidx++] = (void *)uri_length;
+ args[argidx++] = (void *)uri;
+ }
+ if (comment)
+ {
+ p = stpcpy (p, "(comment %b)");
+ assert (argidx+1 < DIM (args));
+ args[argidx++] = (void *)comment_length;
+ args[argidx++] = (void*)comment;
+ }
+ *p++ = ')';
+ *p = 0;
+ assert (argidx < DIM (args));
+ args[argidx] = NULL;
+
+ rc = gcry_sexp_build_array (&list, NULL, format, args);
+ xfree (format);
+ for (i=0; array[i]; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (uri_sexp);
+ gcry_sexp_release (comment_sexp);
+
+ if (!rc)
+ *result = list;
+ return rc;
+}
+
+
+
+/* Return the secret key as an S-Exp after locating it using the grip.
+ Returns NULL if key is not available. 0 = 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 (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ result = !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);
+ 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:
+ 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;
+}
diff --git a/agent/genkey.c b/agent/genkey.c
new file mode 100644
index 0000000..176e77d
--- /dev/null
+++ b/agent/genkey.c
@@ -0,0 +1,484 @@
+/* genkey.c - Generate a keypair
+ * Copyright (C) 2002, 2003, 2004, 2007 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 <http://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 "agent.h"
+#include "i18n.h"
+#include "exechelp.h"
+#include "sysutils.h"
+
+static int
+store_key (gcry_sexp_t private, const char *passphrase, int force)
+{
+ 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);
+ if (rc)
+ {
+ xfree (buf);
+ return rc;
+ }
+ xfree (buf);
+ buf = p;
+ }
+
+ rc = agent_write_private_key (grip, buf, len, force);
+ 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. */
+static int
+check_passphrase_pattern (ctrl_t ctrl, const char *pw)
+{
+ gpg_error_t err = 0;
+ const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN);
+ FILE *infp;
+ const char *argv[10];
+ pid_t pid;
+ int result, i;
+
+ (void)ctrl;
+
+ infp = gnupg_tmpfile ();
+ if (!infp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error creating temporary file: %s\n"), gpg_strerror (err));
+ return 1; /* Error - assume password should not be used. */
+ }
+
+ if (fwrite (pw, strlen (pw), 1, infp) != 1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error writing to temporary file: %s\n"),
+ gpg_strerror (err));
+ fclose (infp);
+ return 1; /* Error - assume password should not be used. */
+ }
+ rewind (infp);
+
+ i = 0;
+ argv[i++] = "--null";
+ argv[i++] = "--",
+ argv[i++] = opt.check_passphrase_pattern,
+ argv[i] = NULL;
+ assert (i < sizeof argv);
+
+ if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid))
+ result = 1; /* Execute error - assume password should no be used. */
+ else if (gnupg_wait_process (pgmname, pid, NULL))
+ result = 1; /* Helper returned an error - probably a match. */
+ else
+ result = 0; /* Success; i.e. no match. */
+
+ /* Overwrite our temporary file. */
+ rewind (infp);
+ for (i=((strlen (pw)+99)/100)*100; i > 0; i--)
+ putc ('\xff', infp);
+ fflush (infp);
+ fclose (infp);
+ 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, _("Enter new passphrase"));
+ if (!err)
+ err = gpg_error (GPG_ERR_CANCELED);
+ }
+ else
+ err = agent_get_confirmation (ctrl, desc,
+ anyway_btn, _("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, _("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 SILENT is set, no message are
+ displayed. */
+int
+check_passphrase_constraints (ctrl_t ctrl, const char *pw, int silent)
+{
+ gpg_error_t err;
+ unsigned int minlen = opt.min_passphrase_len;
+ unsigned int minnonalpha = opt.min_passphrase_nonalpha;
+
+ if (!pw)
+ pw = "";
+
+ if (utf8_charcount (pw) < minlen )
+ {
+ char *desc;
+
+ if (silent)
+ return gpg_error (GPG_ERR_INV_PASSPHRASE);
+
+ desc = xtryasprintf
+ ( ngettext ("Warning: You have entered an insecure passphrase.%%0A"
+ "A passphrase should be at least %u character long.",
+ "Warning: You have entered an insecure passphrase.%%0A"
+ "A passphrase should be at least %u characters long.",
+ minlen), minlen );
+ if (!desc)
+ return gpg_error_from_syserror ();
+ err = take_this_one_anyway (ctrl, desc);
+ xfree (desc);
+ if (err)
+ return err;
+ }
+
+ if (nonalpha_count (pw) < minnonalpha )
+ {
+ char *desc;
+
+ if (silent)
+ return gpg_error (GPG_ERR_INV_PASSPHRASE);
+
+ desc = xtryasprintf
+ ( ngettext ("Warning: You have entered an insecure passphrase.%%0A"
+ "A passphrase should contain at least %u digit or%%0A"
+ "special character.",
+ "Warning: You have entered an insecure passphrase.%%0A"
+ "A passphrase should contain at least %u digits or%%0A"
+ "special characters.",
+ minnonalpha), minnonalpha );
+ if (!desc)
+ return gpg_error_from_syserror ();
+ err = take_this_one_anyway (ctrl, desc);
+ xfree (desc);
+ if (err)
+ return err;
+ }
+
+ /* If configured check the passphrase against a list of know 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 &&
+ check_passphrase_pattern (ctrl, pw))
+ {
+ const char *desc =
+ /* */ _("Warning: You have entered an insecure passphrase.%%0A"
+ "A passphrase may not be a known term or match%%0A"
+ "certain pattern.");
+
+ if (silent)
+ return gpg_error (GPG_ERR_INV_PASSPHRASE);
+
+ err = take_this_one_anyway (ctrl, desc);
+ if (err)
+ return err;
+ }
+
+ /* The final check is to warn about an empty passphrase. */
+ if (!*pw)
+ {
+ const char *desc = (opt.enforce_passphrase_constraints?
+ _("You have not entered a passphrase!%0A"
+ "An empty passphrase is not allowed.") :
+ _("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."));
+
+ if (silent)
+ return gpg_error (GPG_ERR_INV_PASSPHRASE);
+
+ err = take_this_one_anyway2 (ctrl, desc,
+ _("Yes, protection is not needed"));
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+
+/* Callback function to compare the first entered PIN with the one
+ currently being entered. */
+static int
+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 -1;
+}
+
+
+
+/* Generate a new keypair according to the parameters given in
+ KEYPARAM */
+int
+agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen,
+ membuf_t *outbuf)
+{
+ gcry_sexp_t s_keyparam, s_key, s_private, s_public;
+ struct pin_entry_info_s *pi, *pi2;
+ 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. */
+ {
+ const char *text1 = _("Please enter the passphrase to%0A"
+ "to protect your new key");
+ const char *text2 = _("Please re-enter this passphrase");
+ const char *initial_errtext = NULL;
+
+ pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+ pi2 = pi + (sizeof *pi + 100);
+ pi->max_length = 100;
+ pi->max_tries = 3;
+ pi->with_qualitybar = 1;
+ pi2->max_length = 100;
+ pi2->max_tries = 3;
+ pi2->check_cb = reenter_compare_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ next_try:
+ rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi);
+ initial_errtext = NULL;
+ if (!rc)
+ {
+ if (check_passphrase_constraints (ctrl, pi->pin, 0))
+ {
+ pi->failed_tries = 0;
+ pi2->failed_tries = 0;
+ goto next_try;
+ }
+ if (pi->pin && *pi->pin)
+ {
+ rc = agent_askpin (ctrl, text2, NULL, NULL, pi2);
+ if (rc == -1)
+ { /* The re-entered one did not match and the user did not
+ hit cancel. */
+ initial_errtext = _("does not match - try again");
+ goto next_try;
+ }
+ }
+ }
+ if (rc)
+ {
+ xfree (pi);
+ return rc;
+ }
+
+ if (!*pi->pin)
+ {
+ xfree (pi);
+ pi = NULL; /* User does not want a passphrase. */
+ }
+ }
+
+ 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 (pi);
+ 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 (pi);
+ 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 (pi);
+ 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, pi? pi->pin:NULL, 0);
+ xfree (pi); pi = 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 passpahrse to the key S_SKEY and store it. */
+int
+agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey)
+{
+ struct pin_entry_info_s *pi, *pi2;
+ int rc;
+
+ {
+ const char *text1 = _("Please enter the new passphrase");
+ const char *text2 = _("Please re-enter this passphrase");
+ const char *initial_errtext = NULL;
+
+ pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+ pi2 = pi + (sizeof *pi + 100);
+ pi->max_length = 100;
+ pi->max_tries = 3;
+ pi->with_qualitybar = 1;
+ pi2->max_length = 100;
+ pi2->max_tries = 3;
+ pi2->check_cb = reenter_compare_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ next_try:
+ rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi);
+ initial_errtext = NULL;
+ if (!rc)
+ {
+ if (check_passphrase_constraints (ctrl, pi->pin, 0))
+ {
+ pi->failed_tries = 0;
+ pi2->failed_tries = 0;
+ goto next_try;
+ }
+ /* Unless the passphrase is empty, ask to confirm it. */
+ if (pi->pin && *pi->pin)
+ {
+ rc = agent_askpin (ctrl, text2, NULL, NULL, pi2);
+ if (rc == -1)
+ { /* The re-entered one did not match and the user did not
+ hit cancel. */
+ initial_errtext = _("does not match - try again");
+ goto next_try;
+ }
+ }
+ }
+ if (rc)
+ {
+ xfree (pi);
+ return rc;
+ }
+
+ if (!*pi->pin)
+ {
+ xfree (pi);
+ pi = NULL; /* User does not want a passphrase. */
+ }
+ }
+
+ rc = store_key (s_skey, pi? pi->pin:NULL, 1);
+ xfree (pi);
+ return rc;
+}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
new file mode 100644
index 0000000..b00d899
--- /dev/null
+++ b/agent/gpg-agent.c
@@ -0,0 +1,2291 @@
+/* gpg-agent.c - The GnuPG Agent
+ * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005,
+ * 2006, 2007, 2009, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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>
+#ifndef HAVE_W32_SYSTEM
+# include <sys/socket.h>
+# include <sys/un.h>
+#endif /*!HAVE_W32_SYSTEM*/
+#include <unistd.h>
+#include <signal.h>
+#include <pth.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#define JNLIB_NEED_AFLOCAL
+#include "agent.h"
+#include <assuan.h> /* Malloc hooks and socket wrappers. */
+
+#include "i18n.h"
+#include "mkdtemp.h" /* Gnulib replacement. */
+#include "sysutils.h"
+#include "setenv.h"
+#include "gc-opt-flags.h"
+#include "exechelp.h"
+#include "../common/estream.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,
+ oNoGreeting,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oNoGrab,
+ oLogFile,
+ oServer,
+ oDaemon,
+ oBatch,
+
+ oPinentryProgram,
+ oPinentryTouchFile,
+ oDisplay,
+ oTTYname,
+ oTTYtype,
+ oLCctype,
+ oLCmessages,
+ oXauthority,
+ oScdaemonProgram,
+ oDefCacheTTL,
+ oDefCacheTTLSSH,
+ oMaxCacheTTL,
+ oMaxCacheTTLSSH,
+ oEnforcePassphraseConstraints,
+ oMinPassphraseLen,
+ oMinPassphraseNonalpha,
+ oCheckPassphrasePattern,
+ oMaxPassphraseDays,
+ oEnablePassphraseHistory,
+ oUseStandardSocket,
+ oNoUseStandardSocket,
+ oFakedSystemTime,
+
+ oIgnoreCacheForSigning,
+ oAllowMarkTrusted,
+ oAllowPresetPassphrase,
+ oKeepTTY,
+ oKeepDISPLAY,
+ oSSHSupport,
+ oDisableScdaemon,
+ oWriteEnvFile
+};
+
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { aGPGConfList, "gpgconf-list", 256, "@" },
+ { aGPGConfTest, "gpgconf-test", 256, "@" },
+ { aUseStandardSocketP, "use-standard-socket-p", 256, "@" },
+
+ { 301, NULL, 0, N_("@Options:\n ") },
+
+ { oServer, "server", 0, N_("run in server mode (foreground)") },
+ { oDaemon, "daemon", 0, N_("run in daemon mode (background)") },
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oSh, "sh", 0, N_("sh-style command output") },
+ { oCsh, "csh", 0, N_("csh-style command output") },
+ { oOptions, "options" , 2, N_("|FILE|read options from FILE")},
+ { oDebug, "debug" ,4|16, "@"},
+ { oDebugAll, "debug-all" ,0, "@"},
+ { oDebugLevel, "debug-level" ,2, "@"},
+ { oDebugWait,"debug-wait",1, "@"},
+ { oNoDetach, "no-detach" ,0, N_("do not detach from the console")},
+ { oNoGrab, "no-grab" ,0, N_("do not grab keyboard and mouse")},
+ { oLogFile, "log-file" ,2, N_("use a log file for the server")},
+ { oUseStandardSocket, "use-standard-socket", 0,
+ N_("use a standard location for the socket")},
+ { oNoUseStandardSocket, "no-use-standard-socket", 0, "@"},
+ { oPinentryProgram, "pinentry-program", 2 ,
+ N_("|PGM|use PGM as the PIN-Entry program") },
+ { oPinentryTouchFile, "pinentry-touch-file", 2 , "@" },
+ { oScdaemonProgram, "scdaemon-program", 2 ,
+ N_("|PGM|use PGM as the SCdaemon program") },
+ { oDisableScdaemon, "disable-scdaemon", 0, N_("do not use the SCdaemon") },
+ { oFakedSystemTime, "faked-system-time", 2, "@" }, /* (epoch time) */
+
+ { oBatch, "batch", 0, "@" },
+ { oHomedir, "homedir", 2, "@"},
+
+ { oDisplay, "display", 2, "@" },
+ { oTTYname, "ttyname", 2, "@" },
+ { oTTYtype, "ttytype", 2, "@" },
+ { oLCctype, "lc-ctype", 2, "@" },
+ { oLCmessages, "lc-messages", 2, "@" },
+ { oXauthority, "xauthority", 2, "@" },
+ { oKeepTTY, "keep-tty", 0, N_("ignore requests to change the TTY")},
+ { oKeepDISPLAY, "keep-display",
+ 0, N_("ignore requests to change the X display")},
+
+ { oDefCacheTTL, "default-cache-ttl", 4,
+ N_("|N|expire cached PINs after N seconds")},
+ { oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" },
+ { oMaxCacheTTL, "max-cache-ttl", 4, "@" },
+ { oMaxCacheTTLSSH, "max-cache-ttl-ssh", 4, "@" },
+
+ { oEnforcePassphraseConstraints, "enforce-passphrase-constraints", 0, "@"},
+ { oMinPassphraseLen, "min-passphrase-len", 4, "@" },
+ { oMinPassphraseNonalpha, "min-passphrase-nonalpha", 4, "@" },
+ { oCheckPassphrasePattern, "check-passphrase-pattern", 2, "@" },
+ { oMaxPassphraseDays, "max-passphrase-days", 4, "@" },
+ { oEnablePassphraseHistory, "enable-passphrase-history", 0, "@" },
+
+ { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0,
+ N_("do not use the PIN cache when signing")},
+ { oAllowMarkTrusted, "allow-mark-trusted", 0,
+ N_("allow clients to mark keys as \"trusted\"")},
+ { oAllowPresetPassphrase, "allow-preset-passphrase", 0,
+ N_("allow presetting passphrase")},
+ { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh-agent emulation") },
+ { oWriteEnvFile, "write-env-file", 2|8,
+ N_("|FILE|write environment settings also to FILE")},
+ {0}
+};
+
+
+#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. For Windows we use a
+ longer period as the SetWaitableTimer seems to signal earlier than
+ the 2 seconds. */
+#ifdef HAVE_W32_SYSTEM
+#define TIMERTICK_INTERVAL (4)
+#else
+#define TIMERTICK_INTERVAL (2) /* Seconds. */
+#endif
+
+
+/* The list of open file descriptors at startup. Note that this list
+ has been allocated using the standard malloc. */
+static int *startup_fd_list;
+
+/* 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;
+
+/* 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. */
+static char *socket_name;
+
+/* Name of the communication socket used for ssh-agent-emulation. */
+static char *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_ssh;
+
+
+/* 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 will be reread on a HUP if it is not NULL. */
+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);
+
+
+/*
+ Local prototypes.
+ */
+
+static char *create_socket_name (char *standard_name, char *template);
+static gnupg_fd_t create_server_socket (char *name, int is_ssh,
+ assuan_sock_nonce_t *nonce);
+static void create_directories (void);
+
+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_ssh);
+static void check_own_socket (void);
+static int check_for_running_agent (int silent, int mode);
+
+/* Pth wrapper function definitions. */
+ASSUAN_SYSTEM_PTH_IMPL;
+
+GCRY_THREAD_OPTION_PTH_IMPL;
+static int fixed_gcry_pth_init (void)
+{
+ return pth_self ()? 0 : (pth_init () == FALSE) ? errno : 0;
+}
+
+
+#ifndef PTH_HAVE_PTH_THREAD_ID
+static unsigned long pth_thread_id (void)
+{
+ return (unsigned long)pth_self ();
+}
+#endif
+
+
+
+/*
+ Functions.
+ */
+
+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;
+}
+
+
+static const char *
+my_strusage (int level)
+{
+ static char *ver_gcry;
+ const char *p;
+
+ switch (level)
+ {
+ case 11: p = "gpg-agent (GnuPG)";
+ break;
+ case 13: p = VERSION; 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_ASSUAN_VALUE;
+ else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
+ opt.debug = DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE;
+ else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
+ opt.debug = (DBG_ASSUAN_VALUE|DBG_COMMAND_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)
+ log_info ("enabled debug flags:%s%s%s%s%s%s%s%s\n",
+ (opt.debug & DBG_COMMAND_VALUE)? " command":"",
+ (opt.debug & DBG_MPI_VALUE )? " mpi":"",
+ (opt.debug & DBG_CRYPTO_VALUE )? " crypto":"",
+ (opt.debug & DBG_MEMORY_VALUE )? " memory":"",
+ (opt.debug & DBG_CACHE_VALUE )? " cache":"",
+ (opt.debug & DBG_MEMSTAT_VALUE)? " memstat":"",
+ (opt.debug & DBG_HASHING_VALUE)? " hashing":"",
+ (opt.debug & DBG_ASSUAN_VALUE )? " assuan":"");
+}
+
+
+/* Helper for cleanup to remove one socket with NAME. */
+static void
+remove_socket (char *name)
+{
+ if (name && *name)
+ {
+ char *p;
+
+ remove (name);
+ p = strrchr (name, '/');
+ if (p)
+ {
+ *p = 0;
+ rmdir (name);
+ *p = '/';
+ }
+ *name = 0;
+ }
+}
+
+static void
+cleanup (void)
+{
+ remove_socket (socket_name);
+ remove_socket (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)
+{
+ if (!pargs)
+ { /* reset mode */
+ opt.quiet = 0;
+ opt.verbose = 0;
+ opt.debug = 0;
+ opt.no_grab = 0;
+ opt.pinentry_program = NULL;
+ opt.pinentry_touch_file = NULL;
+ 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.max_passphrase_days = MAX_PASSPHRASE_DAYS;
+ opt.enable_passhrase_history = 0;
+ opt.ignore_cache_for_signing = 0;
+ opt.allow_mark_trusted = 0;
+ opt.disable_scdaemon = 0;
+ return 1;
+ }
+
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+
+ case oDebug: opt.debug |= pargs->r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugLevel: debug_level = pargs->r.ret_str; 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);
+ if (DBG_ASSUAN)
+ assuan_set_assuan_log_stream (log_get_stream ());
+ xfree (current_logfile);
+ current_logfile = xtrystrdup (pargs->r.ret_str);
+ }
+ break;
+
+ case oNoGrab: opt.no_grab = 1; break;
+
+ case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
+ case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
+ case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
+ case oDisableScdaemon: opt.disable_scdaemon = 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 oMaxPassphraseDays:
+ opt.max_passphrase_days = pargs->r.ret_ulong;
+ break;
+ case oEnablePassphraseHistory:
+ opt.enable_passhrase_history = 1;
+ break;
+
+ case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
+
+ case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
+
+ case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
+
+ default:
+ return 0; /* not handled */
+ }
+
+ return 1; /* handled */
+}
+
+
+/* The main entry point. */
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ char **orig_argv;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ const char *shell;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int default_config =1;
+ int greeting = 0;
+ int nogreeting = 0;
+ 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;
+ const char *env_file_name = NULL;
+ struct assuan_malloc_hooks malloc_hooks;
+
+ /* 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. */
+ startup_fd_list = get_all_open_fds ();
+#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", JNLIB_LOG_WITH_PREFIX|JNLIB_LOG_WITH_PID);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems ();
+
+
+ /* Libgcrypt requires us to register the threading model first.
+ Note that this will also do the pth_init. */
+ gcry_threads_pth.init = fixed_gcry_pth_init;
+ err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth);
+ if (err)
+ {
+ log_fatal ("can't register GNU Pth with Libgcrypt: %s\n",
+ gpg_strerror (err));
+ }
+
+
+ /* Check that the libraries are suitable. Do it here because
+ the option parsing may need services of the library. */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("%s is too old (need %s, have %s)\n"), "libgcrypt",
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ malloc_hooks.malloc = gcry_malloc;
+ malloc_hooks.realloc = gcry_realloc;
+ malloc_hooks.free = gcry_free;
+ assuan_set_malloc_hooks (&malloc_hooks);
+ assuan_set_assuan_log_prefix (log_get_prefix (NULL));
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ assuan_set_system_hooks (ASSUAN_SYSTEM_PTH);
+ assuan_sock_init ();
+
+ setup_libgcrypt_logging ();
+ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+
+ disable_core_dumps ();
+
+ /* Set default options. */
+ parse_rereadable_options (NULL, 0); /* Reset them to default values. */
+#ifdef USE_STANDARD_SOCKET
+ opt.use_standard_socket = 1; /* Under Windows we always use a standard
+ socket. */
+#endif
+
+ shell = getenv ("SHELL");
+ if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
+ csh_style = 1;
+
+ opt.homedir = default_homedir ();
+
+ /* 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 = 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= 1|(1<<6); /* do not remove the args, ignore version */
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ else if (pargs.r_opt == oOptions)
+ { /* yes there is one, so we do not try the default one, but
+ read the option file when it is encountered at the
+ commandline */
+ default_config = 0;
+ }
+ else if (pargs.r_opt == oNoOptions)
+ default_config = 0; /* --no-options */
+ else if (pargs.r_opt == oHomedir)
+ opt.homedir = pargs.r.ret_str;
+ }
+
+ /* Initialize the secure memory. */
+ gcry_control (GCRYCTL_INIT_SECMEM, 32768, 0);
+ maybe_setuid = 0;
+
+ /*
+ Now we are now working under our real uid
+ */
+
+ if (default_config)
+ configname = make_filename (opt.homedir, "gpg-agent.conf", NULL );
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ next_pass:
+ if (configname)
+ {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if( parse_debug )
+ log_info (_("NOTE: no default option file `%s'\n"),
+ configname );
+ /* Save the default conf file name so that
+ reread_configuration is able to test whether the
+ config file has been created in the meantime. */
+ xfree (config_filename);
+ config_filename = configname;
+ configname = NULL;
+ }
+ else
+ {
+ log_error (_("option file `%s': %s\n"),
+ configname, strerror(errno) );
+ exit(2);
+ }
+ xfree (configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname )
+ log_info (_("reading options from `%s'\n"), configname );
+ default_config = 0;
+ }
+
+ while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
+ {
+ 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 oOptions:
+ /* config files may not be nested (silently ignore them) */
+ if (!configfp)
+ {
+ xfree(configname);
+ configname = xstrdup(pargs.r.ret_str);
+ goto next_pass;
+ }
+ break;
+ case oNoGreeting: nogreeting = 1; break;
+ case oNoVerbose: opt.verbose = 0; break;
+ case oNoOptions: break; /* no-options */
+ case oHomedir: opt.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 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);
+ case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
+ break;
+
+ case oUseStandardSocket: opt.use_standard_socket = 1; break;
+ case oNoUseStandardSocket: opt.use_standard_socket = 0; 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: opt.ssh_support = 1; break;
+ case oWriteEnvFile:
+ if (pargs.r_type)
+ env_file_name = pargs.r.ret_str;
+ else
+ env_file_name = make_filename ("~/.gpg-agent-info", NULL);
+ break;
+
+ default : pargs.err = configfp? 1:2; break;
+ }
+ }
+ if (configfp)
+ {
+ fclose( configfp );
+ configfp = NULL;
+ /* Keep a copy of the name so that it can be read on SIGHUP. */
+ if (config_filename != configname)
+ {
+ xfree (config_filename);
+ config_filename = configname;
+ }
+ configname = NULL;
+ goto next_pass;
+ }
+
+ xfree (configname);
+ configname = NULL;
+ if (log_get_errorcount(0))
+ exit(2);
+ if (nogreeting )
+ greeting = 0;
+
+ if (greeting)
+ {
+ fprintf (stderr, "%s %s; %s\n",
+ strusage(11), strusage(13), strusage(14) );
+ fprintf (stderr, "%s\n", strusage(15) );
+ }
+#ifdef IS_DEVELOPMENT_VERSION
+ /* We don't want to print it here because gpg-agent is useful of its
+ own and quite matured. */
+ /*log_info ("NOTE: this is a development version!\n");*/
+#endif
+
+ set_debug ();
+
+ if (atexit (cleanup))
+ {
+ log_error ("atexit failed\n");
+ cleanup ();
+ exit (1);
+ }
+
+ initialize_module_call_pinentry ();
+ initialize_module_call_scd ();
+ initialize_module_trustlist ();
+
+ /* Try to create missing directories. */
+ create_directories ();
+
+ if (debug_wait && pipe_server)
+ {
+ 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)
+ agent_exit (!opt.use_standard_socket);
+ if (gpgconf_list == 2)
+ agent_exit (0);
+ if (gpgconf_list)
+ {
+ char *filename;
+ char *filename_esc;
+
+ /* List options and default values in the GPG Conf format. */
+ filename = make_filename (opt.homedir, "gpg-agent.conf", NULL );
+ filename_esc = percent_escape (filename, NULL);
+
+ printf ("gpgconf-gpg-agent.conf:%lu:\"%s\n",
+ GC_OPT_FLAG_DEFAULT, filename_esc);
+ xfree (filename);
+ xfree (filename_esc);
+
+ printf ("verbose:%lu:\n"
+ "quiet:%lu:\n"
+ "debug-level:%lu:\"none:\n"
+ "log-file:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
+ printf ("default-cache-ttl:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL );
+ printf ("default-cache-ttl-ssh:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH );
+ printf ("max-cache-ttl:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL );
+ printf ("max-cache-ttl-ssh:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH );
+ printf ("enforce-passphrase-constraints:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+ printf ("min-passphrase-len:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN );
+ printf ("min-passphrase-nonalpha:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
+ MIN_PASSPHRASE_NONALPHA);
+ printf ("check-passphrase-pattern:%lu:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
+ printf ("max-passphrase-days:%lu:%d:\n",
+ GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
+ MAX_PASSPHRASE_DAYS);
+ printf ("enable-passphrase-history:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+ printf ("no-grab:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+ printf ("ignore-cache-for-signing:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+ printf ("allow-mark-trusted:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+ printf ("disable-scdaemon:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+
+ agent_exit (0);
+ }
+
+ /* If this has been called without any options, we merely check
+ whether an agent is already running. We do this here so that we
+ don't clobber a logfile but print it directly to stderr. */
+ if (!pipe_server && !is_daemon)
+ {
+ log_set_prefix (NULL, JNLIB_LOG_WITH_PREFIX);
+ check_for_running_agent (0, 0);
+ agent_exit (0);
+ }
+
+#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
+
+ /* Now start with logging to a file if this is desired. */
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
+ |JNLIB_LOG_WITH_TIME
+ |JNLIB_LOG_WITH_PID));
+ current_logfile = xstrdup (logfile);
+ }
+ if (DBG_ASSUAN)
+ assuan_set_assuan_log_stream (log_get_stream ());
+
+ /* Make sure that we have a default ttyname. */
+ if (!default_ttyname && ttyname (1))
+ default_ttyname = xstrdup (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;
+
+ 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_daemon)
+ ; /* NOTREACHED */
+ else
+ { /* Regular server mode */
+ gnupg_fd_t fd;
+ gnupg_fd_t fd_ssh;
+ pid_t pid;
+
+ /* 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)
+ unsetenv ("DISPLAY");
+#endif
+
+
+ /* Create the sockets. */
+ socket_name = create_socket_name
+ ("S.gpg-agent", "/tmp/gpg-XXXXXX/S.gpg-agent");
+ if (opt.ssh_support)
+ socket_name_ssh = create_socket_name
+ ("S.gpg-agent.ssh", "/tmp/gpg-XXXXXX/S.gpg-agent.ssh");
+
+ fd = create_server_socket (socket_name, 0, &socket_nonce);
+ if (opt.ssh_support)
+ fd_ssh = create_server_socket (socket_name_ssh, 1, &socket_nonce_ssh);
+ else
+ fd_ssh = GNUPG_INVALID_FD;
+
+ /* 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
+ pid = getpid ();
+ printf ("set GPG_AGENT_INFO=%s;%lu;1\n", socket_name, (ulong)pid);
+#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, *infostr_ssh_sock, *infostr_ssh_pid;
+
+ /* Close the socket FD. */
+ close (fd);
+
+ /* Note that we used a standard fork so that Pth runs in
+ both the parent and the child. The pth_fork would
+ terminate Pth in the child but that is not the way we
+ want it. Thus we use a plain fork and terminate Pth here
+ in the parent. The pth_kill may or may not work reliable
+ but it should not harm to call it. Because Pth fiddles
+ with the signal mask 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. es_pth_kill is a wrapper around pth_kill to
+ take care not to use any Pth functions in the estream
+ code after Pth has been killed. */
+ if ( !es_pth_kill () )
+ log_error ("pth_kill failed in forked process\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*/
+
+ /* Create the info string: <name>:<pid>:<protocol_version> */
+ if (asprintf (&infostr, "GPG_AGENT_INFO=%s:%lu:1",
+ socket_name, (ulong)pid ) < 0)
+ {
+ log_error ("out of core\n");
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ if (opt.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_pid, "SSH_AGENT_PID=%u",
+ pid) < 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.ssh_support)
+ *socket_name_ssh = 0;
+
+ if (env_file_name)
+ {
+ FILE *fp;
+
+ fp = fopen (env_file_name, "w");
+ if (!fp)
+ log_error (_("error creating `%s': %s\n"),
+ env_file_name, strerror (errno));
+ else
+ {
+ fputs (infostr, fp);
+ putc ('\n', fp);
+ if (opt.ssh_support)
+ {
+ fputs (infostr_ssh_sock, fp);
+ putc ('\n', fp);
+ fputs (infostr_ssh_pid, fp);
+ putc ('\n', fp);
+ }
+ fclose (fp);
+ }
+ }
+
+
+ if (argc)
+ { /* Run the program given on the commandline. */
+ if (putenv (infostr))
+ {
+ log_error ("failed to set environment: %s\n",
+ strerror (errno) );
+ kill (pid, SIGTERM );
+ exit (1);
+ }
+ if (opt.ssh_support && putenv (infostr_ssh_sock))
+ {
+ log_error ("failed to set environment: %s\n",
+ strerror (errno) );
+ kill (pid, SIGTERM );
+ exit (1);
+ }
+ if (opt.ssh_support && putenv (infostr_ssh_pid))
+ {
+ 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)
+ {
+ *strchr (infostr, '=') = ' ';
+ printf ("setenv %s;\n", infostr);
+ if (opt.ssh_support)
+ {
+ *strchr (infostr_ssh_sock, '=') = ' ';
+ printf ("setenv %s;\n", infostr_ssh_sock);
+ *strchr (infostr_ssh_pid, '=') = ' ';
+ printf ("setenv %s;\n", infostr_ssh_pid);
+ }
+ }
+ else
+ {
+ printf ( "%s; export GPG_AGENT_INFO;\n", infostr);
+ if (opt.ssh_support)
+ {
+ printf ("%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock);
+ printf ("%s; export SSH_AGENT_PID;\n", infostr_ssh_pid);
+ }
+ }
+ xfree (infostr);
+ if (opt.ssh_support)
+ {
+ xfree (infostr_ssh_sock);
+ xfree (infostr_ssh_pid);
+ }
+ exit (0);
+ }
+ /*NOTREACHED*/
+ } /* End parent */
+
+ /*
+ This is the child
+ */
+
+ /* 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 | JNLIB_LOG_RUN_DETACHED);
+ opt.running_detached = 1;
+ }
+
+ if (chdir("/"))
+ {
+ log_error ("chdir to / failed: %s\n", strerror (errno));
+ exit (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*/
+
+ log_info ("%s %s started\n", strusage(11), strusage(13) );
+ handle_connections (fd, opt.ssh_support ? fd_ssh : GNUPG_INVALID_FD);
+ assuan_sock_close (fd);
+ }
+
+ return 0;
+}
+
+
+void
+agent_exit (int rc)
+{
+ /*FIXME: update_random_seed_file();*/
+#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);
+}
+
+
+static void
+agent_init_default_ctrl (ctrl_t ctrl)
+{
+ /* 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;
+
+}
+
+
+static void
+agent_deinit_default_ctrl (ctrl_t ctrl)
+{
+ session_env_release (ctrl->session_env);
+
+ if (ctrl->lc_ctype)
+ xfree (ctrl->lc_ctype);
+ if (ctrl->lc_messages)
+ xfree (ctrl->lc_messages);
+}
+
+
+/* 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;
+ FILE *fp;
+ unsigned int configlineno = 0;
+ int dummy;
+
+ if (!config_filename)
+ return; /* No config file. */
+
+ fp = fopen (config_filename, "r");
+ if (!fp)
+ {
+ log_info (_("option file `%s': %s\n"),
+ config_filename, strerror(errno) );
+ return;
+ }
+
+ parse_rereadable_options (NULL, 1); /* Start from the default values. */
+
+ memset (&pargs, 0, sizeof pargs);
+ dummy = 0;
+ pargs.argc = &dummy;
+ pargs.flags = 1; /* do not remove the args */
+ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
+ {
+ if (pargs.r_opt < -1)
+ pargs.err = 1; /* Print a warning. */
+ else /* Try to parse this option - ignore unchangeable ones. */
+ parse_rereadable_options (&pargs, 1);
+ }
+ fclose (fp);
+ 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;
+}
+
+
+/* Under W32, this function returns the handle of the scdaemon
+ notification event. Calling it the first time creates that
+ event. */
+#ifdef HAVE_W32_SYSTEM
+void *
+get_agent_scd_notify_event (void)
+{
+ static HANDLE the_event;
+
+ if (!the_event)
+ {
+ HANDLE h, h2;
+ SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
+
+ /* We need to use manual reset evet 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 syncronize for scd notify event failed: %s\n",
+ w32_strerror (-1) );
+ CloseHandle (h);
+ }
+ else
+ {
+ CloseHandle (h);
+ the_event = h2;
+ }
+ }
+
+ log_debug ("returning notify handle %p\n", the_event);
+ return the_event;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+
+/* Create a name for the socket. With USE_STANDARD_SOCKET given as
+ true using STANDARD_NAME in the home directory or if given as
+ false from the mkdir type name TEMPLATE. In the latter case a
+ unique name in a unique new directory will be created. In both
+ cases 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, char *template)
+{
+ char *name, *p;
+
+ if (opt.use_standard_socket)
+ name = make_filename (opt.homedir, standard_name, NULL);
+ else
+ {
+ name = xstrdup (template);
+ p = strrchr (name, '/');
+ if (!p)
+ BUG ();
+ *p = 0;
+ if (!mkdtemp (name))
+ {
+ log_error (_("can't create directory `%s': %s\n"),
+ name, strerror (errno));
+ agent_exit (2);
+ }
+ *p = '/';
+ }
+
+ if (strchr (name, PATHSEP_C))
+ {
+ log_error (("`%s' are not allowed in the socket name\n"), PATHSEP_S);
+ agent_exit (2);
+ }
+ if (strlen (name) + 1 >= DIMof (struct sockaddr_un, sun_path) )
+ {
+ log_error (_("name of socket too long\n"));
+ 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. Not that this
+ function needs to be used for the regular socket first and only
+ then for the ssh socket. */
+static gnupg_fd_t
+create_server_socket (char *name, int is_ssh, assuan_sock_nonce_t *nonce)
+{
+ struct sockaddr_un *serv_addr;
+ socklen_t len;
+ gnupg_fd_t fd;
+ int rc;
+
+ fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == ASSUAN_INVALID_FD)
+ {
+ log_error (_("can't create socket: %s\n"), strerror (errno));
+ agent_exit (2);
+ }
+
+ serv_addr = xmalloc (sizeof (*serv_addr));
+ memset (serv_addr, 0, sizeof *serv_addr);
+ serv_addr->sun_family = AF_UNIX;
+ if (strlen (name) + 1 >= sizeof (serv_addr->sun_path))
+ {
+ log_error (_("socket name `%s' is too long\n"), name);
+ agent_exit (2);
+ }
+ strcpy (serv_addr->sun_path, name);
+ len = SUN_LEN (serv_addr);
+ rc = assuan_sock_bind (fd, (struct sockaddr*) serv_addr, len);
+ if (opt.use_standard_socket && rc == -1 && errno == EADDRINUSE)
+ {
+ /* Check whether a gpg-agent is already running on the standard
+ socket. We do this test only if this is not the ssh socket.
+ For ssh we assume that a test for gpg-agent has already been
+ done and reuse the requested ssh 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 (!is_ssh && !check_for_running_agent (1, 1))
+ {
+ 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);
+ agent_exit (2);
+ }
+ remove (name);
+ rc = assuan_sock_bind (fd, (struct sockaddr*) serv_addr, len);
+ }
+ if (rc != -1
+ && (rc=assuan_sock_get_nonce ((struct sockaddr*)serv_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"),
+ serv_addr->sun_path,
+ gpg_strerror (gpg_error_from_errno (errno)));
+
+ assuan_sock_close (fd);
+ if (opt.use_standard_socket)
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ agent_exit (2);
+ }
+
+ if (listen (FD2INT(fd), 5 ) == -1)
+ {
+ log_error (_("listen() failed: %s\n"), strerror (errno));
+ assuan_sock_close (fd);
+ agent_exit (2);
+ }
+
+ if (opt.verbose)
+ log_info (_("listening on socket `%s'\n"), serv_addr->sun_path);
+
+ 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 (stat (fname, &statbuf) && errno == ENOENT)
+ {
+#ifdef HAVE_W32_SYSTEM /*FIXME: Setup proper permissions. */
+ if (!CreateDirectory (fname, NULL))
+ log_error (_("can't create directory `%s': %s\n"),
+ fname, w32_strerror (-1) );
+#else
+ if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR ))
+ log_error (_("can't create directory `%s': %s\n"),
+ fname, strerror (errno) );
+#endif
+ else if (!opt.quiet)
+ log_info (_("directory `%s' created\n"), fname);
+ }
+ 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 (opt.homedir, NULL);
+ if ( 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
+ )
+ {
+#ifdef HAVE_W32_SYSTEM
+ if (!CreateDirectory (home, NULL))
+ log_error (_("can't create directory `%s': %s\n"),
+ home, w32_strerror (-1) );
+#else
+ if (mkdir (home, S_IRUSR|S_IWUSR|S_IXUSR ))
+ log_error (_("can't create directory `%s': %s\n"),
+ home, strerror (errno) );
+#endif
+ 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;
+
+ 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;
+ if (!opt.quiet)
+ {
+ 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 every minute. */
+ if (last_minute + 60 <= time (NULL))
+ {
+ check_own_socket ();
+ last_minute = time (NULL);
+ }
+
+}
+
+
+/* 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 ();
+}
+
+
+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 ();
+}
+
+
+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");
+ 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 %ld running threads\n",
+ pth_ctrl( PTH_CTRL_GETTHREADS ));
+ 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);
+ }
+}
+
+
+/* Check the nonce on a new connection. This is a NOP unless we 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;
+}
+
+
+/* This is the standard connection thread's main function. */
+static void *
+start_connection_thread (void *arg)
+{
+ ctrl_t ctrl = arg;
+
+ if (check_nonce (ctrl, &socket_nonce))
+ return NULL;
+
+ agent_init_default_ctrl (ctrl);
+ if (opt.verbose)
+ log_info (_("handler 0x%lx for fd %d started\n"),
+ pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
+
+ start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
+ if (opt.verbose)
+ log_info (_("handler 0x%lx for fd %d terminated\n"),
+ pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
+
+ agent_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ return NULL;
+}
+
+
+/* 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;
+
+ agent_init_default_ctrl (ctrl);
+ if (opt.verbose)
+ log_info (_("ssh handler 0x%lx for fd %d started\n"),
+ pth_thread_id (), 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"),
+ pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
+
+ agent_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ 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_ssh)
+{
+ pth_attr_t tattr;
+ pth_event_t ev, time_ev;
+ sigset_t sigs;
+ int signo;
+ struct sockaddr_un paddr;
+ socklen_t plen;
+ fd_set fdset, read_fdset;
+ int ret;
+ gnupg_fd_t fd;
+ int nfd;
+
+ tattr = pth_attr_new();
+ pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+ pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+
+#ifndef HAVE_W32_SYSTEM /* fixme */
+ /* Make sure that the signals we are going to handle are not blocked
+ and create an event object for them. We also set the default
+ action to ignore because we use an Pth event to get notified
+ about signals. This avoids that the default action is taken in
+ case soemthing goes wrong within Pth. The problem might also be
+ a Pth bug. */
+ sigemptyset (&sigs );
+ {
+ static const int mysigs[] = { SIGHUP, SIGUSR1, SIGUSR2, SIGINT, SIGTERM };
+ struct sigaction sa;
+ int i;
+
+ for (i=0; i < DIM (mysigs); i++)
+ {
+ sigemptyset (&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ sigaction (mysigs[i], &sa, NULL);
+
+ sigaddset (&sigs, mysigs[i]);
+ }
+ }
+
+ pth_sigmask (SIG_UNBLOCK, &sigs, NULL);
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+#else
+# ifdef PTH_EVENT_HANDLE
+ sigs = 0;
+ ev = pth_event (PTH_EVENT_HANDLE, get_agent_scd_notify_event ());
+ signo = 0;
+# else
+ /* Use a dummy event. */
+ sigs = 0;
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+# endif
+#endif
+ time_ev = NULL;
+
+ /* 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_ssh != GNUPG_INVALID_FD)
+ {
+ FD_SET ( FD2INT(listen_fd_ssh), &fdset);
+ if (FD2INT (listen_fd_ssh) > nfd)
+ nfd = FD2INT (listen_fd_ssh);
+ }
+
+ for (;;)
+ {
+ /* Make sure that our signals are not blocked. */
+ pth_sigmask (SIG_UNBLOCK, &sigs, NULL);
+
+ /* Shutdown test. */
+ if (shutdown_pending)
+ {
+ if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1)
+ break; /* ready */
+
+ /* Do not accept new connections but keep on running the
+ loop to cope with the timer events. */
+ FD_ZERO (&fdset);
+ }
+
+ /* Create a timeout event if needed. To help with power saving
+ we syncronize the ticks to the next full second. */
+ if (!time_ev)
+ {
+ pth_time_t nexttick;
+
+ nexttick = pth_timeout (TIMERTICK_INTERVAL, 0);
+ if (nexttick.tv_usec > 10) /* Use a 10 usec threshhold. */
+ {
+ nexttick.tv_sec++;
+ nexttick.tv_usec = 0;
+ }
+ time_ev = pth_event (PTH_EVENT_TIME, nexttick);
+ }
+
+ /* 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;
+
+ if (time_ev)
+ pth_event_concat (ev, time_ev, NULL);
+ ret = pth_select_ev (nfd+1, &read_fdset, NULL, NULL, NULL, ev);
+ if (time_ev)
+ pth_event_isolate (time_ev);
+
+ if (ret == -1)
+ {
+ if (pth_event_occurred (ev)
+ || (time_ev && pth_event_occurred (time_ev)))
+ {
+ if (pth_event_occurred (ev))
+ {
+#if defined(HAVE_W32_SYSTEM) && defined(PTH_EVENT_HANDLE)
+ agent_sigusr2_action ();
+#else
+ handle_signal (signo);
+#endif
+ }
+ if (time_ev && pth_event_occurred (time_ev))
+ {
+ pth_event_free (time_ev, PTH_FREE_ALL);
+ time_ev = NULL;
+ handle_tick ();
+ }
+ continue;
+ }
+ log_error (_("pth_select failed: %s - waiting 1s\n"),
+ strerror (errno));
+ pth_sleep (1);
+ continue;
+ }
+
+ if (pth_event_occurred (ev))
+ {
+#if defined(HAVE_W32_SYSTEM) && defined(PTH_EVENT_HANDLE)
+ agent_sigusr2_action ();
+#else
+ handle_signal (signo);
+#endif
+ }
+
+ if (time_ev && pth_event_occurred (time_ev))
+ {
+ pth_event_free (time_ev, PTH_FREE_ALL);
+ time_ev = NULL;
+ handle_tick ();
+ }
+
+
+ /* We now might create new threads and because we don't want any
+ signals (as we are handling them here) to be delivered to a
+ new thread. Thus we need to block those signals. */
+ pth_sigmask (SIG_BLOCK, &sigs, NULL);
+
+ if (!shutdown_pending && FD_ISSET (FD2INT (listen_fd), &read_fdset))
+ {
+ ctrl_t ctrl;
+
+ plen = sizeof paddr;
+ fd = INT2FD (pth_accept (FD2INT(listen_fd),
+ (struct sockaddr *)&paddr, &plen));
+ if (fd == GNUPG_INVALID_FD)
+ {
+ log_error ("accept failed: %s\n", strerror (errno));
+ }
+ else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ assuan_sock_close (fd);
+ }
+ else if ( !(ctrl->session_env = session_env_new ()) )
+ {
+ log_error ("error allocating session environment block: %s\n",
+ strerror (errno) );
+ xfree (ctrl);
+ assuan_sock_close (fd);
+ }
+ else
+ {
+ char threadname[50];
+
+ snprintf (threadname, sizeof threadname-1,
+ "conn fd=%d (gpg)", FD2INT(fd));
+ threadname[sizeof threadname -1] = 0;
+ pth_attr_set (tattr, PTH_ATTR_NAME, threadname);
+ ctrl->thread_startup.fd = fd;
+ if (!pth_spawn (tattr, start_connection_thread, ctrl))
+ {
+ log_error ("error spawning connection handler: %s\n",
+ strerror (errno) );
+ assuan_sock_close (fd);
+ xfree (ctrl);
+ }
+ }
+ fd = GNUPG_INVALID_FD;
+ }
+
+ if (!shutdown_pending && listen_fd_ssh != GNUPG_INVALID_FD
+ && FD_ISSET ( FD2INT (listen_fd_ssh), &read_fdset))
+ {
+ ctrl_t ctrl;
+
+ plen = sizeof paddr;
+ fd = INT2FD(pth_accept (FD2INT(listen_fd_ssh),
+ (struct sockaddr *)&paddr, &plen));
+ if (fd == GNUPG_INVALID_FD)
+ {
+ log_error ("accept failed for ssh: %s\n", strerror (errno));
+ }
+ else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ assuan_sock_close (fd);
+ }
+ else if ( !(ctrl->session_env = session_env_new ()) )
+ {
+ log_error ("error allocating session environment block: %s\n",
+ strerror (errno) );
+ xfree (ctrl);
+ assuan_sock_close (fd);
+ }
+ else
+ {
+ char threadname[50];
+
+ agent_init_default_ctrl (ctrl);
+ snprintf (threadname, sizeof threadname-1,
+ "conn fd=%d (ssh)", FD2INT(fd));
+ threadname[sizeof threadname -1] = 0;
+ pth_attr_set (tattr, PTH_ATTR_NAME, threadname);
+ ctrl->thread_startup.fd = fd;
+ if (!pth_spawn (tattr, start_connection_thread_ssh, ctrl) )
+ {
+ log_error ("error spawning ssh connection handler: %s\n",
+ strerror (errno) );
+ assuan_sock_close (fd);
+ xfree (ctrl);
+ }
+ }
+ fd = GNUPG_INVALID_FD;
+ }
+ }
+
+ pth_event_free (ev, PTH_FREE_ALL);
+ if (time_ev)
+ pth_event_free (time_ev, PTH_FREE_ALL);
+ cleanup ();
+ log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
+}
+
+
+
+/* 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;
+ }
+
+ 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. Setting the name to empty does this. */
+ if (socket_name)
+ *socket_name = 0;
+ if (socket_name_ssh)
+ *socket_name_ssh = 0;
+ 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 woul linger around without any real taks. Thus we
+ better check once in a while whether we are really needed. */
+static void
+check_own_socket (void)
+{
+ char *sockname;
+ pth_attr_t tattr;
+
+ if (!opt.use_standard_socket)
+ return; /* This check makes only sense in standard socket mode. */
+
+ if (check_own_socket_running || shutdown_pending)
+ return; /* Still running or already shutting down. */
+
+ sockname = make_filename (opt.homedir, "S.gpg-agent", NULL);
+ if (!sockname)
+ return; /* Out of memory. */
+
+ tattr = pth_attr_new();
+ pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+ pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+ pth_attr_set (tattr, PTH_ATTR_NAME, "check-own-socket");
+
+ if (!pth_spawn (tattr, check_own_socket_thread, sockname))
+ log_error ("error spawning check_own_socket_thread: %s\n",
+ strerror (errno) );
+ pth_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. Usually
+ started with MODE 0. Returns 0 if the agent is running. */
+static int
+check_for_running_agent (int silent, int mode)
+{
+ int rc;
+ char *infostr, *p;
+ assuan_context_t ctx = NULL;
+ int prot, pid;
+
+ if (!mode)
+ {
+ infostr = getenv ("GPG_AGENT_INFO");
+ if (!infostr || !*infostr)
+ {
+ if (!check_for_running_agent (silent, 1))
+ return 0; /* Okay, its running on the standard socket. */
+ if (!silent)
+ log_error (_("no gpg-agent running in this session\n"));
+ return -1;
+ }
+
+ infostr = xstrdup (infostr);
+ if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr)
+ {
+ xfree (infostr);
+ if (!check_for_running_agent (silent, 1))
+ return 0; /* Okay, its running on the standard socket. */
+ if (!silent)
+ log_error (_("malformed GPG_AGENT_INFO environment variable\n"));
+ return -1;
+ }
+
+ *p++ = 0;
+ pid = atoi (p);
+ while (*p && *p != PATHSEP_C)
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if (prot != 1)
+ {
+ xfree (infostr);
+ if (!silent)
+ log_error (_("gpg-agent protocol version %d is not supported\n"),
+ prot);
+ if (!check_for_running_agent (silent, 1))
+ return 0; /* Okay, its running on the standard socket. */
+ return -1;
+ }
+ }
+ else /* MODE != 0 */
+ {
+ infostr = make_filename (opt.homedir, "S.gpg-agent", NULL);
+ pid = (pid_t)(-1);
+ }
+
+ rc = assuan_new (&ctx);
+ if (! rc)
+ rc = assuan_socket_connect (ctx, infostr, pid, 0);
+ xfree (infostr);
+ if (rc)
+ {
+ if (!mode && !check_for_running_agent (silent, 1))
+ return 0; /* Okay, its running on the standard socket. */
+
+ if (!mode && !silent)
+ log_error ("can't connect to the agent: %s\n", gpg_strerror (rc));
+
+ 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/learncard.c b/agent/learncard.c
new file mode 100644
index 0000000..77f2bb0
--- /dev/null
+++ b/agent/learncard.c
@@ -0,0 +1,472 @@
+/* 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 <http://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 coccured */
+
+ 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 coccured */
+
+ 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 coccured */
+
+ 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 all new
+ certificates are send back via Assuan. */
+int
+agent_handle_learn (ctrl_t ctrl, void *assuan_context)
+{
+ int rc;
+
+ struct kpinfo_cb_parm_s parm;
+ struct certinfo_cb_parm_s cparm;
+ struct sinfo_cb_parm_s sparm;
+ 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;
+
+ /* Check whether a card is present and get the serial number */
+ rc = agent_card_serialno (ctrl, &serialno);
+ if (rc)
+ goto leave;
+
+ /* 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;
+ }
+
+ log_info ("card has S/N: %s\n", serialno);
+
+ /* Pass on all the collected status information. */
+ if (assuan_context)
+ {
+ for (sitem = sparm.info; sitem; sitem = sitem->next)
+ {
+ assuan_write_status (assuan_context, sitem->keyword, sitem->data);
+ }
+ }
+
+ /* 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)
+ {
+ 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, *shdkey;
+ size_t n;
+
+ 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 (!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;
+ }
+
+ {
+ unsigned char *shadow_info = make_shadow_info (serialno, item->id);
+ if (!shadow_info)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ xfree (pubkey);
+ goto leave;
+ }
+ rc = agent_shadow_key (pubkey, shadow_info, &shdkey);
+ xfree (shadow_info);
+ }
+ xfree (pubkey);
+ if (rc)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ n = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+ assert (n);
+
+ rc = agent_write_private_key (grip, shdkey, n, 0);
+ xfree (shdkey);
+ if (rc)
+ {
+ log_error ("error writing key: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("stored\n");
+
+ if (assuan_context)
+ {
+ 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:
+ xfree (serialno);
+ release_keypair_info (parm.info);
+ release_certinfo (cparm.info);
+ release_sinfo (sparm.info);
+ return rc;
+}
+
+
diff --git a/agent/minip12.c b/agent/minip12.c
new file mode 100644
index 0000000..2471717
--- /dev/null
+++ b/agent/minip12.c
@@ -0,0 +1,2360 @@
+/* minip12.c - A minimal pkcs-12 implementation.
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gcrypt.h>
+#include <errno.h>
+
+#ifdef TEST
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "../jnlib/logging.h"
+#include "../jnlib/utf8conv.h"
+#include "minip12.h"
+
+#ifndef DIM
+#define DIM(v) (sizeof(v)/sizeof((v)[0]))
+#endif
+
+
+enum
+{
+ UNIVERSAL = 0,
+ APPLICATION = 1,
+ ASNCONTEXT = 2,
+ PRIVATE = 3
+};
+
+
+enum
+{
+ TAG_NONE = 0,
+ TAG_BOOLEAN = 1,
+ TAG_INTEGER = 2,
+ TAG_BIT_STRING = 3,
+ TAG_OCTET_STRING = 4,
+ TAG_NULL = 5,
+ TAG_OBJECT_ID = 6,
+ TAG_OBJECT_DESCRIPTOR = 7,
+ TAG_EXTERNAL = 8,
+ TAG_REAL = 9,
+ TAG_ENUMERATED = 10,
+ TAG_EMBEDDED_PDV = 11,
+ TAG_UTF8_STRING = 12,
+ TAG_REALTIVE_OID = 13,
+ TAG_SEQUENCE = 16,
+ TAG_SET = 17,
+ TAG_NUMERIC_STRING = 18,
+ TAG_PRINTABLE_STRING = 19,
+ TAG_TELETEX_STRING = 20,
+ TAG_VIDEOTEX_STRING = 21,
+ TAG_IA5_STRING = 22,
+ TAG_UTC_TIME = 23,
+ TAG_GENERALIZED_TIME = 24,
+ TAG_GRAPHIC_STRING = 25,
+ TAG_VISIBLE_STRING = 26,
+ TAG_GENERAL_STRING = 27,
+ TAG_UNIVERSAL_STRING = 28,
+ TAG_CHARACTER_STRING = 29,
+ TAG_BMP_STRING = 30
+};
+
+
+static unsigned char const oid_data[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 };
+static unsigned char const oid_encryptedData[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 };
+static unsigned char const oid_pkcs_12_keyBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 };
+static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 };
+static unsigned char const oid_pkcs_12_CertBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 };
+static unsigned char const oid_pkcs_12_CrlBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 };
+
+static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 };
+static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 };
+static unsigned char const oid_x509Certificate_for_pkcs_12[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 };
+
+
+static unsigned char const oid_rsaEncryption[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
+
+
+static unsigned char const data_3desiter2048[30] = {
+ 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E,
+ 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 };
+#define DATA_3DESITER2048_SALT_OFF 18
+
+static unsigned char const data_rc2iter2048[30] = {
+ 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E,
+ 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 };
+#define DATA_RC2ITER2048_SALT_OFF 18
+
+static unsigned char const data_mactemplate[51] = {
+ 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
+ 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04,
+ 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02,
+ 0x02, 0x08, 0x00 };
+#define DATA_MACTEMPLATE_MAC_OFF 17
+#define DATA_MACTEMPLATE_SALT_OFF 39
+
+static unsigned char const data_attrtemplate[106] = {
+ 0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31,
+ 0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00,
+ 0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00,
+ 0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00,
+ 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
+ 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
+ 0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16,
+ 0x04, 0x14 }; /* Need to append SHA-1 digest. */
+#define DATA_ATTRTEMPLATE_KEYID_OFF 73
+
+struct buffer_s
+{
+ unsigned char *buffer;
+ size_t length;
+};
+
+
+struct tag_info
+{
+ int class;
+ int is_constructed;
+ unsigned long tag;
+ unsigned long length; /* length part of the TLV */
+ int nhdr;
+ int ndef; /* It is an indefinite length */
+};
+
+
+/* Parse the buffer at the address BUFFER which is of SIZE and return
+ the tag and the length part from the TLV triplet. Update BUFFER
+ and SIZE on success. Checks that the encoded length does not
+ exhaust the length of the provided buffer. */
+static int
+parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti)
+{
+ int c;
+ unsigned long tag;
+ const unsigned char *buf = *buffer;
+ size_t length = *size;
+
+ ti->length = 0;
+ ti->ndef = 0;
+ ti->nhdr = 0;
+
+ /* Get the tag */
+ if (!length)
+ return -1; /* premature eof */
+ c = *buf++; length--;
+ ti->nhdr++;
+
+ ti->class = (c & 0xc0) >> 6;
+ ti->is_constructed = !!(c & 0x20);
+ tag = c & 0x1f;
+
+ if (tag == 0x1f)
+ {
+ tag = 0;
+ do
+ {
+ tag <<= 7;
+ if (!length)
+ return -1; /* premature eof */
+ c = *buf++; length--;
+ ti->nhdr++;
+ tag |= c & 0x7f;
+ }
+ while (c & 0x80);
+ }
+ ti->tag = tag;
+
+ /* Get the length */
+ if (!length)
+ return -1; /* prematureeof */
+ c = *buf++; length--;
+ ti->nhdr++;
+
+ if ( !(c & 0x80) )
+ ti->length = c;
+ else if (c == 0x80)
+ ti->ndef = 1;
+ else if (c == 0xff)
+ return -1; /* forbidden length value */
+ else
+ {
+ unsigned long len = 0;
+ int count = c & 0x7f;
+
+ for (; count; count--)
+ {
+ len <<= 8;
+ if (!length)
+ return -1; /* premature_eof */
+ c = *buf++; length--;
+ ti->nhdr++;
+ len |= c & 0xff;
+ }
+ ti->length = len;
+ }
+
+ if (ti->class == UNIVERSAL && !ti->tag)
+ ti->length = 0;
+
+ if (ti->length > length)
+ return -1; /* data larger than buffer. */
+
+ *buffer = buf;
+ *size = length;
+ return 0;
+}
+
+
+/* Given an ASN.1 chunk of a structure like:
+
+ 24 NDEF: OCTET STRING -- This is not passed to us
+ 04 1: OCTET STRING -- INPUT point s to here
+ : 30
+ 04 1: OCTET STRING
+ : 80
+ [...]
+ 04 2: OCTET STRING
+ : 00 00
+ : } -- This denotes a Null tag and are the last
+ -- two bytes in INPUT.
+
+ Create a new buffer with the content of that octet string. INPUT
+ is the orginal buffer with a length as stored at LENGTH. Returns
+ NULL on error or a new malloced buffer with the length of this new
+ buffer stored at LENGTH and the number of bytes parsed from input
+ are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is
+ allowed to be passed as NULL if the caller is not interested in
+ this value. */
+static unsigned char *
+cram_octet_string (const unsigned char *input, size_t *length,
+ size_t *input_consumed)
+{
+ const unsigned char *s = input;
+ size_t n = *length;
+ unsigned char *output, *d;
+ struct tag_info ti;
+
+ /* Allocate output buf. We know that it won't be longer than the
+ input buffer. */
+ d = output = gcry_malloc (n);
+ if (!output)
+ goto bailout;
+
+ for (;;)
+ {
+ if (parse_tag (&s, &n, &ti))
+ goto bailout;
+ if (ti.class == UNIVERSAL && ti.tag == TAG_OCTET_STRING
+ && !ti.ndef && !ti.is_constructed)
+ {
+ memcpy (d, s, ti.length);
+ s += ti.length;
+ d += ti.length;
+ n -= ti.length;
+ }
+ else if (ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)
+ break; /* Ready */
+ else
+ goto bailout;
+ }
+
+
+ *length = d - output;
+ if (input_consumed)
+ *input_consumed += s - input;
+ return output;
+
+ bailout:
+ if (input_consumed)
+ *input_consumed += s - input;
+ gcry_free (output);
+ return NULL;
+}
+
+
+
+static int
+string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw,
+ int req_keylen, unsigned char *keybuf)
+{
+ int rc, i, j;
+ gcry_md_hd_t md;
+ gcry_mpi_t num_b1 = NULL;
+ int pwlen;
+ unsigned char hash[20], buf_b[64], buf_i[128], *p;
+ size_t cur_keylen;
+ size_t n;
+
+ cur_keylen = 0;
+ pwlen = strlen (pw);
+ if (pwlen > 63/2)
+ {
+ log_error ("password too long\n");
+ return -1;
+ }
+
+ if (saltlen < 8)
+ {
+ log_error ("salt too short\n");
+ return -1;
+ }
+
+ /* Store salt and password in BUF_I */
+ p = buf_i;
+ for(i=0; i < 64; i++)
+ *p++ = salt [i%saltlen];
+ for(i=j=0; i < 64; i += 2)
+ {
+ *p++ = 0;
+ *p++ = pw[j];
+ if (++j > pwlen) /* Note, that we include the trailing zero */
+ j = 0;
+ }
+
+ for (;;)
+ {
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (rc)
+ {
+ log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ for(i=0; i < 64; i++)
+ gcry_md_putc (md, id);
+ gcry_md_write (md, buf_i, 128);
+ memcpy (hash, gcry_md_read (md, 0), 20);
+ gcry_md_close (md);
+ for (i=1; i < iter; i++)
+ gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20);
+
+ for (i=0; i < 20 && cur_keylen < req_keylen; i++)
+ keybuf[cur_keylen++] = hash[i];
+ if (cur_keylen == req_keylen)
+ {
+ gcry_mpi_release (num_b1);
+ return 0; /* ready */
+ }
+
+ /* need more bytes. */
+ for(i=0; i < 64; i++)
+ buf_b[i] = hash[i % 20];
+ rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n);
+ if (rc)
+ {
+ log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+ gcry_mpi_add_ui (num_b1, num_b1, 1);
+ for (i=0; i < 128; i += 64)
+ {
+ gcry_mpi_t num_ij;
+
+ rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n);
+ if (rc)
+ {
+ log_error ( "gcry_mpi_scan failed: %s\n",
+ gpg_strerror (rc));
+ return -1;
+ }
+ gcry_mpi_add (num_ij, num_ij, num_b1);
+ gcry_mpi_clear_highbit (num_ij, 64*8);
+ rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij);
+ if (rc)
+ {
+ log_error ( "gcry_mpi_print failed: %s\n",
+ gpg_strerror (rc));
+ return -1;
+ }
+ gcry_mpi_release (num_ij);
+ }
+ }
+}
+
+
+static int
+set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter,
+ const char *pw, int keybytes)
+{
+ unsigned char keybuf[24];
+ int rc;
+
+ assert (keybytes == 5 || keybytes == 24);
+ if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf))
+ return -1;
+ rc = gcry_cipher_setkey (chd, keybuf, keybytes);
+ if (rc)
+ {
+ log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+
+ if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf))
+ return -1;
+ rc = gcry_cipher_setiv (chd, keybuf, 8);
+ if (rc)
+ {
+ log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+ return 0;
+}
+
+
+static void
+crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen,
+ int iter, const char *pw, int cipher_algo, int encrypt)
+{
+ gcry_cipher_hd_t chd;
+ int rc;
+
+ rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0);
+ if (rc)
+ {
+ log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc));
+ wipememory (buffer, length);
+ return;
+ }
+ if (set_key_iv (chd, salt, saltlen, iter, pw,
+ cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24))
+ {
+ wipememory (buffer, length);
+ goto leave;
+ }
+
+ rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0)
+ : gcry_cipher_decrypt (chd, buffer, length, NULL, 0);
+
+ if (rc)
+ {
+ wipememory (buffer, length);
+ log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ leave:
+ gcry_cipher_close (chd);
+}
+
+
+/* Decrypt a block of data and try several encodings of the key.
+ CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is
+ a buffer of the same size to receive the decryption result. SALT,
+ SALTLEN, ITER and PW are the information required for decryption
+ and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a
+ function called with the plaintext and used to check whether the
+ decryption succeeded; i.e. that a correct passphrase has been
+ given. That function shall return true if the decryption has likely
+ succeeded. */
+static void
+decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length,
+ char *salt, size_t saltlen,
+ int iter, const char *pw, int cipher_algo,
+ int (*check_fnc) (const void *, size_t))
+{
+ static const char * const charsets[] = {
+ "", /* No conversion - use the UTF-8 passphrase direct. */
+ "ISO-8859-1",
+ "ISO-8859-15",
+ "ISO-8859-2",
+ "ISO-8859-3",
+ "ISO-8859-4",
+ "ISO-8859-5",
+ "ISO-8859-6",
+ "ISO-8859-7",
+ "ISO-8859-8",
+ "ISO-8859-9",
+ "KOI8-R",
+ "IBM437",
+ "IBM850",
+ "EUC-JP",
+ "BIG5",
+ NULL
+ };
+ int charsetidx = 0;
+ char *convertedpw = NULL; /* Malloced and converted password or NULL. */
+ size_t convertedpwsize = 0; /* Allocated length. */
+
+ for (charsetidx=0; charsets[charsetidx]; charsetidx++)
+ {
+ if (*charsets[charsetidx])
+ {
+ jnlib_iconv_t cd;
+ const char *inptr;
+ char *outptr;
+ size_t inbytes, outbytes;
+
+ if (!convertedpw)
+ {
+ /* We assume one byte encodings. Thus we can allocate
+ the buffer of the same size as the original
+ passphrase; the result will actually be shorter
+ then. */
+ convertedpwsize = strlen (pw) + 1;
+ convertedpw = gcry_malloc_secure (convertedpwsize);
+ if (!convertedpw)
+ {
+ log_info ("out of secure memory while"
+ " converting passphrase\n");
+ break; /* Give up. */
+ }
+ }
+
+ cd = jnlib_iconv_open (charsets[charsetidx], "utf-8");
+ if (cd == (jnlib_iconv_t)(-1))
+ continue;
+
+ inptr = pw;
+ inbytes = strlen (pw);
+ outptr = convertedpw;
+ outbytes = convertedpwsize - 1;
+ if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
+ &outptr, &outbytes) == (size_t)-1)
+ {
+ jnlib_iconv_close (cd);
+ continue;
+ }
+ *outptr = 0;
+ jnlib_iconv_close (cd);
+ log_info ("decryption failed; trying charset `%s'\n",
+ charsets[charsetidx]);
+ }
+ memcpy (plaintext, ciphertext, length);
+ crypt_block (plaintext, length, salt, saltlen, iter,
+ convertedpw? convertedpw:pw, cipher_algo, 0);
+ if (check_fnc (plaintext, length))
+ break; /* Decryption succeeded. */
+ }
+ gcry_free (convertedpw);
+}
+
+
+/* Return true if the decryption of an bag_encrypted_data object has
+ likely succeeded. */
+static int
+bag_decrypted_data_p (const void *plaintext, size_t length)
+{
+ struct tag_info ti;
+ const unsigned char *p = plaintext;
+ size_t n = length;
+
+ /* { */
+ /* # warning debug code is enabled */
+ /* FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */
+ /* if (!fp || fwrite (p, n, 1, fp) != 1) */
+ /* exit (2); */
+ /* fclose (fp); */
+ /* } */
+
+ if (parse_tag (&p, &n, &ti))
+ return 0;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ return 0;
+ if (parse_tag (&p, &n, &ti))
+ return 0;
+
+ return 1;
+}
+
+/* Note: If R_RESULT is passed as NULL, a key object as already be
+ processed and thus we need to skip it here. */
+static int
+parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
+ int startoffset, size_t *r_consumed, const char *pw,
+ void (*certcb)(void*, const unsigned char*, size_t),
+ void *certcbarg, gcry_mpi_t **r_result)
+{
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ const unsigned char *p_start = buffer;
+ size_t n = length;
+ const char *where;
+ char salt[20];
+ size_t saltlen;
+ unsigned int iter;
+ unsigned char *plain = NULL;
+ int bad_pass = 0;
+ unsigned char *cram_buffer = NULL;
+ size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */
+ int is_3des = 0;
+ gcry_mpi_t *result = NULL;
+ int result_count;
+
+ if (r_result)
+ *r_result = NULL;
+ where = "start";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "bag.encryptedData.version";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0)
+ goto bailout;
+ p++; n--;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "bag.encryptedData.data";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data)
+ || memcmp (p, oid_data, DIM(oid_data)))
+ goto bailout;
+ p += DIM(oid_data);
+ n -= DIM(oid_data);
+
+ where = "bag.encryptedData.keyinfo";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (!ti.class && ti.tag == TAG_OBJECT_ID
+ && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC)
+ && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC,
+ DIM(oid_pbeWithSHAAnd40BitRC2_CBC)))
+ {
+ p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC);
+ n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC);
+ }
+ else if (!ti.class && ti.tag == TAG_OBJECT_ID
+ && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
+ && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
+ DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
+ {
+ p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ is_3des = 1;
+ }
+ else
+ goto bailout;
+
+ where = "rc2or3des-params";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING
+ || ti.length < 8 || ti.length > 20 )
+ goto bailout;
+ saltlen = ti.length;
+ memcpy (salt, p, saltlen);
+ p += saltlen;
+ n -= saltlen;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_INTEGER || !ti.length )
+ goto bailout;
+ for (iter=0; ti.length; ti.length--)
+ {
+ iter <<= 8;
+ iter |= (*p++) & 0xff;
+ n--;
+ }
+
+ where = "rc2or3des-ciphertext";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+
+ consumed = p - p_start;
+ if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef)
+ {
+ /* Mozilla exported certs now come with single byte chunks of
+ octect strings. (Mozilla Firefox 1.0.4). Arghh. */
+ where = "cram-rc2or3des-ciphertext";
+ cram_buffer = cram_octet_string ( p, &n, &consumed);
+ if (!cram_buffer)
+ goto bailout;
+ p = p_start = cram_buffer;
+ if (r_consumed)
+ *r_consumed = consumed;
+ r_consumed = NULL; /* Ugly hack to not update that value any further. */
+ ti.length = n;
+ }
+ else if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.length )
+ ;
+ else
+ goto bailout;
+
+ log_info ("%lu bytes of %s encrypted text\n",ti.length,is_3des?"3DES":"RC2");
+
+ plain = gcry_malloc_secure (ti.length);
+ if (!plain)
+ {
+ log_error ("error allocating decryption buffer\n");
+ goto bailout;
+ }
+ decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw,
+ is_3des? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40,
+ bag_decrypted_data_p);
+ n = ti.length;
+ startoffset = 0;
+ p_start = p = plain;
+
+ where = "outer.outer.seq";
+ if (parse_tag (&p, &n, &ti))
+ {
+ bad_pass = 1;
+ goto bailout;
+ }
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ {
+ bad_pass = 1;
+ goto bailout;
+ }
+
+ if (parse_tag (&p, &n, &ti))
+ {
+ bad_pass = 1;
+ goto bailout;
+ }
+
+ /* Loop over all certificates inside the bag. */
+ while (n)
+ {
+ int iscrlbag = 0;
+ int iskeybag = 0;
+
+ where = "certbag.nextcert";
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "certbag.objectidentifier";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID)
+ goto bailout;
+ if ( ti.length == DIM(oid_pkcs_12_CertBag)
+ && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag)))
+ {
+ p += DIM(oid_pkcs_12_CertBag);
+ n -= DIM(oid_pkcs_12_CertBag);
+ }
+ else if ( ti.length == DIM(oid_pkcs_12_CrlBag)
+ && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag)))
+ {
+ p += DIM(oid_pkcs_12_CrlBag);
+ n -= DIM(oid_pkcs_12_CrlBag);
+ iscrlbag = 1;
+ }
+ else if ( ti.length == DIM(oid_pkcs_12_keyBag)
+ && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag)))
+ {
+ /* The TrustedMIME plugin for MS Outlook started to create
+ files with just one outer 3DES encrypted container and
+ inside the certificates as well as the key. */
+ p += DIM(oid_pkcs_12_keyBag);
+ n -= DIM(oid_pkcs_12_keyBag);
+ iskeybag = 1;
+ }
+ else
+ goto bailout;
+
+ where = "certbag.before.certheader";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (iscrlbag)
+ {
+ log_info ("skipping unsupported crlBag\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+ else if (iskeybag && (result || !r_result))
+ {
+ log_info ("one keyBag already processed; skipping this one\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+ else if (iskeybag)
+ {
+ int len;
+
+ log_info ("processing simple keyBag\n");
+
+ /* Fixme: This code is duplicated from parse_bag_data. */
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+ || ti.length != 1 || *p)
+ goto bailout;
+ p++; n--;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_rsaEncryption)
+ || memcmp (p, oid_rsaEncryption,
+ DIM(oid_rsaEncryption)))
+ goto bailout;
+ p += DIM (oid_rsaEncryption);
+ n -= DIM (oid_rsaEncryption);
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (n < len)
+ goto bailout;
+ p += len;
+ n -= len;
+ if ( parse_tag (&p, &n, &ti)
+ || ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+ if ( parse_tag (&p, &n, &ti)
+ || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+
+ result = gcry_calloc (10, sizeof *result);
+ if (!result)
+ {
+ log_error ( "error allocating result array\n");
+ goto bailout;
+ }
+ result_count = 0;
+
+ where = "reading.keybag.key-parameters";
+ for (result_count = 0; len && result_count < 9;)
+ {
+ if ( parse_tag (&p, &n, &ti)
+ || ti.class || ti.tag != TAG_INTEGER)
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (!result_count && ti.length == 1 && !*p)
+ ; /* ignore the very first one if it is a 0 */
+ else
+ {
+ int rc;
+
+ rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p,
+ ti.length, NULL);
+ if (rc)
+ {
+ log_error ("error parsing key parameter: %s\n",
+ gpg_strerror (rc));
+ goto bailout;
+ }
+ result_count++;
+ }
+ p += ti.length;
+ n -= ti.length;
+ }
+ if (len)
+ goto bailout;
+ }
+ else
+ {
+ log_info ("processing certBag\n");
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_x509Certificate_for_pkcs_12)
+ || memcmp (p, oid_x509Certificate_for_pkcs_12,
+ DIM(oid_x509Certificate_for_pkcs_12)))
+ goto bailout;
+ p += DIM(oid_x509Certificate_for_pkcs_12);
+ n -= DIM(oid_x509Certificate_for_pkcs_12);
+
+ where = "certbag.before.octetstring";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef)
+ goto bailout;
+
+ /* Return the certificate. */
+ if (certcb)
+ certcb (certcbarg, p, ti.length);
+
+ p += ti.length;
+ n -= ti.length;
+ }
+
+ /* Ugly hack to cope with the padding: Forget about the rest if
+ that is less or equal to the cipher's block length. We can
+ reasonable assume that all valid data will be longer than
+ just one block. */
+ if (n <= 8)
+ n = 0;
+
+ /* Skip the optional SET with the pkcs12 cert attributes. */
+ if (n)
+ {
+ where = "bag.attributes";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (!ti.class && ti.tag == TAG_SEQUENCE)
+ ; /* No attributes. */
+ else if (!ti.class && ti.tag == TAG_SET && !ti.ndef)
+ { /* The optional SET. */
+ p += ti.length;
+ n -= ti.length;
+ if (n <= 8)
+ n = 0;
+ if (n && parse_tag (&p, &n, &ti))
+ goto bailout;
+ }
+ else
+ goto bailout;
+ }
+ }
+
+ if (r_consumed)
+ *r_consumed = consumed;
+ gcry_free (plain);
+ gcry_free (cram_buffer);
+ if (r_result)
+ *r_result = result;
+ return 0;
+
+ bailout:
+ if (result)
+ {
+ int i;
+
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ if (r_consumed)
+ *r_consumed = consumed;
+ gcry_free (plain);
+ gcry_free (cram_buffer);
+ log_error ("encryptedData error at \"%s\", offset %u\n",
+ where, (unsigned int)((p - p_start)+startoffset));
+ if (bad_pass)
+ {
+ /* Note, that the following string might be used by other programs
+ to check for a bad passphrase; it should therefore not be
+ translated or changed. */
+ log_error ("possibly bad passphrase given\n");
+ }
+ return -1;
+}
+
+
+/* Return true if the decryption of a bag_data object has likely
+ succeeded. */
+static int
+bag_data_p (const void *plaintext, size_t length)
+{
+ struct tag_info ti;
+ const unsigned char *p = plaintext;
+ size_t n = length;
+
+/* { */
+/* # warning debug code is enabled */
+/* FILE *fp = fopen ("tmp-3des-plain-key.der", "wb"); */
+/* if (!fp || fwrite (p, n, 1, fp) != 1) */
+/* exit (2); */
+/* fclose (fp); */
+/* } */
+
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ return 0;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+ || ti.length != 1 || *p)
+ return 0;
+
+ return 1;
+}
+
+
+static gcry_mpi_t *
+parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
+ size_t *r_consumed, const char *pw)
+{
+ int rc;
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ const unsigned char *p_start = buffer;
+ size_t n = length;
+ const char *where;
+ char salt[20];
+ size_t saltlen;
+ unsigned int iter;
+ int len;
+ unsigned char *plain = NULL;
+ gcry_mpi_t *result = NULL;
+ int result_count, i;
+ unsigned char *cram_buffer = NULL;
+ size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */
+
+ where = "start";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+
+ consumed = p - p_start;
+ if (ti.is_constructed && ti.ndef)
+ {
+ /* Mozilla exported certs now come with single byte chunks of
+ octect strings. (Mozilla Firefox 1.0.4). Arghh. */
+ where = "cram-data.outersegs";
+ cram_buffer = cram_octet_string ( p, &n, &consumed);
+ if (!cram_buffer)
+ goto bailout;
+ p = p_start = cram_buffer;
+ if (r_consumed)
+ *r_consumed = consumed;
+ r_consumed = NULL; /* Ugly hack to not update that value any further. */
+ }
+
+
+ where = "data.outerseqs";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "data.objectidentifier";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)
+ || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
+ DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)))
+ goto bailout;
+ p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
+ n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ where = "shrouded,outerseqs";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
+ || memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
+ DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
+ goto bailout;
+ p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+
+ where = "3des-params";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING
+ || ti.length < 8 || ti.length > 20)
+ goto bailout;
+ saltlen = ti.length;
+ memcpy (salt, p, saltlen);
+ p += saltlen;
+ n -= saltlen;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_INTEGER || !ti.length )
+ goto bailout;
+ for (iter=0; ti.length; ti.length--)
+ {
+ iter <<= 8;
+ iter |= (*p++) & 0xff;
+ n--;
+ }
+
+ where = "3des-ciphertext";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length )
+ goto bailout;
+
+ log_info ("%lu bytes of 3DES encrypted text\n", ti.length);
+
+ plain = gcry_malloc_secure (ti.length);
+ if (!plain)
+ {
+ log_error ("error allocating decryption buffer\n");
+ goto bailout;
+ }
+ consumed += p - p_start + ti.length;
+ decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw,
+ GCRY_CIPHER_3DES,
+ bag_data_p);
+ n = ti.length;
+ startoffset = 0;
+ p_start = p = plain;
+
+ where = "decrypted-text";
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+ || ti.length != 1 || *p)
+ goto bailout;
+ p++; n--;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_rsaEncryption)
+ || memcmp (p, oid_rsaEncryption,
+ DIM(oid_rsaEncryption)))
+ goto bailout;
+ p += DIM (oid_rsaEncryption);
+ n -= DIM (oid_rsaEncryption);
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (n < len)
+ goto bailout;
+ p += len;
+ n -= len;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+
+ result = gcry_calloc (10, sizeof *result);
+ if (!result)
+ {
+ log_error ( "error allocating result array\n");
+ goto bailout;
+ }
+ result_count = 0;
+
+ where = "reading.key-parameters";
+ for (result_count=0; len && result_count < 9;)
+ {
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER)
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (!result_count && ti.length == 1 && !*p)
+ ; /* ignore the very first one if it is a 0 */
+ else
+ {
+ rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p,
+ ti.length, NULL);
+ if (rc)
+ {
+ log_error ("error parsing key parameter: %s\n",
+ gpg_strerror (rc));
+ goto bailout;
+ }
+ result_count++;
+ }
+ p += ti.length;
+ n -= ti.length;
+ }
+ if (len)
+ goto bailout;
+
+ gcry_free (cram_buffer);
+ if (r_consumed)
+ *r_consumed = consumed;
+ return result;
+
+ bailout:
+ gcry_free (plain);
+ if (result)
+ {
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ gcry_free (cram_buffer);
+ log_error ( "data error at \"%s\", offset %u\n",
+ where, (unsigned int)((p - buffer) + startoffset));
+ if (r_consumed)
+ *r_consumed = consumed;
+ return NULL;
+}
+
+
+/* Parse a PKCS12 object and return an array of MPI representing the
+ secret key parameters. This is a very limited implementation in
+ that it is only able to look for 3DES encoded encryptedData and
+ tries to extract the first private key object it finds. In case of
+ an error NULL is returned. CERTCB and CERRTCBARG are used to pass
+ X.509 certificates back to the caller. */
+gcry_mpi_t *
+p12_parse (const unsigned char *buffer, size_t length, const char *pw,
+ void (*certcb)(void*, const unsigned char*, size_t),
+ void *certcbarg)
+{
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ const unsigned char *p_start = buffer;
+ size_t n = length;
+ const char *where;
+ int bagseqlength, len;
+ int bagseqndef, lenndef;
+ gcry_mpi_t *result = NULL;
+ unsigned char *cram_buffer = NULL;
+
+ where = "pfx";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "pfxVersion";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3)
+ goto bailout;
+ p++; n--;
+
+ where = "authSave";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data)
+ || memcmp (p, oid_data, DIM(oid_data)))
+ goto bailout;
+ p += DIM(oid_data);
+ n -= DIM(oid_data);
+
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+
+ if (ti.is_constructed && ti.ndef)
+ {
+ /* Mozilla exported certs now come with single byte chunks of
+ octect strings. (Mozilla Firefox 1.0.4). Arghh. */
+ where = "cram-bags";
+ cram_buffer = cram_octet_string ( p, &n, NULL);
+ if (!cram_buffer)
+ goto bailout;
+ p = p_start = cram_buffer;
+ }
+
+ where = "bags";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ bagseqndef = ti.ndef;
+ bagseqlength = ti.length;
+ while (bagseqlength || bagseqndef)
+ {
+/* log_debug ( "at offset %u\n", (p - p_start)); */
+ where = "bag-sequence";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (bagseqndef && ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)
+ break; /* Ready */
+ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ if (!bagseqndef)
+ {
+ if (bagseqlength < ti.nhdr)
+ goto bailout;
+ bagseqlength -= ti.nhdr;
+ if (bagseqlength < ti.length)
+ goto bailout;
+ bagseqlength -= ti.length;
+ }
+ lenndef = ti.ndef;
+ len = ti.length;
+
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (lenndef)
+ len = ti.nhdr;
+ else
+ len -= ti.nhdr;
+
+ if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData)
+ && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData)))
+ {
+ size_t consumed = 0;
+
+ p += DIM(oid_encryptedData);
+ n -= DIM(oid_encryptedData);
+ if (!lenndef)
+ len -= DIM(oid_encryptedData);
+ where = "bag.encryptedData";
+ if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw,
+ certcb, certcbarg,
+ result? NULL : &result))
+ goto bailout;
+ if (lenndef)
+ len += consumed;
+ }
+ else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data)
+ && !memcmp (p, oid_data, DIM(oid_data)))
+ {
+ if (result)
+ {
+ log_info ("already got an key object, skipping this one\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+ else
+ {
+ size_t consumed = 0;
+
+ p += DIM(oid_data);
+ n -= DIM(oid_data);
+ if (!lenndef)
+ len -= DIM(oid_data);
+ result = parse_bag_data (p, n, (p - p_start), &consumed, pw);
+ if (!result)
+ goto bailout;
+ if (lenndef)
+ len += consumed;
+ }
+ }
+ else
+ {
+ log_info ("unknown bag type - skipped\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+
+ if (len < 0 || len > n)
+ goto bailout;
+ p += len;
+ n -= len;
+ if (lenndef)
+ {
+ /* Need to skip the Null Tag. */
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (!(ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed))
+ goto bailout;
+ }
+ }
+
+ gcry_free (cram_buffer);
+ return result;
+ bailout:
+ log_error ("error at \"%s\", offset %u\n",
+ where, (unsigned int)(p - p_start));
+ if (result)
+ {
+ int i;
+
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ gcry_free (cram_buffer);
+ return NULL;
+}
+
+
+
+static size_t
+compute_tag_length (size_t n)
+{
+ int needed = 0;
+
+ if (n < 128)
+ needed += 2; /* tag and one length byte */
+ else if (n < 256)
+ needed += 3; /* tag, number of length bytes, 1 length byte */
+ else if (n < 65536)
+ needed += 4; /* tag, number of length bytes, 2 length bytes */
+ else
+ {
+ log_error ("object too larger to encode\n");
+ return 0;
+ }
+ return needed;
+}
+
+static unsigned char *
+store_tag_length (unsigned char *p, int tag, size_t n)
+{
+ if (tag == TAG_SEQUENCE)
+ tag |= 0x20; /* constructed */
+
+ *p++ = tag;
+ if (n < 128)
+ *p++ = n;
+ else if (n < 256)
+ {
+ *p++ = 0x81;
+ *p++ = n;
+ }
+ else if (n < 65536)
+ {
+ *p++ = 0x82;
+ *p++ = n >> 8;
+ *p++ = n;
+ }
+
+ return p;
+}
+
+
+/* Create the final PKCS-12 object from the sequences contained in
+ SEQLIST. PW is the password. That array is terminated with an NULL
+ object. */
+static unsigned char *
+create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
+{
+ int i;
+ size_t needed = 0;
+ size_t len[8], n;
+ unsigned char *macstart;
+ size_t maclen;
+ unsigned char *result, *p;
+ size_t resultlen;
+ char salt[8];
+ unsigned char keybuf[20];
+ gcry_md_hd_t md;
+ int rc;
+ int with_mac = 1;
+
+
+ /* 9 steps to create the pkcs#12 Krampf. */
+
+ /* 8. The MAC. */
+ /* We add this at step 0. */
+
+ /* 7. All the buffers. */
+ for (i=0; sequences[i].buffer; i++)
+ needed += sequences[i].length;
+
+ /* 6. This goes into a sequences. */
+ len[6] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 5. Encapsulate all in an octet string. */
+ len[5] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 4. And tag it with [0]. */
+ len[4] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 3. Prepend an data OID. */
+ needed += 2 + DIM (oid_data);
+
+ /* 2. Put all into a sequences. */
+ len[2] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 1. Prepend the version integer 3. */
+ needed += 3;
+
+ /* 0. And the final outer sequence. */
+ if (with_mac)
+ needed += DIM (data_mactemplate);
+ len[0] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* Allocate a buffer. */
+ result = gcry_malloc (needed);
+ if (!result)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+ p = result;
+
+ /* 0. Store the very outer sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the version integer 3. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 3;
+
+ /* 2. Store another sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[2]);
+
+ /* 3. Store the data OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
+ memcpy (p, oid_data, DIM (oid_data));
+ p += DIM (oid_data);
+
+ /* 4. Next comes a context tag. */
+ p = store_tag_length (p, 0xa0, len[4]);
+
+ /* 5. And an octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, len[5]);
+
+ /* 6. And the inner sequence. */
+ macstart = p;
+ p = store_tag_length (p, TAG_SEQUENCE, len[6]);
+
+ /* 7. Append all the buffers. */
+ for (i=0; sequences[i].buffer; i++)
+ {
+ memcpy (p, sequences[i].buffer, sequences[i].length);
+ p += sequences[i].length;
+ }
+
+ if (with_mac)
+ {
+ /* Intermezzo to compute the MAC. */
+ maclen = p - macstart;
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf))
+ {
+ gcry_free (result);
+ return NULL;
+ }
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+ if (rc)
+ {
+ log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc));
+ gcry_free (result);
+ return NULL;
+ }
+ rc = gcry_md_setkey (md, keybuf, 20);
+ if (rc)
+ {
+ log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc));
+ gcry_md_close (md);
+ gcry_free (result);
+ return NULL;
+ }
+ gcry_md_write (md, macstart, maclen);
+
+ /* 8. Append the MAC template and fix it up. */
+ memcpy (p, data_mactemplate, DIM (data_mactemplate));
+ memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8);
+ memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20);
+ p += DIM (data_mactemplate);
+ gcry_md_close (md);
+ }
+
+ /* Ready. */
+ resultlen = p - result;
+ if (needed != resultlen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)resultlen);
+
+ *r_length = resultlen;
+ return result;
+}
+
+
+/* Build a DER encoded SEQUENCE with the key:
+
+ SEQUENCE {
+ INTEGER 0
+ SEQUENCE {
+ OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
+ NULL
+ }
+ OCTET STRING, encapsulates {
+ SEQUENCE {
+ INTEGER 0
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ }
+ }
+ }
+*/
+
+static unsigned char *
+build_key_sequence (gcry_mpi_t *kparms, size_t *r_length)
+{
+ int rc, i;
+ size_t needed, n;
+ unsigned char *plain, *p;
+ size_t plainlen;
+ size_t outseqlen, oidseqlen, octstrlen, inseqlen;
+
+ needed = 3; /* The version(?) integer of value 0. */
+ for (i=0; kparms[i]; i++)
+ {
+ n = 0;
+ rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]);
+ if (rc)
+ {
+ log_error ("error formatting parameter: %s\n", gpg_strerror (rc));
+ return NULL;
+ }
+ needed += n;
+ n = compute_tag_length (n);
+ if (!n)
+ return NULL;
+ needed += n;
+ }
+ if (i != 8)
+ {
+ log_error ("invalid parameters for p12_build\n");
+ return NULL;
+ }
+ /* Now this all goes into a sequence. */
+ inseqlen = needed;
+ n = compute_tag_length (needed);
+ if (!n)
+ return NULL;
+ needed += n;
+ /* Encapsulate all into an octet string. */
+ octstrlen = needed;
+ n = compute_tag_length (needed);
+ if (!n)
+ return NULL;
+ needed += n;
+ /* Prepend the object identifier sequence. */
+ oidseqlen = 2 + DIM (oid_rsaEncryption) + 2;
+ needed += 2 + oidseqlen;
+ /* The version number. */
+ needed += 3;
+ /* And finally put the whole thing into a sequence. */
+ outseqlen = needed;
+ n = compute_tag_length (needed);
+ if (!n)
+ return NULL;
+ needed += n;
+
+ /* allocate 8 extra bytes for padding */
+ plain = gcry_malloc_secure (needed+8);
+ if (!plain)
+ {
+ log_error ("error allocating encryption buffer\n");
+ return NULL;
+ }
+
+ /* And now fill the plaintext buffer. */
+ p = plain;
+ p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
+ /* Store version. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 0;
+ /* Store object identifier sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, oidseqlen);
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption));
+ memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption));
+ p += DIM (oid_rsaEncryption);
+ *p++ = TAG_NULL;
+ *p++ = 0;
+ /* Start with the octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, octstrlen);
+ p = store_tag_length (p, TAG_SEQUENCE, inseqlen);
+ /* Store the key parameters. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 0;
+ for (i=0; kparms[i]; i++)
+ {
+ n = 0;
+ rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]);
+ if (rc)
+ {
+ log_error ("oops: error formatting parameter: %s\n",
+ gpg_strerror (rc));
+ gcry_free (plain);
+ return NULL;
+ }
+ p = store_tag_length (p, TAG_INTEGER, n);
+
+ n = plain + needed - p;
+ rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]);
+ if (rc)
+ {
+ log_error ("oops: error storing parameter: %s\n",
+ gpg_strerror (rc));
+ gcry_free (plain);
+ return NULL;
+ }
+ p += n;
+ }
+
+ plainlen = p - plain;
+ assert (needed == plainlen);
+ /* Append some pad characters; we already allocated extra space. */
+ n = 8 - plainlen % 8;
+ for (i=0; i < n; i++, plainlen++)
+ *p++ = n;
+
+ *r_length = plainlen;
+ return plain;
+}
+
+
+
+static unsigned char *
+build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
+ const unsigned char *sha1hash, const char *keyidstr,
+ size_t *r_length)
+{
+ size_t len[11], needed;
+ unsigned char *p, *keybag;
+ size_t keybaglen;
+
+ /* Walk 11 steps down to collect the info: */
+
+ /* 10. The data goes into an octet string. */
+ needed = compute_tag_length (buflen);
+ needed += buflen;
+
+ /* 9. Prepend the algorithm identifier. */
+ needed += DIM (data_3desiter2048);
+
+ /* 8. Put a sequence around. */
+ len[8] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 7. Prepend a [0] tag. */
+ len[7] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 6b. The attributes which are appended at the end. */
+ if (sha1hash)
+ needed += DIM (data_attrtemplate) + 20;
+
+ /* 6. Prepend the shroudedKeyBag OID. */
+ needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ /* 5+4. Put all into two sequences. */
+ len[5] = needed;
+ needed += compute_tag_length ( needed);
+ len[4] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 3. This all goes into an octet string. */
+ len[3] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 2. Prepend another [0] tag. */
+ len[2] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 1. Prepend the data OID. */
+ needed += 2 + DIM (oid_data);
+
+ /* 0. Prepend another sequence. */
+ len[0] = needed;
+ needed += compute_tag_length (needed);
+
+ /* Now that we have all length information, allocate a buffer. */
+ p = keybag = gcry_malloc (needed);
+ if (!keybag)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+
+ /* Walk 11 steps up to store the data. */
+
+ /* 0. Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the data OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
+ memcpy (p, oid_data, DIM (oid_data));
+ p += DIM (oid_data);
+
+ /* 2. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[2]);
+
+ /* 3. And an octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, len[3]);
+
+ /* 4+5. Two sequences. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[4]);
+ p = store_tag_length (p, TAG_SEQUENCE, len[5]);
+
+ /* 6. Store the shroudedKeyBag OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID,
+ DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag));
+ memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
+ DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag));
+ p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ /* 7. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[7]);
+
+ /* 8. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[8]);
+
+ /* 9. Now for the pre-encoded algorithm identifier and the salt. */
+ memcpy (p, data_3desiter2048, DIM (data_3desiter2048));
+ memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8);
+ p += DIM (data_3desiter2048);
+
+ /* 10. And the octet string with the encrypted data. */
+ p = store_tag_length (p, TAG_OCTET_STRING, buflen);
+ memcpy (p, buffer, buflen);
+ p += buflen;
+
+ /* Append the attributes whose length we calculated at step 2b. */
+ if (sha1hash)
+ {
+ int i;
+
+ memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
+ for (i=0; i < 8; i++)
+ p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
+ p += DIM (data_attrtemplate);
+ memcpy (p, sha1hash, 20);
+ p += 20;
+ }
+
+
+ keybaglen = p - keybag;
+ if (needed != keybaglen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)keybaglen);
+
+ *r_length = keybaglen;
+ return keybag;
+}
+
+
+static unsigned char *
+build_cert_bag (unsigned char *buffer, size_t buflen, char *salt,
+ size_t *r_length)
+{
+ size_t len[9], needed;
+ unsigned char *p, *certbag;
+ size_t certbaglen;
+
+ /* Walk 9 steps down to collect the info: */
+
+ /* 8. The data goes into an octet string. */
+ needed = compute_tag_length (buflen);
+ needed += buflen;
+
+ /* 7. The algorithm identifier. */
+ needed += DIM (data_rc2iter2048);
+
+ /* 6. The data OID. */
+ needed += 2 + DIM (oid_data);
+
+ /* 5. A sequence. */
+ len[5] = needed;
+ needed += compute_tag_length ( needed);
+
+ /* 4. An integer. */
+ needed += 3;
+
+ /* 3. A sequence. */
+ len[3] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 2. A [0] tag. */
+ len[2] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 1. The encryptedData OID. */
+ needed += 2 + DIM (oid_encryptedData);
+
+ /* 0. The first sequence. */
+ len[0] = needed;
+ needed += compute_tag_length (needed);
+
+ /* Now that we have all length information, allocate a buffer. */
+ p = certbag = gcry_malloc (needed);
+ if (!certbag)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+
+ /* Walk 9 steps up to store the data. */
+
+ /* 0. Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the encryptedData OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData));
+ memcpy (p, oid_encryptedData, DIM (oid_encryptedData));
+ p += DIM (oid_encryptedData);
+
+ /* 2. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[2]);
+
+ /* 3. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[3]);
+
+ /* 4. Store the integer 0. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 0;
+
+ /* 5. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[5]);
+
+ /* 6. Store the data OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
+ memcpy (p, oid_data, DIM (oid_data));
+ p += DIM (oid_data);
+
+ /* 7. Now for the pre-encoded algorithm identifier and the salt. */
+ memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048));
+ memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8);
+ p += DIM (data_rc2iter2048);
+
+ /* 8. And finally the [0] tag with the encrypted data. */
+ p = store_tag_length (p, 0x80, buflen);
+ memcpy (p, buffer, buflen);
+ p += buflen;
+ certbaglen = p - certbag;
+
+ if (needed != certbaglen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)certbaglen);
+
+ *r_length = certbaglen;
+ return certbag;
+}
+
+
+static unsigned char *
+build_cert_sequence (unsigned char *buffer, size_t buflen,
+ const unsigned char *sha1hash, const char *keyidstr,
+ size_t *r_length)
+{
+ size_t len[8], needed, n;
+ unsigned char *p, *certseq;
+ size_t certseqlen;
+ int i;
+
+ assert (strlen (keyidstr) == 8);
+
+ /* Walk 8 steps down to collect the info: */
+
+ /* 7. The data goes into an octet string. */
+ needed = compute_tag_length (buflen);
+ needed += buflen;
+
+ /* 6. A [0] tag. */
+ len[6] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 5. An OID. */
+ needed += 2 + DIM (oid_x509Certificate_for_pkcs_12);
+
+ /* 4. A sequence. */
+ len[4] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 3. A [0] tag. */
+ len[3] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 2b. The attributes which are appended at the end. */
+ if (sha1hash)
+ needed += DIM (data_attrtemplate) + 20;
+
+ /* 2. An OID. */
+ needed += 2 + DIM (oid_pkcs_12_CertBag);
+
+ /* 1. A sequence. */
+ len[1] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 0. The first sequence. */
+ len[0] = needed;
+ needed += compute_tag_length (needed);
+
+ /* Now that we have all length information, allocate a buffer. */
+ p = certseq = gcry_malloc (needed + 8 /*(for padding)*/);
+ if (!certseq)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+
+ /* Walk 8 steps up to store the data. */
+
+ /* 0. Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the second sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[1]);
+
+ /* 2. Store the pkcs12-cert-bag OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag));
+ memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag));
+ p += DIM (oid_pkcs_12_CertBag);
+
+ /* 3. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[3]);
+
+ /* 4. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[4]);
+
+ /* 5. Store the x509Certificate OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID,
+ DIM (oid_x509Certificate_for_pkcs_12));
+ memcpy (p, oid_x509Certificate_for_pkcs_12,
+ DIM (oid_x509Certificate_for_pkcs_12));
+ p += DIM (oid_x509Certificate_for_pkcs_12);
+
+ /* 6. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[6]);
+
+ /* 7. And the octet string with the actual certificate. */
+ p = store_tag_length (p, TAG_OCTET_STRING, buflen);
+ memcpy (p, buffer, buflen);
+ p += buflen;
+
+ /* Append the attributes whose length we calculated at step 2b. */
+ if (sha1hash)
+ {
+ memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
+ for (i=0; i < 8; i++)
+ p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
+ p += DIM (data_attrtemplate);
+ memcpy (p, sha1hash, 20);
+ p += 20;
+ }
+
+ certseqlen = p - certseq;
+ if (needed != certseqlen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)certseqlen);
+
+ /* Append some pad characters; we already allocated extra space. */
+ n = 8 - certseqlen % 8;
+ for (i=0; i < n; i++, certseqlen++)
+ *p++ = n;
+
+ *r_length = certseqlen;
+ return certseq;
+}
+
+
+/* Expect the RSA key parameters in KPARMS and a password in PW.
+ Create a PKCS structure from it and return it as well as the length
+ in R_LENGTH; return NULL in case of an error. If CHARSET is not
+ NULL, re-encode PW to that character set. */
+unsigned char *
+p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,
+ const char *pw, const char *charset, size_t *r_length)
+{
+ unsigned char *buffer = NULL;
+ size_t n, buflen;
+ char salt[8];
+ struct buffer_s seqlist[3];
+ int seqlistidx = 0;
+ unsigned char sha1hash[20];
+ char keyidstr[8+1];
+ char *pwbuf = NULL;
+ size_t pwbufsize = 0;
+
+ n = buflen = 0; /* (avoid compiler warning). */
+ memset (sha1hash, 0, 20);
+ *keyidstr = 0;
+
+ if (charset && pw && *pw)
+ {
+ jnlib_iconv_t cd;
+ const char *inptr;
+ char *outptr;
+ size_t inbytes, outbytes;
+
+ /* We assume that the converted passphrase is at max 2 times
+ longer than its utf-8 encoding. */
+ pwbufsize = strlen (pw)*2 + 1;
+ pwbuf = gcry_malloc_secure (pwbufsize);
+ if (!pwbuf)
+ {
+ log_error ("out of secure memory while converting passphrase\n");
+ goto failure;
+ }
+
+ cd = jnlib_iconv_open (charset, "utf-8");
+ if (cd == (jnlib_iconv_t)(-1))
+ {
+ log_error ("can't convert passphrase to"
+ " requested charset `%s': %s\n",
+ charset, strerror (errno));
+ gcry_free (pwbuf);
+ goto failure;
+ }
+
+ inptr = pw;
+ inbytes = strlen (pw);
+ outptr = pwbuf;
+ outbytes = pwbufsize - 1;
+ if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
+ &outptr, &outbytes) == (size_t)-1)
+ {
+ log_error ("error converting passphrase to"
+ " requested charset `%s': %s\n",
+ charset, strerror (errno));
+ gcry_free (pwbuf);
+ jnlib_iconv_close (cd);
+ goto failure;
+ }
+ *outptr = 0;
+ jnlib_iconv_close (cd);
+ pw = pwbuf;
+ }
+
+
+ if (cert && certlen)
+ {
+ /* Calculate the hash value we need for the bag attributes. */
+ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen);
+ sprintf (keyidstr, "%02x%02x%02x%02x",
+ sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]);
+
+ /* Encode the certificate. */
+ buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr,
+ &buflen);
+ if (!buffer)
+ goto failure;
+
+ /* Encrypt it. */
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ crypt_block (buffer, buflen, salt, 8, 2048, pw,
+ GCRY_CIPHER_RFC2268_40, 1);
+
+ /* Encode the encrypted stuff into a bag. */
+ seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n);
+ seqlist[seqlistidx].length = n;
+ gcry_free (buffer);
+ buffer = NULL;
+ if (!seqlist[seqlistidx].buffer)
+ goto failure;
+ seqlistidx++;
+ }
+
+
+ if (kparms)
+ {
+ /* Encode the key. */
+ buffer = build_key_sequence (kparms, &buflen);
+ if (!buffer)
+ goto failure;
+
+ /* Encrypt it. */
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1);
+
+ /* Encode the encrypted stuff into a bag. */
+ if (cert && certlen)
+ seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
+ sha1hash, keyidstr, &n);
+ else
+ seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
+ NULL, NULL, &n);
+ seqlist[seqlistidx].length = n;
+ gcry_free (buffer);
+ buffer = NULL;
+ if (!seqlist[seqlistidx].buffer)
+ goto failure;
+ seqlistidx++;
+ }
+
+ seqlist[seqlistidx].buffer = NULL;
+ seqlist[seqlistidx].length = 0;
+
+ buffer = create_final (seqlist, pw, &buflen);
+
+ failure:
+ if (pwbuf)
+ {
+ wipememory (pwbuf, pwbufsize);
+ gcry_free (pwbuf);
+ }
+ for ( ; seqlistidx; seqlistidx--)
+ gcry_free (seqlist[seqlistidx].buffer);
+
+ *r_length = buffer? buflen : 0;
+ return buffer;
+}
+
+
+#ifdef TEST
+
+static void
+cert_cb (void *opaque, const unsigned char *cert, size_t certlen)
+{
+ printf ("got a certificate of %u bytes length\n", certlen);
+}
+
+int
+main (int argc, char **argv)
+{
+ FILE *fp;
+ struct stat st;
+ unsigned char *buf;
+ size_t buflen;
+ gcry_mpi_t *result;
+
+ if (argc != 3)
+ {
+ fprintf (stderr, "usage: testp12 file passphrase\n");
+ return 1;
+ }
+
+ gcry_control (GCRYCTL_DISABLE_SECMEM, NULL);
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL);
+
+ fp = fopen (argv[1], "rb");
+ if (!fp)
+ {
+ fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+
+ buflen = st.st_size;
+ buf = gcry_malloc (buflen+1);
+ if (!buf || fread (buf, buflen, 1, fp) != 1)
+ {
+ fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+ fclose (fp);
+
+ result = p12_parse (buf, buflen, argv[2], cert_cb, NULL);
+ if (result)
+ {
+ int i, rc;
+ unsigned char *tmpbuf;
+
+ for (i=0; result[i]; i++)
+ {
+ rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf,
+ NULL, result[i]);
+ if (rc)
+ printf ("%d: [error printing number: %s]\n",
+ i, gpg_strerror (rc));
+ else
+ {
+ printf ("%d: %s\n", i, tmpbuf);
+ gcry_free (tmpbuf);
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -O0 -g -DTEST=1 -o minip12 minip12.c ../jnlib/libjnlib.a -L /usr/local/lib -lgcrypt -lgpg-error"
+End:
+*/
+#endif /* TEST */
diff --git a/agent/minip12.h b/agent/minip12.h
new file mode 100644
index 0000000..998f82f
--- /dev/null
+++ b/agent/minip12.h
@@ -0,0 +1,36 @@
+/* minip12.h - Global definitions for the minimal pkcs-12 implementation.
+ * Copyright (C) 2002, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MINIP12_H
+#define MINIP12_H
+
+#include <gcrypt.h>
+
+gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length,
+ const char *pw,
+ void (*certcb)(void*, const unsigned char*, size_t),
+ void *certcbarg);
+
+unsigned char *p12_build (gcry_mpi_t *kparms,
+ unsigned char *cert, size_t certlen,
+ const char *pw, const char *charset,
+ size_t *r_length);
+
+
+#endif /*MINIP12_H*/
diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
new file mode 100644
index 0000000..9e1c47d
--- /dev/null
+++ b/agent/pkdecrypt.c
@@ -0,0 +1,151 @@
+/* pkdecrypt.c - public key decryption (well, acually 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 <http://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. */
+int
+agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
+ const unsigned char *ciphertext, size_t ciphertextlen,
+ membuf_t *outbuf)
+{
+ 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;
+
+ 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 ("keygrip:", ctrl->keygrip, 20);
+ log_printhex ("cipher: ", ciphertext, ciphertextlen);
+ }
+ rc = agent_key_from_file (ctrl, desc_text,
+ ctrl->keygrip, &shadow_info,
+ CACHE_MODE_NORMAL, NULL, &s_skey);
+ if (rc)
+ {
+ if (gpg_err_code (rc) == GPG_ERR_ENOENT)
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ else
+ log_error ("failed to read the secret key\n");
+ goto leave;
+ }
+
+ if (!s_skey)
+ { /* 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, ciphertext, shadow_info, &buf, &len );
+ if (rc)
+ {
+ log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ {
+ char tmpbuf[60];
+
+ sprintf (tmpbuf, "(5:value%u:", (unsigned int)len);
+ put_membuf (outbuf, tmpbuf, strlen (tmpbuf));
+ 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..25cadb2
--- /dev/null
+++ b/agent/pksign.c
@@ -0,0 +1,261 @@
+/* pksign.c - public key signing (well, actually using a secret key)
+ * Copyright (C) 2001, 2002, 2003, 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 <http://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"
+
+
+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);
+ }
+
+ }
+
+ *r_hash = hash;
+ return rc;
+}
+
+
+/* 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. */
+int
+agent_pksign_do (ctrl_t ctrl, const char *desc_text,
+ gcry_sexp_t *signature_sexp,
+ cache_mode_t cache_mode, lookup_ttl_t lookup_ttl)
+{
+ gcry_sexp_t s_skey = NULL, s_sig = NULL;
+ unsigned char *shadow_info = NULL;
+ unsigned int rc = 0; /* FIXME: gpg-error? */
+
+ if (! ctrl->have_keygrip)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip,
+ &shadow_info, cache_mode, lookup_ttl,
+ &s_skey);
+ if (rc)
+ {
+ log_error ("failed to read the secret key\n");
+ goto leave;
+ }
+
+ if (!s_skey)
+ {
+ /* Divert operation to the smartcard */
+
+ unsigned char *buf = NULL;
+ size_t len = 0;
+
+ rc = divert_pksign (ctrl,
+ ctrl->digest.value,
+ ctrl->digest.valuelen,
+ ctrl->digest.algo,
+ shadow_info, &buf);
+ if (rc)
+ {
+ log_error ("smartcard signing failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ len = gcry_sexp_canon_len (buf, 0, NULL, NULL);
+ assert (len);
+
+ rc = gcry_sexp_sscan (&s_sig, NULL, (char*)buf, len);
+ xfree (buf);
+ if (rc)
+ {
+ log_error ("failed to convert sigbuf returned by divert_pksign "
+ "into S-Exp: %s", gpg_strerror (rc));
+ goto leave;
+ }
+ }
+ else
+ {
+ /* No smartcard, but a private key */
+
+ gcry_sexp_t s_hash = NULL;
+
+ /* Put the hash into a sexp */
+ if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
+ rc = do_encode_raw_pkcs1 (ctrl->digest.value,
+ ctrl->digest.valuelen,
+ gcry_pk_get_nbits (s_skey),
+ &s_hash);
+ else
+ rc = do_encode_md (ctrl->digest.value,
+ ctrl->digest.valuelen,
+ ctrl->digest.algo,
+ &s_hash,
+ ctrl->digest.raw_value);
+ if (rc)
+ goto leave;
+
+ if (DBG_CRYPTO)
+ {
+ log_debug ("skey: ");
+ gcry_sexp_dump (s_skey);
+ }
+
+ /* sign */
+ rc = gcry_pk_sign (&s_sig, s_hash, s_skey);
+ gcry_sexp_release (s_hash);
+ if (rc)
+ {
+ log_error ("signing failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ {
+ log_debug ("result: ");
+ gcry_sexp_dump (s_sig);
+ }
+ }
+
+ leave:
+
+ *signature_sexp = s_sig;
+
+ gcry_sexp_release (s_skey);
+ xfree (shadow_info);
+
+ return rc;
+}
+
+/* SIGN whatever information we have accumulated in CTRL and write it
+ back to OUTFP. */
+int
+agent_pksign (ctrl_t ctrl, const char *desc_text,
+ membuf_t *outbuf, cache_mode_t cache_mode)
+{
+ gcry_sexp_t s_sig = NULL;
+ char *buf = NULL;
+ size_t len = 0;
+ int rc = 0;
+
+ rc = agent_pksign_do (ctrl, desc_text, &s_sig, cache_mode, NULL);
+ if (rc)
+ goto leave;
+
+ len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xmalloc (len);
+ len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+
+ put_membuf (outbuf, buf, len);
+
+ leave:
+ gcry_sexp_release (s_sig);
+ xfree (buf);
+
+ return rc;
+}
diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c
new file mode 100644
index 0000000..72de91b
--- /dev/null
+++ b/agent/preset-passphrase.c
@@ -0,0 +1,270 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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
+#include <windows.h> /* To initialize the sockets. fixme */
+#endif
+
+#define JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+#include "minip12.h"
+#include "simple-pwquery.h"
+#include "i18n.h"
+#include "sysutils.h"
+
+
+enum cmd_and_opt_values
+{ aNull = 0,
+ oVerbose = 'v',
+ oPassphrase = 'P',
+
+ oPreset = 'c',
+ oForget = 'f',
+
+ oNoVerbose = 500,
+
+ oHomedir,
+
+aTest };
+
+
+static const char *opt_homedir;
+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, "@" },
+ {0}
+};
+
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "gpg-preset-passphrase (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+
+ case 1:
+ case 40:
+ p = _("Usage: gpg-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;
+}
+
+
+
+
+/* Include the implementation of map_spwq_error. */
+MAP_SPWQ_ERROR_IMPL
+
+
+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 = map_spwq_error (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 = map_spwq_error (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;
+
+ set_strusage (my_strusage);
+ log_set_prefix ("gpg-preset-passphrase", 1);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems ();
+
+ opt_homedir = default_homedir ();
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* (do not remove the args) */
+ while (arg_parse (&pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oHomedir: opt_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;
+ }
+ }
+ if (log_get_errorcount(0))
+ exit(2);
+
+ if (argc == 1)
+ keygrip = *argv;
+ else
+ usage (1);
+
+ /* Tell simple-pwquery about the the standard socket name. */
+ {
+ char *tmp = make_filename (opt_homedir, "S.gpg-agent", 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..dc040f9
--- /dev/null
+++ b/agent/protect-tool.c
@@ -0,0 +1,1271 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+#include "minip12.h"
+#include "i18n.h"
+#include "get-passphrase.h"
+#include "sysutils.h"
+#include "estream.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,
+
+ oP12Import,
+ oP12Export,
+ oP12Charset,
+ oStore,
+ oForce,
+ oHaveCert,
+ oNoFailOnExist,
+ oHomedir,
+ oPrompt,
+ oStatusMsg,
+
+ 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 const char *opt_homedir;
+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_p12_charset;
+static const char *opt_agent_program;
+
+static char *get_passphrase (int promptno);
+static void release_passphrase (char *pw);
+static int store_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force);
+
+
+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 (oP12Import, "p12-import",
+ "import a pkcs#12 encoded private key"),
+ ARGPARSE_c (oP12Export, "p12-export",
+ "export a private key pkcs#12 encoded"),
+
+ 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_s (oP12Charset,"p12-charset",
+ "|NAME|set charset for a new PKCS#12 passphrase to NAME"),
+ 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_end ()
+};
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "gpg-protect-tool (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+
+ case 1:
+ case 40: p = _("Usage: gpg-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 = 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;
+ 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);
+ 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 (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 (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)
+ 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);
+
+
+ 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');
+}
+
+
+static int
+rsa_key_check (struct rsa_secret_key_s *skey)
+{
+ int err = 0;
+ gcry_mpi_t t = gcry_mpi_snew (0);
+ gcry_mpi_t t1 = gcry_mpi_snew (0);
+ gcry_mpi_t t2 = gcry_mpi_snew (0);
+ gcry_mpi_t phi = gcry_mpi_snew (0);
+
+ /* check that n == p * q */
+ gcry_mpi_mul (t, skey->p, skey->q);
+ if (gcry_mpi_cmp( t, skey->n) )
+ {
+ log_error ("RSA oops: n != p * q\n");
+ err++;
+ }
+
+ /* check that p is less than q */
+ if (gcry_mpi_cmp (skey->p, skey->q) > 0)
+ {
+ gcry_mpi_t tmp;
+
+ log_info ("swapping secret primes\n");
+ tmp = gcry_mpi_copy (skey->p);
+ gcry_mpi_set (skey->p, skey->q);
+ gcry_mpi_set (skey->q, tmp);
+ gcry_mpi_release (tmp);
+ /* and must recompute u of course */
+ gcry_mpi_invm (skey->u, skey->p, skey->q);
+ }
+
+ /* check that e divides neither p-1 nor q-1 */
+ gcry_mpi_sub_ui (t, skey->p, 1 );
+ gcry_mpi_div (NULL, t, t, skey->e, 0);
+ if (!gcry_mpi_cmp_ui( t, 0) )
+ {
+ log_error ("RSA oops: e divides p-1\n");
+ err++;
+ }
+ gcry_mpi_sub_ui (t, skey->q, 1);
+ gcry_mpi_div (NULL, t, t, skey->e, 0);
+ if (!gcry_mpi_cmp_ui( t, 0))
+ {
+ log_info ( "RSA oops: e divides q-1\n" );
+ err++;
+ }
+
+ /* check that d is correct. */
+ gcry_mpi_sub_ui (t1, skey->p, 1);
+ gcry_mpi_sub_ui (t2, skey->q, 1);
+ gcry_mpi_mul (phi, t1, t2);
+ gcry_mpi_invm (t, skey->e, phi);
+ if (gcry_mpi_cmp (t, skey->d))
+ { /* no: try universal exponent. */
+ gcry_mpi_gcd (t, t1, t2);
+ gcry_mpi_div (t, NULL, phi, t, 0);
+ gcry_mpi_invm (t, skey->e, t);
+ if (gcry_mpi_cmp (t, skey->d))
+ {
+ log_error ("RSA oops: bad secret exponent\n");
+ err++;
+ }
+ }
+
+ /* check for correctness of u */
+ gcry_mpi_invm (t, skey->p, skey->q);
+ if (gcry_mpi_cmp (t, skey->u))
+ {
+ log_info ( "RSA oops: bad u parameter\n");
+ err++;
+ }
+
+ if (err)
+ log_info ("RSA secret key check failed\n");
+
+ gcry_mpi_release (t);
+ gcry_mpi_release (t1);
+ gcry_mpi_release (t2);
+ gcry_mpi_release (phi);
+
+ return err? -1:0;
+}
+
+
+/* A callback used by p12_parse to return a certificate. */
+static void
+import_p12_cert_cb (void *opaque, const unsigned char *cert, size_t certlen)
+{
+ struct b64state state;
+ gpg_error_t err, err2;
+
+ (void)opaque;
+
+ err = b64enc_start (&state, stdout, "CERTIFICATE");
+ if (!err)
+ err = b64enc_write (&state, cert, certlen);
+ err2 = b64enc_finish (&state);
+ if (!err)
+ err = err2;
+ if (err)
+ log_error ("error writing armored certificate: %s\n", gpg_strerror (err));
+}
+
+static void
+import_p12_file (const char *fname)
+{
+ char *buf;
+ unsigned char *result;
+ size_t buflen, resultlen, buf_off;
+ int i;
+ int rc;
+ gcry_mpi_t *kparms;
+ struct rsa_secret_key_s sk;
+ gcry_sexp_t s_key;
+ unsigned char *key;
+ unsigned char grip[20];
+ char *pw;
+
+ /* fixme: we should release some stuff on error */
+
+ buf = read_file (fname, &buflen);
+ if (!buf)
+ return;
+
+ /* GnuPG 2.0.4 accidently created binary P12 files with the string
+ "The passphrase is %s encoded.\n\n" prepended to the ASN.1 data.
+ We fix that here. */
+ if (buflen > 29 && !memcmp (buf, "The passphrase is ", 18))
+ {
+ for (buf_off=18; buf_off < buflen && buf[buf_off] != '\n'; buf_off++)
+ ;
+ buf_off++;
+ if (buf_off < buflen && buf[buf_off] == '\n')
+ buf_off++;
+ }
+ else
+ buf_off = 0;
+
+ kparms = p12_parse ((unsigned char*)buf+buf_off, buflen-buf_off,
+ (pw=get_passphrase (2)),
+ import_p12_cert_cb, NULL);
+ release_passphrase (pw);
+ xfree (buf);
+ if (!kparms)
+ {
+ log_error ("error parsing or decrypting the PKCS-12 file\n");
+ return;
+ }
+ for (i=0; kparms[i]; i++)
+ ;
+ if (i != 8)
+ {
+ log_error ("invalid structure of private key\n");
+ return;
+ }
+
+
+/* print_mpi (" n", kparms[0]); */
+/* print_mpi (" e", kparms[1]); */
+/* print_mpi (" d", kparms[2]); */
+/* print_mpi (" p", kparms[3]); */
+/* print_mpi (" q", kparms[4]); */
+/* print_mpi ("dmp1", kparms[5]); */
+/* print_mpi ("dmq1", kparms[6]); */
+/* print_mpi (" u", kparms[7]); */
+
+ sk.n = kparms[0];
+ sk.e = kparms[1];
+ sk.d = kparms[2];
+ sk.q = kparms[3];
+ sk.p = kparms[4];
+ sk.u = kparms[7];
+ if (rsa_key_check (&sk))
+ return;
+/* print_mpi (" n", sk.n); */
+/* print_mpi (" e", sk.e); */
+/* print_mpi (" d", sk.d); */
+/* print_mpi (" p", sk.p); */
+/* print_mpi (" q", sk.q); */
+/* print_mpi (" u", sk.u); */
+
+ /* Create an S-expresion from the parameters. */
+ rc = gcry_sexp_build (&s_key, NULL,
+ "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+ sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL);
+ for (i=0; i < 8; i++)
+ gcry_mpi_release (kparms[i]);
+ gcry_free (kparms);
+ if (rc)
+ {
+ log_error ("failed to created S-expression from key: %s\n",
+ gpg_strerror (rc));
+ return;
+ }
+
+ /* Compute the keygrip. */
+ if (!gcry_pk_get_keygrip (s_key, grip))
+ {
+ log_error ("can't calculate keygrip\n");
+ return;
+ }
+ log_info ("keygrip: ");
+ for (i=0; i < 20; i++)
+ log_printf ("%02X", grip[i]);
+ log_printf ("\n");
+
+ /* Convert to canonical encoding. */
+ buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (buflen);
+ key = gcry_xmalloc_secure (buflen);
+ buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, key, buflen);
+ assert (buflen);
+ gcry_sexp_release (s_key);
+
+ pw = get_passphrase (4);
+ rc = agent_protect (key, pw, &result, &resultlen);
+ 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);
+ }
+
+ if (opt_store)
+ store_private_key (grip, result, resultlen, opt_force);
+ else
+ fwrite (result, resultlen, 1, stdout);
+
+ xfree (result);
+}
+
+
+
+static gcry_mpi_t *
+sexp_to_kparms (gcry_sexp_t sexp)
+{
+ gcry_sexp_t list, l2;
+ const char *name;
+ const char *s;
+ size_t n;
+ int i, idx;
+ const char *elems;
+ gcry_mpi_t *array;
+
+ list = gcry_sexp_find_token (sexp, "private-key", 0 );
+ if(!list)
+ return NULL;
+ l2 = gcry_sexp_cadr (list);
+ gcry_sexp_release (list);
+ list = l2;
+ name = gcry_sexp_nth_data (list, 0, &n);
+ if(!name || n != 3 || memcmp (name, "rsa", 3))
+ {
+ gcry_sexp_release (list);
+ return NULL;
+ }
+
+ /* Parameter names used with RSA. */
+ elems = "nedpqu";
+ array = xcalloc (strlen(elems) + 1, sizeof *array);
+ for (idx=0, s=elems; *s; s++, idx++ )
+ {
+ l2 = gcry_sexp_find_token (list, s, 1);
+ if (!l2)
+ {
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ return NULL; /* required parameter not found */
+ }
+ array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release (l2);
+ if (!array[idx])
+ {
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ return NULL; /* required parameter is invalid */
+ }
+ }
+
+ gcry_sexp_release (list);
+ return array;
+}
+
+
+/* Check whether STRING is a KEYGRIP, i.e has the correct length and
+ does only consist of uppercase hex characters. */
+static int
+is_keygrip (const char *string)
+{
+ int i;
+
+ for(i=0; string[i] && i < 41; i++)
+ if (!strchr("01234567890ABCDEF", string[i]))
+ return 0;
+ return i == 40;
+}
+
+
+static void
+export_p12_file (const char *fname)
+{
+ int rc;
+ gcry_mpi_t kparms[9], *kp;
+ unsigned char *key;
+ size_t keylen;
+ gcry_sexp_t private;
+ struct rsa_secret_key_s sk;
+ int i;
+ unsigned char *cert = NULL;
+ size_t certlen = 0;
+ int keytype;
+ size_t keylen_for_wipe = 0;
+ char *pw;
+
+ if ( is_keygrip (fname) )
+ {
+ char hexgrip[40+4+1];
+ char *p;
+
+ assert (strlen(fname) == 40);
+ strcpy (stpcpy (hexgrip, fname), ".key");
+
+ p = make_filename (opt_homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ key = read_key (p);
+ xfree (p);
+ }
+ else
+ key = read_key (fname);
+
+ if (!key)
+ return;
+
+ keytype = agent_private_key_type (key);
+ if (keytype == PRIVATE_KEY_PROTECTED)
+ {
+ unsigned char *tmpkey;
+ size_t tmplen;
+
+ rc = agent_unprotect (key, (pw=get_passphrase (1)),
+ NULL, &tmpkey, &tmplen);
+ release_passphrase (pw);
+ if (rc)
+ {
+ if (opt_status_msg && gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE )
+ log_info ("[PROTECT-TOOL:] bad-passphrase\n");
+ log_error ("unprotecting key `%s' failed: %s\n",
+ fname, gpg_strerror (rc));
+ xfree (key);
+ return;
+ }
+ xfree (key);
+ key = tmpkey;
+ keylen_for_wipe = tmplen;
+
+ keytype = agent_private_key_type (key);
+ }
+
+ if (keytype == PRIVATE_KEY_SHADOWED)
+ {
+ log_error ("`%s' is a shadowed private key - can't export it\n", fname);
+ wipememory (key, keylen_for_wipe);
+ xfree (key);
+ return;
+ }
+ else if (keytype != PRIVATE_KEY_CLEAR)
+ {
+ log_error ("\%s' is not a private key\n", fname);
+ wipememory (key, keylen_for_wipe);
+ xfree (key);
+ return;
+ }
+
+
+ if (opt_have_cert)
+ {
+ cert = (unsigned char*)read_file ("-", &certlen);
+ if (!cert)
+ {
+ wipememory (key, keylen_for_wipe);
+ xfree (key);
+ return;
+ }
+ }
+
+
+ if (gcry_sexp_new (&private, key, 0, 0))
+ {
+ log_error ("gcry_sexp_new failed\n");
+ wipememory (key, keylen_for_wipe);
+ xfree (key);
+ xfree (cert);
+ return;
+ }
+ wipememory (key, keylen_for_wipe);
+ xfree (key);
+
+ kp = sexp_to_kparms (private);
+ gcry_sexp_release (private);
+ if (!kp)
+ {
+ log_error ("error converting key parameters\n");
+ xfree (cert);
+ return;
+ }
+ sk.n = kp[0];
+ sk.e = kp[1];
+ sk.d = kp[2];
+ sk.p = kp[3];
+ sk.q = kp[4];
+ sk.u = kp[5];
+ xfree (kp);
+
+
+ kparms[0] = sk.n;
+ kparms[1] = sk.e;
+ kparms[2] = sk.d;
+ kparms[3] = sk.q;
+ kparms[4] = sk.p;
+ kparms[5] = gcry_mpi_snew (0); /* compute d mod (p-1) */
+ gcry_mpi_sub_ui (kparms[5], kparms[3], 1);
+ gcry_mpi_mod (kparms[5], sk.d, kparms[5]);
+ kparms[6] = gcry_mpi_snew (0); /* compute d mod (q-1) */
+ gcry_mpi_sub_ui (kparms[6], kparms[4], 1);
+ gcry_mpi_mod (kparms[6], sk.d, kparms[6]);
+ kparms[7] = sk.u;
+ kparms[8] = NULL;
+
+ pw = get_passphrase (3);
+ key = p12_build (kparms, cert, certlen, pw, opt_p12_charset, &keylen);
+ release_passphrase (pw);
+ xfree (cert);
+ for (i=0; i < 8; i++)
+ gcry_mpi_release (kparms[i]);
+ if (!key)
+ return;
+
+#ifdef HAVE_DOSISH_SYSTEM
+ setmode ( fileno (stdout) , O_BINARY );
+#endif
+ fwrite (key, keylen, 1, stdout);
+ xfree (key);
+}
+
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int cmd = 0;
+ const char *fname;
+
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ log_set_prefix ("gpg-protect-tool", 1);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems ();
+
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("%s is too old (need %s, have %s)\n"), "libgcrypt",
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ setup_libgcrypt_logging ();
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+
+ opt_homedir = default_homedir ();
+
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* (do not remove the args) */
+ while (arg_parse (&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: opt_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 oP12Import: cmd = oP12Import; break;
+ case oP12Export: cmd = oP12Export; break;
+ case oP12Charset: opt_p12_charset = pargs.r.ret_str; 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;
+
+ default: pargs.err = ARGPARSE_PRINT_ERROR; break;
+ }
+ }
+ if (log_get_errorcount (0))
+ exit (2);
+
+ fname = "-";
+ if (argc == 1)
+ fname = *argv;
+ else if (argc > 1)
+ usage (1);
+
+ /* Set the information which can't be taken from envvars. */
+ gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT,
+ opt.verbose,
+ opt_homedir,
+ 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 (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 == oP12Import)
+ import_p12_file (fname);
+ else if (cmd == oP12Export)
+ export_p12_file (fname);
+ else if (cmd == oS2Kcalibration)
+ {
+ if (!opt.verbose)
+ opt.verbose++; /* We need to see something. */
+ get_standard_s2k_count ();
+ }
+ else
+ show_file (fname);
+
+ 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)
+ 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);
+ }
+}
+
+static int
+store_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force)
+{
+ char *fname;
+ estream_t fp;
+ char hexgrip[40+4+1];
+
+ bin2hex (grip, 20, hexgrip);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (opt_homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ if (force)
+ fp = es_fopen (fname, "wb");
+ else
+ {
+ if (!access (fname, F_OK))
+ {
+ if (opt_status_msg)
+ log_info ("[PROTECT-TOOL:] secretkey-exists\n");
+ if (opt_no_fail_on_exist)
+ log_info ("secret key file `%s' already exists\n", fname);
+ else
+ log_error ("secret key file `%s' already exists\n", fname);
+ xfree (fname);
+ return opt_no_fail_on_exist? 0 : -1;
+ }
+ /* FWIW: Under Windows Vista the standard fopen in the msvcrt
+ fails if the "x" GNU extension is used. */
+ fp = es_fopen (fname, "wbx");
+ }
+
+ if (!fp)
+ {
+ log_error ("can't create `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ return -1;
+ }
+
+ if (es_fwrite (buffer, length, 1, fp) != 1)
+ {
+ log_error ("error writing `%s': %s\n", fname, strerror (errno));
+ es_fclose (fp);
+ remove (fname);
+ xfree (fname);
+ return -1;
+ }
+ if (es_fclose (fp))
+ {
+ log_error ("error closing `%s': %s\n", fname, strerror (errno));
+ remove (fname);
+ xfree (fname);
+ return -1;
+ }
+ log_info ("secret key stored as `%s'\n", fname);
+
+ if (opt_status_msg)
+ log_info ("[PROTECT-TOOL:] secretkey-stored\n");
+
+ xfree (fname);
+ return 0;
+}
diff --git a/agent/protect.c b/agent/protect.c
new file mode 100644
index 0000000..d6c9641
--- /dev/null
+++ b/agent/protect.c
@@ -0,0 +1,1321 @@
+/* protect.c - Un/Protect a secret key
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002,
+ * 2003, 2007, 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 <http://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>
+#ifdef HAVE_W32_SYSTEM
+# include <windows.h>
+#else
+# include <sys/times.h>
+#endif
+
+#include "agent.h"
+
+#include "sexp-parse.h"
+
+#define PROT_CIPHER GCRY_CIPHER_AES
+#define PROT_CIPHER_STRING "aes"
+#define PROT_CIPHER_KEYLEN (128/8)
+
+
+/* A table containing the information needed to create a protected
+ private key */
+static struct {
+ const char *algo;
+ const char *parmlist;
+ int prot_from, prot_to;
+} protect_info[] = {
+ { "rsa", "nedpqu", 2, 5 },
+ { "dsa", "pqgyx", 4, 4 },
+ { "elg", "pgyx", 3, 3 },
+ { NULL }
+};
+
+
+/* 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
+ GetProcessTimes (GetCurrentProcess (),
+ &data->creation_time, &data->exit_time,
+ &data->kernel_time, &data->user_time);
+#else
+ struct tms tmp;
+
+ times (&tmp);
+ data->ticks = tmp.tms_utime;
+#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)*10000000);
+#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 about 100ms 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 > 100)
+ break;
+ }
+
+ count = (unsigned long)(((double)count / ms) * 100);
+ count /= 1024;
+ count *= 1024;
+ if (count < 65536)
+ count = 65536;
+
+ if (opt.verbose)
+ {
+ ms = calibrate_s2k_count_one (count);
+ log_info ("S2K calibration: %lu iterations for %lums\n", count, ms);
+ }
+
+ return count;
+}
+
+
+
+/* Return the standard S2K count. */
+unsigned long
+get_standard_s2k_count (void)
+{
+ static unsigned long count;
+
+ if (!count)
+ count = calibrate_s2k_count ();
+
+ /* Enforce a lower limit. */
+ return count < 65536 ? 65536 : count;
+}
+
+
+
+
+/* Calculate the MIC for a private key S-Exp. SHA1HASH should point to
+ a 20 byte buffer. This function is suitable for any algorithms. */
+static int
+calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
+{
+ const unsigned char *hash_begin, *hash_end;
+ const unsigned char *s;
+ size_t n;
+
+ 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"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ hash_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* skip over 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 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 *protbegin, size_t protlen,
+ const char *passphrase, const unsigned char *sha1hash,
+ unsigned char **result, size_t *resultlen)
+{
+ gcry_cipher_hd_t hd;
+ const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc";
+ int blklen, enclen, outlen;
+ unsigned char *iv = NULL;
+ int rc;
+ char *outbuf = NULL;
+ char *p;
+ int saltpos, ivpos, encpos;
+
+ *resultlen = 0;
+ *result = NULL;
+
+ rc = gcry_cipher_open (&hd, PROT_CIPHER, 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, so 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. */
+ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+ outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
+ enclen = outlen/blklen * blklen;
+ outbuf = gcry_malloc_secure (outlen);
+ if (!outbuf)
+ rc = out_of_core ();
+ if (!rc)
+ {
+ /* Allocate random bytes to be used as IV, padding and s2k salt. */
+ iv = xtrymalloc (blklen*2+8);
+ if (!iv)
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ else
+ {
+ gcry_create_nonce (iv, blklen*2+8);
+ rc = gcry_cipher_setiv (hd, iv, blklen);
+ }
+ }
+ 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, iv+2*blklen,
+ get_standard_s2k_count (), key, keylen);
+ if (!rc)
+ rc = gcry_cipher_setkey (hd, key, keylen);
+ xfree (key);
+ }
+ }
+ if (!rc)
+ {
+ p = outbuf;
+ *p++ = '(';
+ *p++ = '(';
+ memcpy (p, protbegin, protlen);
+ p += protlen;
+ memcpy (p, ")(4:hash4:sha120:", 17);
+ p += 17;
+ memcpy (p, sha1hash, 20);
+ p += 20;
+ *p++ = ')';
+ *p++ = ')';
+ memcpy (p, iv+blklen, blklen);
+ p += blklen;
+ assert ( p - outbuf == outlen);
+ rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
+ }
+ gcry_cipher_close (hd);
+ if (rc)
+ {
+ xfree (iv);
+ xfree (outbuf);
+ return rc;
+ }
+
+ /* 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", 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,
+ blklen, &ivpos, 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, iv+2*blklen, 8);
+ memcpy (p+ivpos, iv, blklen);
+ memcpy (p+encpos, outbuf, enclen);
+ xfree (iv);
+ xfree (outbuf);
+ return 0;
+}
+
+
+
+/* Protect the key encoded in canonical format in PLAINKEY. We assume
+ a valid S-Exp here. */
+int
+agent_protect (const unsigned char *plainkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen)
+{
+ int rc;
+ 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;
+ unsigned char hashvalue[20];
+ char timestamp_exp[35];
+ unsigned char *protected;
+ size_t protectedlen;
+ int depth = 0;
+ unsigned char *p;
+ gcry_md_hd_t md;
+
+ /* Create an S-expression with the procted-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);
+
+ prot_begin = prot_end = NULL;
+ for (i=0; (c=protect_info[infidx].parmlist[i]); i++)
+ {
+ if (i == protect_info[infidx].prot_from)
+ 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)
+ 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 == protect_info[infidx].prot_to)
+ 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-exp */
+ assert (depth == 1);
+ rc = sskip (&s, &depth);
+ if (rc)
+ return rc;
+ assert (!depth);
+ real_end = s-1;
+
+
+ /* Hash the stuff. Because the timestamp_exp won't get protected,
+ we can't simply hash a continuous buffer but need to use several
+ md_writes. */
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 );
+ if (rc)
+ return rc;
+ gcry_md_write (md, hash_begin, hash_end - hash_begin);
+ gcry_md_write (md, timestamp_exp, 35);
+ gcry_md_write (md, ")", 1);
+ memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20);
+ gcry_md_close (md);
+
+ rc = do_encryption (prot_begin, prot_end - prot_begin + 1,
+ passphrase, hashvalue,
+ &protected, &protectedlen);
+ if (rc)
+ return rc;
+
+ /* Now create the protected version of the key. Note that the 10
+ extra bytes are for 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 int
+do_decryption (const unsigned char *protected, size_t protectedlen,
+ const char *passphrase,
+ const unsigned char *s2ksalt, unsigned long s2kcount,
+ const unsigned char *iv, size_t ivlen,
+ unsigned char **result)
+{
+ int rc = 0;
+ int blklen;
+ gcry_cipher_hd_t hd;
+ unsigned char *outbuf;
+ size_t reallen;
+
+ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+ if (protectedlen < 4 || (protectedlen%blklen))
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+
+ rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
+ GCRY_CIPHER_SECURE);
+ if (rc)
+ return rc;
+
+ outbuf = gcry_malloc_secure (protectedlen);
+ if (!outbuf)
+ rc = out_of_core ();
+ if (!rc)
+ rc = gcry_cipher_setiv (hd, iv, ivlen);
+ 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, s2kcount, key, keylen);
+ if (!rc)
+ rc = gcry_cipher_setkey (hd, key, keylen);
+ xfree (key);
+ }
+ }
+ if (!rc)
+ rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
+ protected, protectedlen);
+ gcry_cipher_close (hd);
+ if (rc)
+ {
+ xfree (outbuf);
+ return rc;
+ }
+ /* Do a quick check first. */
+ 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. 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 int
+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 (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 (*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;
+ /* End intermezzo */
+
+ /* 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 protocted_at unless this is NULL. */
+int
+agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
+ gnupg_isotime_t protected_at,
+ unsigned char **result, size_t *resultlen)
+{
+ 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;
+ const unsigned char *prot_begin;
+ unsigned char *cleartext = NULL; /* Just to avoid gcc warning. */
+ 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);
+ 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 */
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"))
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
+ 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 (n != 16) /* Wrong blocksize for IV (we support only aes-128). */
+ 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);
+
+ rc = do_decryption (s, n,
+ passphrase, s2ksalt, s2kcount,
+ iv, 16,
+ &cleartext);
+ if (rc)
+ return rc;
+
+ rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
+ 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;
+
+ 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 tha 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. */
+int
+agent_private_key_type (const unsigned char *privatekey)
+{
+ const unsigned char *s;
+ size_t n;
+
+ 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"))
+ 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)
+{
+ int rc;
+ gcry_md_hd_t md;
+ int pass, i;
+ int used = 0;
+ int pwlen = strlen (passphrase);
+
+ if ( (s2kmode != 0 && s2kmode != 1 && s2kmode != 3)
+ || !hashalgo || !keylen || !key || !passphrase)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if ((s2kmode == 1 ||s2kmode == 3) && !s2ksalt)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ rc = gcry_md_open (&md, hashalgo, GCRY_MD_FLAG_SECURE);
+ if (rc)
+ return rc;
+
+ for (pass=0; used < keylen; pass++)
+ {
+ if (pass)
+ {
+ gcry_md_reset (md);
+ for (i=0; i < pass; i++) /* preset the hash context */
+ gcry_md_putc (md, 0);
+ }
+
+ if (s2kmode == 1 || s2kmode == 3)
+ {
+ int len2 = pwlen + 8;
+ unsigned long count = len2;
+
+ if (s2kmode == 3)
+ {
+ count = s2kcount;
+ if (count < len2)
+ count = len2;
+ }
+
+ while (count > len2)
+ {
+ gcry_md_write (md, s2ksalt, 8);
+ gcry_md_write (md, passphrase, pwlen);
+ count -= len2;
+ }
+ if (count < 8)
+ gcry_md_write (md, s2ksalt, count);
+ else
+ {
+ gcry_md_write (md, s2ksalt, 8);
+ count -= 8;
+ gcry_md_write (md, passphrase, count);
+ }
+ }
+ else
+ gcry_md_write (md, passphrase, pwlen);
+
+ gcry_md_final (md);
+ i = gcry_md_get_algo_dlen (hashalgo);
+ if (i > keylen - used)
+ i = keylen - used;
+ memcpy (key+used, gcry_md_read (md, hashalgo), i);
+ used += i;
+ }
+ gcry_md_close(md);
+ return 0;
+}
+
+
+
+
+/* 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
+ "ti-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 */
+int
+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)
+{
+ const unsigned char *s;
+ size_t n;
+
+ if (r_hexsn)
+ *r_hexsn = NULL;
+ if (r_idstr)
+ *r_idstr = NULL;
+
+ 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;
+ }
+
+ return 0;
+}
+
diff --git a/agent/t-protect.c b/agent/t-protect.c
new file mode 100644
index 0000000..0e29caf
--- /dev/null
+++ b/agent/t-protect.c
@@ -0,0 +1,310 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#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
+ {
+ 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 },
+
+ /* 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);
+ if (gpg_err_code (ret) != specs[i].ret_expected)
+ {
+ printf ("agent_protect() returned `%i/%s'; expected `%i/%s'\n",
+ 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
+ "ti-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) */
+}
+
+
+
+
+int
+main (int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ 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 ();
+
+ return 0;
+}
diff --git a/agent/trans.c b/agent/trans.c
new file mode 100644
index 0000000..9e48889
--- /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 <http://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..be5406b
--- /dev/null
+++ b/agent/trustlist.c
@@ -0,0 +1,763 @@
+/* trustlist.c - Maintain the list of trusted keys
+ * Copyright (C) 2002, 2004, 2006, 2007, 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 <http://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 <pth.h>
+
+#include "agent.h"
+#include <assuan.h> /* fixme: need a way to avoid assuan calls here */
+#include "i18n.h"
+#include "estream.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 pth_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;
+
+ if (!initialized)
+ {
+ if (!pth_mutex_init (&trusttable_lock))
+ log_fatal ("error initializing mutex: %s\n", strerror (errno));
+ initialized = 1;
+ }
+}
+
+
+
+
+static void
+lock_trusttable (void)
+{
+ if (!pth_mutex_acquire (&trusttable_lock, 0, NULL))
+ log_fatal ("failed to acquire mutex in %s\n", __FILE__);
+}
+
+static void
+unlock_trusttable (void)
+{
+ if (!pth_mutex_release (&trusttable_lock))
+ log_fatal ("failed to release mutex in %s\n", __FILE__);
+}
+
+
+
+static gpg_error_t
+read_one_trustfile (const char *fname, int allow_include,
+ trustitem_t **addr_of_table,
+ size_t *addr_of_tablesize,
+ int *addr_of_tableidx)
+{
+ gpg_error_t err = 0;
+ FILE *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 = fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ while (fgets (line, DIM(line)-1, fp))
+ {
+ lnr++;
+
+ if (!*line || line[strlen(line)-1] != '\n')
+ {
+ /* Eat until end of line. */
+ while ( (c=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[strlen(line)-1] = 0; /* Chop the LF. */
+
+ /* 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;
+
+ if (!allow_include)
+ {
+ log_error (_("statement \"%s\" ignored in `%s', line %d\n"),
+ "include-default", fname, lnr);
+ continue;
+ }
+ /* fixme: Should check for trailing garbage. */
+
+ etcname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL);
+ if ( !strcmp (etcname, fname) ) /* Same file. */
+ log_info (_("statement \"%s\" ignored in `%s', line %d\n"),
+ "include-default", fname, lnr);
+ else if ( access (etcname, F_OK) && errno == 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, 0,
+ &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 && !feof (fp) )
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error reading `%s', line %d: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ }
+
+ leave:
+ if (fp)
+ 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. */
+static gpg_error_t
+read_trustfiles (void)
+{
+ gpg_error_t err;
+ trustitem_t *table, *ti;
+ int tableidx;
+ size_t tablesize;
+ char *fname;
+ int allow_include = 1;
+
+ tablesize = 20;
+ table = xtrycalloc (tablesize, sizeof *table);
+ if (!table)
+ return gpg_error_from_syserror ();
+ tableidx = 0;
+
+ fname = make_filename (opt.homedir, "trustlist.txt", NULL);
+ if ( access (fname, F_OK) )
+ {
+ if ( errno == ENOENT )
+ ; /* Silently ignore a non-existing trustfile. */
+ else
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err));
+ }
+ xfree (fname);
+ fname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL);
+ allow_include = 0;
+ }
+ err = read_one_trustfile (fname, allow_include,
+ &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. */
+ lock_trusttable ();
+ xfree (trusttable);
+ trusttable = NULL;
+ trusttablesize = 0;
+ unlock_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;
+ }
+
+ lock_trusttable ();
+ xfree (trusttable);
+ trusttable = ti;
+ trusttablesize = tableidx;
+ unlock_trusttable ();
+ return 0;
+}
+
+
+
+/* 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)
+{
+ gpg_error_t err;
+ trustitem_t *ti;
+ size_t len;
+ unsigned char fprbin[20];
+
+ if (r_disabled)
+ *r_disabled = 0;
+
+ if ( hexcolon2bin (fpr, fprbin, 20) < 0 )
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ if (!trusttable)
+ {
+ err = read_trustfiles ();
+ if (err)
+ {
+ log_error (_("error reading list of trusted root certificates\n"));
+ return err;
+ }
+ }
+
+ 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;
+
+ if (ti->flags.relax)
+ {
+ err = agent_write_status (ctrl,
+ "TRUSTLISTFLAG", "relax",
+ NULL);
+ if (err)
+ return err;
+ }
+ else if (ti->flags.cm)
+ {
+ err = agent_write_status (ctrl,
+ "TRUSTLISTFLAG", "cm",
+ NULL);
+ if (err)
+ return err;
+ }
+ return ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
+ }
+ }
+ return gpg_error (GPG_ERR_NOT_TRUSTED);
+}
+
+
+/* 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;
+
+ if (!trusttable)
+ {
+ err = read_trustfiles ();
+ if (err)
+ {
+ log_error (_("error reading list of trusted root certificates\n"));
+ return err;
+ }
+ }
+
+ if (trusttable)
+ {
+ /* We need to lock the table because the scheduler may interrupt
+ assuan_send_data and an other thread may then re-read the table. */
+ lock_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 want 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;
+ 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 priviliges 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 (opt.homedir, "trustlist.txt", NULL);
+ if ( access (fname, W_OK) && errno != 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. */
+ _("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, _("Yes"), _("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. */
+ _("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, _("Correct"), _("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 ();
+ agent_reload_trustlist ();
+ if (!agent_istrusted (ctrl, fpr, &is_disabled) || is_disabled)
+ {
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
+ }
+
+ fname = make_filename (opt.homedir, "trustlist.txt", NULL);
+ if ( access (fname, F_OK) && errno == ENOENT)
+ {
+ fp = es_fopen (fname, "wx");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ 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+");
+ 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\n", yes_i_trust?"":"!", fprformatted, flag);
+ if (es_ferror (fp))
+ err = gpg_error_from_syserror ();
+
+ if (es_fclose (fp))
+ err = gpg_error_from_syserror ();
+
+ agent_reload_trustlist ();
+ xfree (fname);
+ unlock_trusttable ();
+ xfree (fprformatted);
+ xfree (nameformatted);
+ 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 ();
+ xfree (trusttable);
+ trusttable = NULL;
+ trusttablesize = 0;
+ unlock_trusttable ();
+ bump_key_eventcounter ();
+}