This commit was manufactured by cvs2svn to create branch
authorRepo Admin <nobody@gnupg.org>
Tue, 5 Aug 2003 17:11:04 +0000 (17:11 +0000)
committerRepo Admin <nobody@gnupg.org>
Tue, 5 Aug 2003 17:11:04 +0000 (17:11 +0000)
'GNUPG-1-9-BRANCH'.

76 files changed:
agent/ChangeLog [new file with mode: 0644]
agent/Makefile.am [new file with mode: 0644]
agent/agent.h [new file with mode: 0644]
agent/call-scd.c [new file with mode: 0644]
agent/command.c [new file with mode: 0644]
agent/divert-scd.c [new file with mode: 0644]
agent/findkey.c [new file with mode: 0644]
agent/genkey.c [new file with mode: 0644]
agent/gpg-agent.c [new file with mode: 0644]
agent/learncard.c [new file with mode: 0644]
agent/minip12.c [new file with mode: 0644]
agent/minip12.h [new file with mode: 0644]
agent/pkdecrypt.c [new file with mode: 0644]
agent/pksign.c [new file with mode: 0644]
agent/protect-tool.c [new file with mode: 0644]
agent/protect.c [new file with mode: 0644]
agent/simple-pwquery.c [new file with mode: 0644]
common/ChangeLog [new file with mode: 0644]
common/Makefile.am [new file with mode: 0644]
common/README [new file with mode: 0644]
common/errors.h [new file with mode: 0644]
common/gettime.c [new file with mode: 0644]
common/iobuf.c [new file with mode: 0644]
common/iobuf.h [new file with mode: 0644]
common/maperror.c [new file with mode: 0644]
common/membuf.c [new file with mode: 0644]
common/membuf.h [new file with mode: 0644]
common/miscellaneous.c [new file with mode: 0644]
common/simple-pwquery.c [new file with mode: 0644]
common/simple-pwquery.h [new file with mode: 0644]
common/ttyio.c [new file with mode: 0644]
common/ttyio.h [new file with mode: 0644]
common/util.h [new file with mode: 0644]
common/yesno.c [new file with mode: 0644]
kbx/Makefile.am [new file with mode: 0644]
kbx/kbxutil.c [new file with mode: 0644]
kbx/keybox-blob.c [new file with mode: 0644]
scd/ChangeLog [new file with mode: 0644]
scd/Makefile.am [new file with mode: 0644]
scd/apdu.c [new file with mode: 0644]
scd/apdu.h [new file with mode: 0644]
scd/app-common.h [new file with mode: 0644]
scd/app-openpgp.c [new file with mode: 0644]
scd/app.c [new file with mode: 0644]
scd/card-common.h [new file with mode: 0644]
scd/card-p15.c [new file with mode: 0644]
scd/card.c [new file with mode: 0644]
scd/command.c [new file with mode: 0644]
scd/iso7816.c [new file with mode: 0644]
scd/iso7816.h [new file with mode: 0644]
scd/sc-copykeys.c [new file with mode: 0644]
scd/sc-investigate.c [new file with mode: 0644]
scd/scdaemon.c [new file with mode: 0644]
scd/scdaemon.h [new file with mode: 0644]
sm/ChangeLog [new file with mode: 0644]
sm/Makefile.am [new file with mode: 0644]
sm/call-agent.c [new file with mode: 0644]
sm/call-dirmngr.c [new file with mode: 0644]
sm/certchain.c [new file with mode: 0644]
sm/certcheck.c [new file with mode: 0644]
sm/certdump.c [new file with mode: 0644]
sm/certlist.c [new file with mode: 0644]
sm/certreqgen.c [new file with mode: 0644]
sm/decrypt.c [new file with mode: 0644]
sm/delete.c [new file with mode: 0644]
sm/encrypt.c [new file with mode: 0644]
sm/export.c [new file with mode: 0644]
sm/fingerprint.c [new file with mode: 0644]
sm/gpgsm.c [new file with mode: 0644]
sm/gpgsm.h [new file with mode: 0644]
sm/import.c [new file with mode: 0644]
sm/keydb.c [new file with mode: 0644]
sm/keylist.c [new file with mode: 0644]
sm/server.c [new file with mode: 0644]
sm/sign.c [new file with mode: 0644]
sm/verify.c [new file with mode: 0644]

diff --git a/agent/ChangeLog b/agent/ChangeLog
new file mode 100644 (file)
index 0000000..10f4d45
--- /dev/null
@@ -0,0 +1,565 @@
+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 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 (file)
index 0000000..400aa2f
--- /dev/null
@@ -0,0 +1,62 @@
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+localedir = $(datadir)/locale
+INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\"
+
+bin_PROGRAMS = gpg-agent
+pkglib_PROGRAMS = gpg-protect-tool
+
+AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBGCRYPT_CFLAGS) \
+              $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS)
+
+gpg_agent_SOURCES = \
+       gpg-agent.c agent.h \
+       command.c \
+       query.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 \
+       sexp-parse.h
+
+
+gpg_agent_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \
+                $(LIBGCRYPT_LIBS) $(PTH_LIBS) $(LIBASSUAN_LIBS) \
+                -lgpg-error @INTLLIBS@
+
+gpg_protect_tool_SOURCES = \
+       protect-tool.c \
+       protect.c \
+       simple-pwquery.c simple-pwquery.h \
+       minip12.c minip12.h 
+
+gpg_protect_tool_LDADD = ../jnlib/libjnlib.a \
+        ../common/libcommon.a ../common/libsimple-pwquery.a \
+        $(LIBGCRYPT_LIBS) -lgpg-error @INTLLIBS@
+
+
diff --git a/agent/agent.h b/agent/agent.h
new file mode 100644 (file)
index 0000000..eb4f4e3
--- /dev/null
@@ -0,0 +1,226 @@
+/* agent.h - Global definitions for the agent
+ *     Copyright (C) 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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>
+#include <errno.h>
+
+#include <gcrypt.h>
+#include "../common/util.h"
+#include "../common/errors.h"
+
+/* Convenience function to be used instead of returning the old
+   GNUPG_Out_Of_Core. */
+static __inline__ gpg_error_t
+out_of_core (void)
+{
+  return gpg_error (gpg_err_code_from_errno (errno));
+}
+
+#define MAX_DIGEST_LEN 24 
+
+/* 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 */
+  const char *pinentry_program; 
+  const char *scdaemon_program; 
+  int no_grab;      /* don't let the pinentry grab the keyboard */
+  unsigned long def_cache_ttl;
+
+  int running_detached; /* we are running detached from the tty. */
+
+  int ignore_cache_for_signing;
+  int keep_tty;  /* don't switch the TTY (for pinentry) on request */
+  int keep_display;  /* don't switch the DISPLAY (for pinentry) on request */
+} 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 server_control_s {
+  struct server_local_s *server_local;
+  char *display;
+  char *ttyname;
+  char *ttytype;
+  char *lc_ctype;
+  char *lc_messages;
+  struct {
+    int algo;
+    unsigned char value[MAX_DIGEST_LEN];
+    int valuelen;
+  } digest;
+  char keygrip[20];
+  int have_keygrip;
+
+};
+typedef struct server_control_s *CTRL;
+
+
+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 (*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
+};
+
+/*-- gpg-agent.c --*/
+void agent_exit (int rc); /* also implemented in other tools */
+void agent_init_default_ctrl (struct server_control_s *ctrl);
+
+/*-- command.c --*/
+void start_command_handler (int, int);
+
+/*-- findkey.c --*/
+int agent_write_private_key (const unsigned char *grip,
+                             const void *buffer, size_t length, int force);
+gcry_sexp_t agent_key_from_file (CTRL ctrl, const unsigned char *grip,
+                                 unsigned char **shadow_info,
+                                 int ignore_cache);
+int agent_key_available (const unsigned char *grip);
+
+/*-- query.c --*/
+int agent_askpin (CTRL ctrl,
+                  const char *desc_text, struct pin_entry_info_s *pininfo);
+int agent_get_passphrase (CTRL ctrl, char **retpass,
+                          const char *desc, const char *prompt,
+                          const char *errtext);
+int agent_get_confirmation (CTRL ctrl, const char *desc, const char *ok,
+                           const char *cancel);
+
+/*-- cache.c --*/
+void agent_flush_cache (void);
+int agent_put_cache (const char *key, const char *data, int ttl);
+const char *agent_get_cache (const char *key, void **cache_id);
+void agent_unlock_cache_entry (void **cache_id);
+
+
+/*-- pksign.c --*/
+int agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache);
+
+/*-- pkdecrypt.c --*/
+int agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen,
+                     FILE *outfp);
+
+/*-- genkey.c --*/
+int agent_genkey (CTRL ctrl,
+                  const char *keyparam, size_t keyparmlen, FILE *outfp);
+int agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey);
+
+/*-- protect.c --*/
+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,
+                     unsigned char **result, size_t *resultlen);
+int agent_private_key_type (const unsigned char *privatekey);
+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);
+
+
+/*-- trustlist.c --*/
+int agent_istrusted (const char *fpr);
+int agent_listtrusted (void *assuan_context);
+int agent_marktrusted (CTRL ctrl, const char *name, const char *fpr, int flag);
+
+
+/*-- divert-scd.c --*/
+int divert_pksign (CTRL ctrl, 
+                   const unsigned char *digest, size_t digestlen, int algo,
+                   const unsigned char *shadow_info, unsigned char **r_sig);
+int divert_pkdecrypt (CTRL ctrl,
+                      const unsigned char *cipher,
+                      const unsigned char *shadow_info,
+                      char **r_buf, size_t *r_len);
+int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context);
+
+
+/*-- call-scd.c --*/
+int agent_card_learn (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 (char **r_serialno);
+int agent_card_pksign (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_pkdecrypt (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 (const char *id, char **r_buf, size_t *r_buflen);
+int agent_card_readkey (const char *id, unsigned char **r_buf);
+int agent_card_scd (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 (void *assuan_context);
+
+
+#endif /*AGENT_H*/
diff --git a/agent/call-scd.c b/agent/call-scd.c
new file mode 100644 (file)
index 0000000..14487f1
--- /dev/null
@@ -0,0 +1,661 @@
+/* call-scd.c - fork of the scdaemon to do SC operations
+ *     Copyright (C) 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* Fixme: For now we have serialized all access to the scdaemon which
+   make sense becuase the scdaemon can't handle concurrent connections
+   right now.  We should however keep a list of connections and lock
+   just that connection - it migth make sense to implemtn parts of
+   this in Assuan.*/
+
+#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 USE_GNU_PTH
+# include <pth.h>
+#endif
+
+#include "agent.h"
+#include <assuan.h>
+
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+static ASSUAN_CONTEXT scd_ctx = NULL;
+#ifdef USE_GNU_PTH
+static pth_mutex_t scd_lock = PTH_MUTEX_INIT;
+#endif
+
+/* 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 ctx;
+  int (*getpin_cb)(void *, const char *, char*, size_t);
+  void *getpin_cb_arg;
+};
+
+struct membuf {
+  size_t len;
+  size_t size;
+  char *buf;
+  int out_of_core;
+};
+
+
+\f
+/* A simple implementation of a dynamic buffer.  Use init_membuf() to
+   create a buffer, put_membuf to append bytes and get_membuf to
+   release and return the buffer.  Allocation errors are detected but
+   only returned at the final get_membuf(), this helps not to clutter
+   the code with out of core checks.  */
+
+static void
+init_membuf (struct membuf *mb, int initiallen)
+{
+  mb->len = 0;
+  mb->size = initiallen;
+  mb->out_of_core = 0;
+  mb->buf = xtrymalloc (initiallen);
+  if (!mb->buf)
+      mb->out_of_core = 1;
+}
+
+static void
+put_membuf (struct membuf *mb, const void *buf, size_t len)
+{
+  if (mb->out_of_core)
+    return;
+
+  if (mb->len + len >= mb->size)
+    {
+      char *p;
+      
+      mb->size += len + 1024;
+      p = xtryrealloc (mb->buf, mb->size);
+      if (!p)
+        {
+          mb->out_of_core = 1;
+          return;
+        }
+      mb->buf = p;
+    }
+  memcpy (mb->buf + mb->len, buf, len);
+  mb->len += len;
+}
+
+static void *
+get_membuf (struct membuf *mb, size_t *len)
+{
+  char *p;
+
+  if (mb->out_of_core)
+    {
+      xfree (mb->buf);
+      mb->buf = NULL;
+      return NULL;
+    }
+
+  p = mb->buf;
+  *len = mb->len;
+  mb->buf = NULL;
+  mb->out_of_core = 1; /* don't allow a reuse */
+  return p;
+}
+
+
+
+\f
+static int 
+unlock_scd (int rc)
+{
+#ifdef USE_GNU_PTH
+  if (!pth_mutex_release (&scd_lock))
+    {
+      log_error ("failed to release the SCD lock\n");
+      if (!rc)
+        rc = gpg_error (GPG_ERR_INTERNAL);
+    }
+#endif
+  return rc;
+}
+
+/* Fork off the SCdaemon if this has not already been done */
+static int
+start_scd (void)
+{
+  int rc;
+  const char *pgmname;
+  ASSUAN_CONTEXT ctx;
+  const char *argv[3];
+  int no_close_list[3];
+  int i;
+
+#ifdef USE_GNU_PTH
+  if (!pth_mutex_acquire (&scd_lock, 0, NULL))
+    {
+      log_error ("failed to acquire the SCD lock\n");
+      return gpg_error (GPG_ERR_INTERNAL);
+    }
+#endif
+
+  if (scd_ctx)
+    return 0; /* No need to serialize things because the agent is
+                 expected to tun as a single-thread (or may be in
+                 future using libpth) */
+
+  if (opt.verbose)
+    log_info ("no running SCdaemon - starting it\n");
+      
+  if (fflush (NULL))
+    {
+      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+      log_error ("error flushing pending output: %s\n", strerror (errno));
+      return unlock_scd (tmperr);
+    }
+
+  if (!opt.scdaemon_program || !*opt.scdaemon_program)
+    opt.scdaemon_program = GNUPG_DEFAULT_SCDAEMON;
+  if ( !(pgmname = strrchr (opt.scdaemon_program, '/')))
+    pgmname = opt.scdaemon_program;
+  else
+    pgmname++;
+
+  argv[0] = pgmname;
+  argv[1] = "--server";
+  argv[2] = NULL;
+
+  i=0;
+  if (!opt.running_detached)
+    {
+      if (log_get_fd () != -1)
+        no_close_list[i++] = log_get_fd ();
+      no_close_list[i++] = fileno (stderr);
+    }
+  no_close_list[i] = -1;
+
+  /* connect to the pinentry and perform initial handshaking */
+  rc = assuan_pipe_connect (&ctx, opt.scdaemon_program, (char**)argv,
+                            no_close_list);
+  if (rc)
+    {
+      log_error ("can't connect to the SCdaemon: %s\n",
+                 assuan_strerror (rc));
+      return unlock_scd (gpg_error (GPG_ERR_NO_SCDAEMON));
+    }
+  scd_ctx = ctx;
+  
+  if (DBG_ASSUAN)
+    log_debug ("connection to SCdaemon established\n");
+  return 0;
+}
+
+
+\f
+static AssuanError
+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 (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 ();
+  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 (scd_ctx, "LEARN --force",
+                        NULL, NULL, NULL, NULL,
+                        learn_status_cb, &parm);
+  if (rc)
+    return unlock_scd (map_assuan_err (rc));
+
+  return unlock_scd (0);
+}
+
+
+\f
+static AssuanError
+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 ASSUAN_Unexpected_Status;
+      for (n=0,s=line; hexdigitp (s); s++, n++)
+        ;
+      if (!n || (n&1)|| !(spacep (s) || !*s) )
+        return ASSUAN_Invalid_Status;
+      *serialno = xtrymalloc (n+1);
+      if (!*serialno)
+        return ASSUAN_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 (char **r_serialno)
+{
+  int rc;
+  char *serialno = NULL;
+
+  rc = start_scd ();
+  if (rc)
+    return rc;
+
+  /* Hmm, do we really need this reset - scddaemon should do this or
+     we can do this if we for some reason figure out that the
+     operation might have failed due to a missing RESET.  Hmmm, I feel
+     this is really SCdaemon's duty */
+/*    rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); */
+/*    if (rc) */
+/*      return unlock_scd (map_assuan_err (rc)); */
+
+  rc = assuan_transact (scd_ctx, "SERIALNO",
+                        NULL, NULL, NULL, NULL,
+                        get_serialno_cb, &serialno);
+  if (rc)
+    {
+      xfree (serialno);
+      return unlock_scd (map_assuan_err (rc));
+    }
+  *r_serialno = serialno;
+  return unlock_scd (0);
+}
+
+\f
+static AssuanError
+membuf_data_cb (void *opaque, const void *buffer, size_t length)
+{
+  struct membuf *data = opaque;
+
+  if (buffer)
+    put_membuf (data, buffer, length);
+  return 0;
+}
+  
+/* Handle the NEEDPIN inquiry. */
+static AssuanError
+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])))
+    {
+      log_error ("unsupported inquiry `%s'\n", line);
+      return ASSUAN_Inquire_Unknown;
+    }
+  line += 7;
+
+  pinlen = 90;
+  pin = gcry_malloc_secure (pinlen);
+  if (!pin)
+    return ASSUAN_Out_Of_Core;
+
+  rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen);
+  if (rc)
+    rc = ASSUAN_Canceled;
+  if (!rc)
+    rc = assuan_send_data (parm->ctx, pin, pinlen);
+  xfree (pin);
+
+  return rc;
+}
+
+
+
+/* Create a signature using the current card */
+int
+agent_card_pksign (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];
+  struct membuf data;
+  struct inq_needpin_s inqparm;
+  size_t len;
+  unsigned char *sigbuf;
+  size_t sigbuflen;
+
+  *r_buf = NULL;
+  rc = start_scd ();
+  if (rc)
+    return rc;
+
+  if (indatalen*2 + 50 > DIM(line))
+    return unlock_scd (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 (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc)
+    return unlock_scd (map_assuan_err (rc));
+
+  init_membuf (&data, 1024);
+  inqparm.ctx = scd_ctx;
+  inqparm.getpin_cb = getpin_cb;
+  inqparm.getpin_cb_arg = getpin_cb_arg;
+  snprintf (line, DIM(line)-1, "PKSIGN %s", keyid);
+  line[DIM(line)-1] = 0;
+  rc = assuan_transact (scd_ctx, line,
+                        membuf_data_cb, &data,
+                        inq_needpin, &inqparm,
+                        NULL, NULL);
+  if (rc)
+    {
+      xfree (get_membuf (&data, &len));
+      return unlock_scd (map_assuan_err (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;
+  *r_buf = xtrymalloc (*r_buflen);
+  if (!*r_buf)
+    {
+      gpg_error_t tmperr = out_of_core ();
+      xfree (*r_buf);
+      return unlock_scd (tmperr);
+    }
+  p = stpcpy (*r_buf, "(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 (0);
+}
+
+/* Decipher INDATA using the current card. Note that the returned value is */
+int
+agent_card_pkdecrypt (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];
+  struct membuf data;
+  struct inq_needpin_s inqparm;
+  size_t len;
+
+  *r_buf = NULL;
+  rc = start_scd ();
+  if (rc)
+    return rc;
+
+  /* FIXME: use secure memory where appropriate */
+  if (indatalen*2 + 50 > DIM(line))
+    return unlock_scd (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 (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc)
+    return unlock_scd (map_assuan_err (rc));
+
+  init_membuf (&data, 1024);
+  inqparm.ctx = scd_ctx;
+  inqparm.getpin_cb = getpin_cb;
+  inqparm.getpin_cb_arg = getpin_cb_arg;
+  snprintf (line, DIM(line)-1, "PKDECRYPT %s", keyid);
+  line[DIM(line)-1] = 0;
+  rc = assuan_transact (scd_ctx, line,
+                        membuf_data_cb, &data,
+                        inq_needpin, &inqparm,
+                        NULL, NULL);
+  if (rc)
+    {
+      xfree (get_membuf (&data, &len));
+      return unlock_scd (map_assuan_err (rc));
+    }
+  *r_buf = get_membuf (&data, r_buflen);
+  if (!*r_buf)
+    return unlock_scd (gpg_error (GPG_ERR_ENOMEM));
+
+  return unlock_scd (0);
+}
+
+
+\f
+/* Read a certificate with ID into R_BUF and R_BUFLEN. */
+int
+agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen)
+{
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+  struct membuf data;
+  size_t len;
+
+  *r_buf = NULL;
+  rc = start_scd ();
+  if (rc)
+    return rc;
+
+  init_membuf (&data, 1024);
+  snprintf (line, DIM(line)-1, "READCERT %s", id);
+  line[DIM(line)-1] = 0;
+  rc = assuan_transact (scd_ctx, line,
+                        membuf_data_cb, &data,
+                        NULL, NULL,
+                        NULL, NULL);
+  if (rc)
+    {
+      xfree (get_membuf (&data, &len));
+      return unlock_scd (map_assuan_err (rc));
+    }
+  *r_buf = get_membuf (&data, r_buflen);
+  if (!*r_buf)
+    return unlock_scd (gpg_error (GPG_ERR_ENOMEM));
+
+  return unlock_scd (0);
+}
+
+
+\f
+/* 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 (const char *id, unsigned char **r_buf)
+{
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+  struct membuf data;
+  size_t len, buflen;
+
+  *r_buf = NULL;
+  rc = start_scd ();
+  if (rc)
+    return rc;
+
+  init_membuf (&data, 1024);
+  snprintf (line, DIM(line)-1, "READKEY %s", id);
+  line[DIM(line)-1] = 0;
+  rc = assuan_transact (scd_ctx, line,
+                        membuf_data_cb, &data,
+                        NULL, NULL,
+                        NULL, NULL);
+  if (rc)
+    {
+      xfree (get_membuf (&data, &len));
+      return unlock_scd (map_assuan_err (rc));
+    }
+  *r_buf = get_membuf (&data, &buflen);
+  if (!*r_buf)
+    return unlock_scd (gpg_error (GPG_ERR_ENOMEM));
+
+  if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL))
+    {
+      xfree (*r_buf); *r_buf = NULL;
+      return unlock_scd (gpg_error (GPG_ERR_INV_VALUE));
+    }
+
+  return unlock_scd (0);
+}
+
+
+
+\f
+static AssuanError
+pass_status_thru (void *opaque, const char *line)
+{
+  ASSUAN_CONTEXT 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 AssuanError
+pass_data_thru (void *opaque, const void *buffer, size_t length)
+{
+  ASSUAN_CONTEXT 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 SCDAEMOPN.  The PIN
+   inquirey is handled inside gpg-agent. */
+int
+agent_card_scd (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;
+
+  rc = start_scd ();
+  if (rc)
+    return rc;
+
+  inqparm.ctx = scd_ctx;
+  inqparm.getpin_cb = getpin_cb;
+  inqparm.getpin_cb_arg = getpin_cb_arg;
+  rc = assuan_transact (scd_ctx, cmdline,
+                        pass_data_thru, assuan_context,
+                        inq_needpin, &inqparm,
+                        pass_status_thru, assuan_context);
+  if (rc)
+    {
+      return unlock_scd (map_assuan_err (rc));
+    }
+
+  return unlock_scd (0);
+}
+
+
diff --git a/agent/command.c b/agent/command.c
new file mode 100644 (file)
index 0000000..ed4ea6b
--- /dev/null
@@ -0,0 +1,782 @@
+/* command.c - gpg-agent command handler
+ *     Copyright (C) 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* 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 <assuan.h>
+
+#include "agent.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, ASSUAN_ ## 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 assuan_ctx;
+  int message_fd;
+  int use_cache_for_signing;
+};
+
+
+
+
+\f
+static void
+reset_notify (ASSUAN_CONTEXT ctx)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+
+  memset (ctrl->keygrip, 0, 20);
+  ctrl->have_keygrip = 0;
+  ctrl->digest.valuelen = 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)));
+}
+
+/* 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 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)
+    return set_error (Parameter_Error, "invalid hexstring");
+  if ((n&1))
+    return set_error (Parameter_Error, "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 fucntions
+   returns an error. */
+static int
+parse_keygrip (ASSUAN_CONTEXT ctx, const char *string, unsigned char *buf)
+{
+  int rc;
+  size_t n;
+  const unsigned char *p;
+
+  rc = parse_hexstring (ctx, string, &n);
+  if (rc)
+    return rc;
+  n /= 2;
+  if (n != 20)
+    return set_error (Parameter_Error, "invalid length of keygrip");
+
+  for (p=string, n=0; n < 20; p += 2, n++)
+    buf[n] = xtoi_2 (p);
+
+  return 0;
+}
+
+
+
+
+/* ISTRUSTED <hexstring_with_fingerprint>
+
+   Return OK when we have an entry with this fingerprint in our
+   trustlist */
+static int
+cmd_istrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+  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 (Parameter_Error, "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 (fpr);
+  if (!rc)
+    return 0;
+  else if (rc == -1)
+    return ASSUAN_Not_Trusted;
+  else
+    {
+      log_error ("command is_trusted failed: %s\n", gpg_strerror (rc));
+      return map_to_assuan_status (rc);
+    }
+}
+
+/* LISTTRUSTED 
+
+   List all entries from the trustlist */
+static int
+cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+  int rc = agent_listtrusted (ctx);
+  if (rc)
+    log_error ("command listtrusted failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+/* MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>
+
+   Store a new key in into the trustlist*/
+static int
+cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL 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 (Parameter_Error, "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 (Parameter_Error, "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 map_to_assuan_status (rc);
+}
+
+
+
+\f
+/* HAVEKEY <hexstring_with_keygrip>
+  
+   Return success when the secret key is available */
+static int
+cmd_havekey (ASSUAN_CONTEXT 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 ASSUAN_No_Secret_Key;
+
+  return 0;
+}
+
+
+/* SIGKEY <hexstring_with_keygrip>
+   SETKEY <hexstring_with_keygrip>
+  
+   Set the  key used for a sign or decrypt operation */
+static int
+cmd_sigkey (ASSUAN_CONTEXT ctx, char *line)
+{
+  int rc;
+  CTRL ctrl = assuan_get_pointer (ctx);
+
+  rc = parse_keygrip (ctx, line, ctrl->keygrip);
+  if (rc)
+    return rc;
+  ctrl->have_keygrip = 1;
+  return 0;
+}
+
+
+/* SETHASH <algonumber> <hexstring> 
+
+  The client can use this command to tell the server about the data
+  (which usually is a hash) to be signed. */
+static int
+cmd_sethash (ASSUAN_CONTEXT ctx, char *line)
+{
+  int rc;
+  size_t n;
+  char *p;
+  CTRL ctrl = assuan_get_pointer (ctx);
+  unsigned char *buf;
+  char *endp;
+  int algo;
+
+  /* parse the algo number and check it */
+  algo = (int)strtoul (line, &endp, 10);
+  for (line = endp; *line == ' ' || *line == '\t'; line++)
+    ;
+  if (!algo || gcry_md_test_algo (algo))
+    return set_error (Unsupported_Algorithm, NULL);
+  ctrl->digest.algo = algo;
+
+  /* parse the hash value */
+  rc = parse_hexstring (ctx, line, &n);
+  if (rc)
+    return rc;
+  n /= 2;
+  if (n != 16 && n != 20 && n != 24 && n != 32)
+    return set_error (Parameter_Error, "unsupported length of hash");
+  if (n > MAX_DIGEST_LEN)
+    return set_error (Parameter_Error, "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;
+}
+
+
+/* PKSIGN <options>
+
+   Perform the actual sign operation. Neither input nor output are
+   sensitive to eavesdropping */
+static int
+cmd_pksign (ASSUAN_CONTEXT ctx, char *line)
+{
+  int rc;
+  int ignore_cache = 0;
+  CTRL ctrl = assuan_get_pointer (ctx);
+
+  if (opt.ignore_cache_for_signing)
+    ignore_cache = 1;
+  else if (!ctrl->server_local->use_cache_for_signing)
+    ignore_cache = 1;
+
+  rc = agent_pksign (ctrl, assuan_get_data_fp (ctx), ignore_cache);
+  if (rc)
+    log_error ("command pksign failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+/* PKDECRYPT <options>
+
+   Perform the actual decrypt operation.  Input is not 
+   sensitive to eavesdropping */
+static int
+cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line)
+{
+  int rc;
+  CTRL ctrl = assuan_get_pointer (ctx);
+  char *value;
+  size_t valuelen;
+
+  /* First inquire the data to decrypt */
+  rc = assuan_inquire (ctx, "CIPHERTEXT",
+                       &value, &valuelen, MAXLEN_CIPHERTEXT);
+  if (rc)
+    return rc;
+
+  rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx));
+  xfree (value);
+  if (rc)
+    log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+/* GENKEY
+
+   Generate a new key, store the secret part and return the public
+   part.  Here is an example transaction:
+
+   C: GENKEY
+   S: INQUIRE KEYPARM
+   C: D (genkey (rsa (nbits  1024)))
+   C: END
+   S: D (public-key
+   S: D   (rsa (n 326487324683264) (e 10001)))
+   S  OK key created
+*/
+
+static int
+cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  int rc;
+  char *value;
+  size_t valuelen;
+
+  /* First inquire the parameters */
+  rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
+  if (rc)
+    return rc;
+
+  rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx));
+  xfree (value);
+  if (rc)
+    log_error ("command genkey failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+static void
+plus_to_blank (char *s)
+{
+  for (; *s; s++)
+    {
+      if (*s == '+')
+        *s = ' ';
+    }
+}
+
+/* GET_PASSPHRASE <cache_id> [<error_message> <prompt> <description>]
+
+   This function is usually used to ask for a passphrase to be used
+   for conventional encryption, but may also be used by programs which
+   need specal handling of passphrases.  This command uses a syntax
+   which helps clients to use the agent with minimum effort.  The
+   agent either returns with an error or with a OK followed by the hex
+   encoded passphrase.  Note that the length of the strings is
+   implicitly limited by the maximum length of a command.
+*/
+
+static int
+cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  int rc;
+  const char *pw;
+  char *response;
+  char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
+  char *p;
+  void *cache_marker;
+
+  /* parse the stuff */
+  for (p=line; *p == ' '; p++)
+    ;
+  cacheid = p;
+  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 garbage */
+            }
+        }
+    }
+  if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+    return set_error (Parameter_Error, "invalid length of cacheID");
+  if (!desc)
+    return set_error (Parameter_Error, "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;
+
+  /* Note: we store the hexified versions in the cache. */
+  pw = cacheid ? agent_get_cache (cacheid, &cache_marker) : NULL;
+  if (pw)
+    {
+      assuan_begin_confidential (ctx);
+      rc = assuan_set_okay_line (ctx, pw);
+      agent_unlock_cache_entry (&cache_marker);
+    }
+  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);
+
+      rc = agent_get_passphrase (ctrl, &response, desc, prompt, errtext);
+      if (!rc)
+        {
+          if (cacheid)
+            agent_put_cache (cacheid, response, 0);
+          assuan_begin_confidential (ctx);
+          rc = assuan_set_okay_line (ctx, response);
+          xfree (response);
+        }
+    }
+
+  if (rc)
+    log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+/* CLEAR_PASSPHRASE <cache_id>
+
+   may be used to invalidate the cache entry for a passphrase.  The
+   function returns with OK even when there is no cached passphrase.
+*/
+
+static int
+cmd_clear_passphrase (ASSUAN_CONTEXT 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 (Parameter_Error, "invalid length of cacheID");
+
+  agent_put_cache (cacheid, NULL, 0);
+  return 0;
+}
+
+\f
+/* LEARN [--send]
+
+   Learn something about the currently inserted smartcard.  With
+   --send the new certificates are send back.  */
+static int
+cmd_learn (ASSUAN_CONTEXT ctx, char *line)
+{
+  int rc;
+
+  rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL);
+  if (rc)
+    log_error ("command learn failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+\f
+/* PASSWD <hexstring_with_keygrip>
+  
+   Change the passphrase/PID for the key identified by keygrip in LINE. */
+static int
+cmd_passwd (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL 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)
+    return rc; /* we can't jump to leave because this is already an
+                  Assuan error code. */
+
+  s_skey = agent_key_from_file (ctrl, grip, &shadow_info, 1);
+  if (!s_skey && !shadow_info)
+    rc = gpg_error (GPG_ERR_NO_SECKEY);
+  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);
+
+  gcry_sexp_release (s_skey);
+  xfree (shadow_info);
+  if (rc)
+    log_error ("command passwd failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+\f
+/* SCD <commands to pass to the scdaemon>
+  
+   This is a general quote command to redirect everything to the
+   SCDAEMON. */
+static int
+cmd_scd (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  int rc;
+
+  rc = divert_generic_cmd (ctrl, line, ctx);
+
+  return map_to_assuan_status (rc);
+}
+
+
+\f
+static int
+option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
+{
+   CTRL ctrl = assuan_get_pointer (ctx);
+
+  if (!strcmp (key, "display"))
+    {
+      if (ctrl->display)
+        free (ctrl->display);
+      ctrl->display = strdup (value);
+      if (!ctrl->display)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "ttyname"))
+    {
+      if (!opt.keep_tty)
+        {
+          if (ctrl->ttyname)
+            free (ctrl->ttyname);
+          ctrl->ttyname = strdup (value);
+          if (!ctrl->ttyname)
+            return ASSUAN_Out_Of_Core;
+        }
+    }
+  else if (!strcmp (key, "ttytype"))
+    {
+      if (!opt.keep_tty)
+        {
+          if (ctrl->ttytype)
+            free (ctrl->ttytype);
+          ctrl->ttytype = strdup (value);
+          if (!ctrl->ttytype)
+            return ASSUAN_Out_Of_Core;
+        }
+    }
+  else if (!strcmp (key, "lc-ctype"))
+    {
+      if (ctrl->lc_ctype)
+        free (ctrl->lc_ctype);
+      ctrl->lc_ctype = strdup (value);
+      if (!ctrl->lc_ctype)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "lc-messages"))
+    {
+      if (ctrl->lc_messages)
+        free (ctrl->lc_messages);
+      ctrl->lc_messages = strdup (value);
+      if (!ctrl->lc_messages)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "use-cache-for-signing"))
+    ctrl->server_local->use_cache_for_signing = *value? atoi (value) : 0;
+  else
+    return ASSUAN_Invalid_Option;
+
+  return 0;
+}
+
+\f
+/* Tell the assuan library about our commands */
+static int
+register_commands (ASSUAN_CONTEXT ctx)
+{
+  static struct {
+    const char *name;
+    int (*handler)(ASSUAN_CONTEXT, char *line);
+  } table[] = {
+    { "ISTRUSTED",      cmd_istrusted },
+    { "HAVEKEY",        cmd_havekey },
+    { "SIGKEY",         cmd_sigkey },
+    { "SETKEY",         cmd_sigkey },
+    { "SETHASH",        cmd_sethash },
+    { "PKSIGN",         cmd_pksign },
+    { "PKDECRYPT",      cmd_pkdecrypt },
+    { "GENKEY",         cmd_genkey },
+    { "GET_PASSPHRASE", cmd_get_passphrase },
+    { "CLEAR_PASSPHRASE", cmd_clear_passphrase },
+    { "LISTTRUSTED",    cmd_listtrusted },
+    { "MARKTRUSTED",    cmd_marktrusted },
+    { "LEARN",          cmd_learn },
+    { "PASSWD",         cmd_passwd },
+    { "INPUT",          NULL }, 
+    { "OUTPUT",         NULL }, 
+    { "SCD",            cmd_scd },
+    { NULL }
+  };
+  int i, rc;
+
+  for (i=0; table[i].name; i++)
+    {
+      rc = assuan_register_command (ctx, table[i].name, table[i].handler);
+      if (rc)
+        return rc;
+    } 
+  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 */
+void
+start_command_handler (int listen_fd, int fd)
+{
+  int rc;
+  ASSUAN_CONTEXT ctx;
+  struct server_control_s ctrl;
+
+  memset (&ctrl, 0, sizeof ctrl);
+  agent_init_default_ctrl (&ctrl);
+  
+  if (listen_fd == -1 && fd == -1)
+    {
+      int filedes[2];
+
+      filedes[0] = 0;
+      filedes[1] = 1;
+      rc = assuan_init_pipe_server (&ctx, filedes);
+    }
+  else if (listen_fd != -1)
+    {
+      rc = assuan_init_socket_server (&ctx, listen_fd);
+    }
+  else 
+    {
+      rc = assuan_init_connected_socket_server (&ctx, fd);
+    }
+  if (rc)
+    {
+      log_error ("failed to initialize the server: %s\n",
+                 assuan_strerror(rc));
+      agent_exit (2);
+    }
+  rc = register_commands (ctx);
+  if (rc)
+    {
+      log_error ("failed to register commands with Assuan: %s\n",
+                 assuan_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;
+
+  if (DBG_ASSUAN)
+    assuan_set_log_stream (ctx, log_get_stream ());
+
+  for (;;)
+    {
+      rc = assuan_accept (ctx);
+      if (rc == -1)
+        {
+          break;
+        }
+      else if (rc)
+        {
+          log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
+          break;
+        }
+      
+      rc = assuan_process (ctx);
+      if (rc)
+        {
+          log_info ("Assuan processing failed: %s\n", assuan_strerror (rc));
+          continue;
+        }
+    }
+
+
+  assuan_deinit_server (ctx);
+  if (ctrl.display)
+    free (ctrl.display);
+  if (ctrl.ttyname)
+    free (ctrl.ttyname);
+  if (ctrl.ttytype)
+    free (ctrl.ttytype);
+  if (ctrl.lc_ctype)
+    free (ctrl.lc_ctype);
+  if (ctrl.lc_messages)
+    free (ctrl.lc_messages);
+}
+
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
new file mode 100644 (file)
index 0000000..69f1844
--- /dev/null
@@ -0,0 +1,319 @@
+/* divert-scd.c - divert operations to the scdaemon 
+ *     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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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 "sexp-parse.h"
+#include "i18n.h"
+
+
+static int
+ask_for_card (CTRL ctrl, const unsigned char *shadow_info, char **r_kid)
+{
+  int rc, i;
+  const unsigned char *s;
+  size_t n;
+  char *serialno;
+  int no_card = 0;
+  char *desc;
+  char *want_sn, *want_kid;
+  int want_sn_displen;
+
+  *r_kid = 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);
+  want_sn = xtrymalloc (n*2+1);
+  if (!want_sn)
+    return out_of_core ();
+  for (i=0; i < n; i++)
+    sprintf (want_sn+2*i, "%02X", s[i]);
+  s += n;
+  /* We assume that a 20 byte serial number is a standard one which
+     seems to have the property to have a zero in the last nibble.  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--;
+
+  n = snext (&s);
+  if (!n)
+    return gpg_error (GPG_ERR_INV_SEXP);
+  want_kid = xtrymalloc (n+1);
+  if (!want_kid)
+    {
+      gpg_error_t tmperr = out_of_core ();
+      xfree (want_sn);
+      return tmperr;
+    }
+  memcpy (want_kid, s, n);
+  want_kid[n] = 0;
+
+  for (;;)
+    {
+      rc = agent_card_serialno (&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 accesing 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);
+              free (desc);
+            }
+        }
+      if (rc)
+        {
+          xfree (want_sn);
+          xfree (want_kid);
+          return rc;
+        }
+    }
+}
+
+
+/* Put the DIGEST into an DER encoded comtainer 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)
+{
+  byte *frame;
+  byte asn[100];
+  size_t asnlen;
+
+  asnlen = DIM(asn);
+  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 */
+static int 
+getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
+{
+  struct pin_entry_info_s *pi;
+  int rc;
+  char *desc;
+  CTRL ctrl = opaque;
+
+  if (maxbuf < 2)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+
+  /* 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. */
+  pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+  pi->max_length = maxbuf-1;
+  pi->min_digits = 0;  /* we want a real passphrase */
+  pi->max_digits = 8;
+  pi->max_tries = 3;
+
+  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, pi);
+  free (desc);
+  if (!rc)
+    {
+      strncpy (buf, pi->pin, maxbuf-1);
+      buf[maxbuf-1] = 0;
+    }
+  xfree (pi);
+  return rc;
+}
+
+
+
+
+int
+divert_pksign (CTRL 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;
+  char *sigval;
+  unsigned char *data;
+  size_t ndata;
+
+  rc = ask_for_card (ctrl, shadow_info, &kid);
+  if (rc)
+    return rc;
+
+  rc = encode_md_for_card (digest, digestlen, algo, 
+                           &data, &ndata);
+  if (rc)
+    return rc;
+
+  rc = agent_card_pksign (kid, getpin_cb, ctrl,
+                          data, ndata, &sigval, &siglen);
+  if (!rc)
+    *r_sig = sigval;
+  xfree (data);
+  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 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 (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 ctrl, const char *cmdline, void *assuan_context)
+{
+  return agent_card_scd (cmdline, getpin_cb, ctrl, assuan_context);
+}
+
+
+
+
+
diff --git a/agent/findkey.c b/agent/findkey.c
new file mode 100644 (file)
index 0000000..db36cb1
--- /dev/null
@@ -0,0 +1,359 @@
+/* findkey.c - locate the secret key
+ *     Copyright (C) 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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 "agent.h"
+
+/* Helper to pass data to the check callback of the unprotect function. */
+struct try_unprotect_arg_s {
+  const unsigned char *protected_key;
+  unsigned char *unprotected_key;
+};
+
+
+
+int
+agent_write_private_key (const unsigned char *grip,
+                         const void *buffer, size_t length, int force)
+{
+  int i;
+  char *fname;
+  FILE *fp;
+  char hexgrip[40+4+1];
+  
+  for (i=0; i < 20; i++)
+    sprintf (hexgrip+2*i, "%02X", grip[i]);
+  strcpy (hexgrip+40, ".key");
+
+  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  if (force)
+    fp = fopen (fname, "wb");
+  else
+    {
+      int fd;
+
+      if (!access (fname, F_OK))
+      {
+        log_error ("secret key file `%s' already exists\n", fname);
+        xfree (fname);
+        return gpg_error (GPG_ERR_GENERAL);
+      }
+
+      /* 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.
+
+        The mode parameter to open is what fopen uses.  It will be
+        combined with the process' umask automatically.  */
+      fd = open (fname, O_CREAT | O_EXCL | O_RDWR,
+                S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+      if (fd < 0)
+       fp = 0;
+      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;
+    }
+
+  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;
+
+  assert (!arg->unprotected_key);
+  return agent_unprotect (arg->protected_key, pi->pin,
+                          &arg->unprotected_key, &dummy);
+}
+
+
+/* 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. */
+static int
+unprotect (CTRL ctrl,
+           unsigned char **keybuf, const unsigned char *grip, int ignore_cache)
+{
+  struct pin_entry_info_s *pi;
+  struct try_unprotect_arg_s arg;
+  int rc, i;
+  unsigned char *result;
+  size_t resultlen;
+  char hexgrip[40+1];
+  
+  for (i=0; i < 20; i++)
+    sprintf (hexgrip+2*i, "%02X", grip[i]);
+  hexgrip[40] = 0;
+
+  /* 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 (!ignore_cache)
+    {
+      void *cache_marker;
+      const char *pw = agent_get_cache (hexgrip, &cache_marker);
+      if (pw)
+        {
+          rc = agent_unprotect (*keybuf, pw, &result, &resultlen);
+          agent_unlock_cache_entry (&cache_marker);
+          if (!rc)
+            {
+              xfree (*keybuf);
+              *keybuf = result;
+              return 0;
+            }
+          rc  = 0;
+        }
+    }
+  
+  pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+  pi->max_length = 100;
+  pi->min_digits = 0;  /* we want a real passphrase */
+  pi->max_digits = 8;
+  pi->max_tries = 3;
+  pi->check_cb = try_unprotect_cb;
+  arg.protected_key = *keybuf;
+  arg.unprotected_key = NULL;
+  pi->check_cb_arg = &arg;
+
+  rc = agent_askpin (ctrl, NULL, pi);
+  if (!rc)
+    {
+      assert (arg.unprotected_key);
+      agent_put_cache (hexgrip, pi->pin, 0);
+      xfree (*keybuf);
+      *keybuf = arg.unprotected_key;
+    }
+  xfree (pi);
+  return rc;
+}
+
+
+
+/* Return the secret key as an S-Exp after locating it using the grip.
+   Returns NULL if key is not available or the operation should be
+   diverted to a token.  In the latter case shadow_info will point to
+   an allocated S-Expression with the shadow_info part from the file.
+   With IGNORE_CACHE passed as true the passphrase is not taken from
+   the cache.*/
+gcry_sexp_t
+agent_key_from_file (CTRL ctrl,
+                     const unsigned char *grip, unsigned char **shadow_info,
+                     int ignore_cache)
+{
+  int i, rc;
+  char *fname;
+  FILE *fp;
+  struct stat st;
+  unsigned char *buf;
+  size_t len, buflen, erroff;
+  gcry_sexp_t s_skey;
+  char hexgrip[40+4+1];
+  
+  if (shadow_info)
+      *shadow_info = NULL;
+
+  for (i=0; i < 20; i++)
+    sprintf (hexgrip+2*i, "%02X", grip[i]);
+  strcpy (hexgrip+40, ".key");
+
+  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  fp = fopen (fname, "rb");
+  if (!fp)
+    {
+      log_error ("can't open `%s': %s\n", fname, strerror (errno));
+      xfree (fname);
+      return NULL;
+    }
+  
+  if (fstat (fileno(fp), &st))
+    {
+      log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+      xfree (fname);
+      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));
+      xfree (fname);
+      fclose (fp);
+      xfree (buf);
+      return NULL;
+    }
+
+  rc = gcry_sexp_sscan (&s_skey, &erroff, 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 NULL;
+    }
+  len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
+  assert (len);
+  buf = xtrymalloc (len);
+  if (!buf)
+    {
+      gcry_sexp_release (s_skey);
+      return NULL;
+    }
+  len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len);
+  assert (len);
+  gcry_sexp_release (s_skey);
+
+  switch (agent_private_key_type (buf))
+    {
+    case PRIVATE_KEY_CLEAR:
+      break; /* no unprotection needed */
+    case PRIVATE_KEY_PROTECTED:
+      rc = unprotect (ctrl, &buf, grip, ignore_cache);
+      if (rc)
+        log_error ("failed to unprotect the secret key: %s\n",
+                   gpg_strerror (rc));
+      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;
+                }
+            }
+          if (rc)
+            log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+        }
+      rc = -1; /* ugly interface: we return an error but keep a value
+                  in shadow_info.  */
+      break;
+    default:
+      log_error ("invalid private key format\n");
+      rc = gpg_error (GPG_ERR_BAD_SECKEY);
+      break;
+    }
+  if (rc)
+    {
+      xfree (buf);
+      return NULL;
+    }
+
+  /* arggg FIXME: does scan support secure memory? */
+  rc = gcry_sexp_sscan (&s_skey, &erroff,
+                        buf, gcry_sexp_canon_len (buf, 0, NULL, NULL));
+  xfree (buf);
+  if (rc)
+    {
+      log_error ("failed to build S-Exp (off=%u): %s\n",
+                 (unsigned int)erroff, gpg_strerror (rc));
+      return NULL;
+    }
+
+  return s_skey;
+}
+
+/* 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 i;
+  char *fname;
+  char hexgrip[40+4+1];
+  
+  for (i=0; i < 20; i++)
+    sprintf (hexgrip+2*i, "%02X", grip[i]);
+  strcpy (hexgrip+40, ".key");
+
+  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  i = !access (fname, R_OK)? 0 : -1;
+  xfree (fname);
+  return i;
+}
+
+
+
diff --git a/agent/genkey.c b/agent/genkey.c
new file mode 100644 (file)
index 0000000..0a0577f
--- /dev/null
@@ -0,0 +1,240 @@
+/* pksign.c - Generate a keypair
+ *     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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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"
+
+static int
+store_key (gcry_sexp_t private, const char *passphrase, int force)
+{
+  int rc;
+  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;
+}
+
+/* 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 */
+  pi->cb_errtext = _("does not match - try again");
+  return -1;
+}
+
+
+
+/* Generate a new keypair according to the parameters given in
+   KEYPARAM */
+int
+agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
+              FILE *outfp) 
+{
+  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");
+
+    pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+    pi2 = pi + (sizeof *pi + 100);
+    pi->max_length = 100;
+    pi->max_tries = 3;
+    pi2->max_length = 100;
+    pi2->max_tries = 3;
+    pi2->check_cb = reenter_compare_cb;
+    pi2->check_cb_arg = pi->pin;
+
+    rc = agent_askpin (ctrl, text1, pi);
+    if (!rc)
+      rc = agent_askpin (ctrl, text2, pi2);
+    if (rc)
+      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 map_gcry_err (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 */
+  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 */
+  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);
+  if (fwrite (buf, len, 1, outfp) != 1)
+    {
+      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+      log_error ("error writing public key: %s\n", strerror (errno));
+      gcry_sexp_release (s_private);
+      gcry_sexp_release (s_public);
+      xfree (buf);
+      return tmperr;
+    }
+  gcry_sexp_release (s_public);
+  xfree (buf);
+
+  return 0;
+}
+
+
+\f
+/* Apply a new passpahrse to the key S_SKEY and store it. */
+int
+agent_protect_and_store (CTRL 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");
+
+    pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+    pi2 = pi + (sizeof *pi + 100);
+    pi->max_length = 100;
+    pi->max_tries = 3;
+    pi2->max_length = 100;
+    pi2->max_tries = 3;
+    pi2->check_cb = reenter_compare_cb;
+    pi2->check_cb_arg = pi->pin;
+
+    rc = agent_askpin (ctrl, text1, pi);
+    if (!rc)
+      rc = agent_askpin (ctrl, text2, pi2);
+    if (rc)
+      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 0;
+}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
new file mode 100644 (file)
index 0000000..675f2be
--- /dev/null
@@ -0,0 +1,1063 @@
+/* gpg-agent.c  -  The GnuPG Agent
+ *     Copyright (C) 2000, 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <signal.h>
+#ifdef USE_GNU_PTH
+# include <pth.h>
+#endif
+
+#define JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+#include <assuan.h> /* malloc hooks */
+
+#include "i18n.h"
+#include "sysutils.h"
+
+
+enum cmd_and_opt_values 
+{ aNull = 0,
+  oCsh           = 'c',
+  oQuiet         = 'q',
+  oSh            = 's',
+  oVerbose       = 'v',
+  
+  oNoVerbose = 500,
+  oOptions,
+  oDebug,
+  oDebugAll,
+  oDebugWait,
+  oNoGreeting,
+  oNoOptions,
+  oHomedir,
+  oNoDetach,
+  oNoGrab,
+  oLogFile,
+  oServer,
+  oDaemon,
+  oBatch,
+
+  oPinentryProgram,
+  oDisplay,
+  oTTYname,
+  oTTYtype,
+  oLCctype,
+  oLCmessages,
+  oScdaemonProgram,
+  oDefCacheTTL,
+  oDisablePth,
+
+  oIgnoreCacheForSigning,
+  oKeepTTY,
+  oKeepDISPLAY,
+
+aTest };
+
+
+
+static ARGPARSE_OPTS opts[] = {
+  
+  { 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_("read options from file")},
+  { oDebug,    "debug"     ,4|16, N_("set debugging flags")},
+  { oDebugAll, "debug-all" ,0, N_("enable full debugging")},
+  { 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")},
+  { oDisablePth, "disable-pth", 0, N_("do not allow multiple connections")},
+
+  { oPinentryProgram, "pinentry-program", 2 , "path to PIN Entry program" },
+  { oDisplay,    "display",     2, "set the display" },
+  { oTTYname,    "ttyname",     2, "set the tty terminal node name" },
+  { oTTYtype,    "ttytype",     2, "set the tty terminal type" },
+  { oLCctype,    "lc-ctype",    2, "set the tty LC_CTYPE value" },
+  { oLCmessages, "lc-messages", 2, "set the tty LC_MESSAGES value" },
+
+  { oScdaemonProgram, "scdaemon-program", 2 , "path to SCdaemon program" },
+  { oDefCacheTTL, "default-cache-ttl", 4,
+                                 "|N|expire cached PINs after N seconds"},
+  { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0,
+                                 "do not use the PIN cache when signing"},
+  { oKeepTTY, "keep-tty", 0,  N_("ignore requests to change the TTY")},
+  { oKeepDISPLAY, "keep-display",
+                          0, N_("ignore requests to change the X display")},
+  {0}
+};
+
+
+static volatile int caught_fatal_sig = 0;
+
+/* flag to indicate that a shutdown was requested */
+static int shutdown_pending;
+
+
+/* It is possible that we are currently running under setuid permissions */
+static int maybe_setuid = 1;
+
+/* Name of the communication socket */
+static char socket_name[128];
+
+/* 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;
+
+/* Name of a config file, which will be reread on a HUP if it is not NULL. */
+static char *config_filename;
+
+
+/* Local prototypes. */
+static void create_directories (void);
+#ifdef USE_GNU_PTH
+static void handle_connections (int listen_fd);
+#endif
+
+
+
+static const char *
+my_strusage (int level)
+{
+  const char *p;
+  switch (level)
+    {
+    case 11: p = "gpg-agent (GnuPG)";
+      break;
+    case 13: p = VERSION; break;
+    case 17: p = PRINTABLE_OS_NAME; break;
+    case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+      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;
+}
+
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+    set_gettext_file( PACKAGE );
+#else
+#ifdef ENABLE_NLS
+    setlocale (LC_ALL, "");
+    bindtextdomain (PACKAGE, LOCALEDIR);
+    textdomain (PACKAGE);
+#endif
+#endif
+}
+
+
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+  /* translate the log levels */
+  switch (level)
+    {
+    case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+    case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+    case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+    case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+    case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+    case GCRY_LOG_BUG:  level = JNLIB_LOG_BUG; break;
+    case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+    default:            level = JNLIB_LOG_ERROR; break;  
+    }
+  log_logv (level, fmt, arg_ptr);
+}
+
+
+static void
+cleanup (void)
+{
+  if (*socket_name)
+    {
+      char *p;
+
+      remove (socket_name);
+      p = strrchr (socket_name, '/');
+      if (p)
+        {
+          *p = 0;
+          rmdir (socket_name);
+          *p = '/';
+        }
+      *socket_name = 0;
+    }
+}
+
+
+static RETSIGTYPE
+cleanup_sh (int sig)
+{
+  if (caught_fatal_sig)
+    raise (sig);
+  caught_fatal_sig = 1;
+
+  /* gcry_control( GCRYCTL_TERM_SECMEM );*/
+  cleanup ();
+
+#ifndef HAVE_DOSISH_SYSTEM
+  {    /* reset action to default action and raise signal again */
+    struct sigaction nact;
+    nact.sa_handler = SIG_DFL;
+    sigemptyset( &nact.sa_mask );
+    nact.sa_flags = 0;
+    sigaction( sig, &nact, NULL);
+  }
+#endif
+  raise( sig );
+}
+
+
+/* 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. */
+static int
+parse_rereadable_options (ARGPARSE_ARGS *pargs)
+{
+  if (!pargs)
+    { /* reset mode */
+      opt.quiet = 0;
+      opt.verbose = 0;
+      opt.debug = 0;
+      opt.no_grab = 0;
+      opt.pinentry_program = NULL;
+      opt.scdaemon_program = NULL;
+      opt.def_cache_ttl = 10*60; /* default to 10 minutes */
+      opt.ignore_cache_for_signing = 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 oNoGrab: opt.no_grab = 1; break;
+      
+    case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
+    case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
+
+    case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
+      
+    case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
+
+    default:
+      return 0; /* not handled */
+    }
+  return 1; /* handled */
+}
+
+
+int
+main (int argc, char **argv )
+{
+  ARGPARSE_ARGS pargs;
+  int orig_argc;
+  int may_coredump;
+  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 disable_pth = 0;
+
+  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", 1|4); 
+  i18n_init ();
+
+  /* We need to initialize Pth before libgcrypt, because the libgcrypt
+     initialization done by gcry_check_version internally sets up its
+     mutex system.  Note that one must not link against pth if
+     USE_GNU_PTH is not defined. */
+#ifdef USE_GNU_PTH
+  if (!pth_init ())
+    {
+      log_error ("failed to initialize the Pth library\n");
+      exit (1);
+    }
+#endif /*USE_GNU_PTH*/
+
+  /* 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( _("libgcrypt is too old (need %s, have %s)\n"),
+                 NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+    }
+
+  assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+
+  gcry_set_log_handler (my_gcry_logger, NULL);
+  gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+
+  may_coredump = disable_core_dumps ();
+
+  parse_rereadable_options (NULL); /* Reset them to default values. */
+
+  shell = getenv ("SHELL");
+  if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
+    csh_style = 1;
+  
+  opt.homedir = getenv("GNUPGHOME");
+  if (!opt.homedir || !*opt.homedir)
+    opt.homedir = GNUPG_DEFAULT_HOMEDIR;
+
+
+  /* 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, 16384, 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 );
+           }
+          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))
+        continue; /* Already handled */
+      switch (pargs.r_opt)
+        {
+        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 oDisablePth: disable_pth = 1; break;
+
+        case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
+        case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
+        case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
+        case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
+        case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break;
+
+        case oKeepTTY: opt.keep_tty = 1; break;
+        case oKeepDISPLAY: opt.keep_display = 1; 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. */
+      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
+  log_info ("NOTE: this is a development version!\n");
+#endif
+
+  
+  if (atexit (cleanup))
+    {
+      log_error ("atexit failed\n");
+      cleanup ();
+      exit (1);
+    }
+
+  create_directories ();
+
+  if (debug_wait && pipe_server)
+    {
+      log_debug ("waiting for debugger - my pid is %u .....\n",
+                 (unsigned int)getpid());
+      sleep (debug_wait);
+      log_debug ("... okay\n");
+    }
+
+  if (!pipe_server && !is_daemon)
+    log_info (_("please use the option `--daemon'"
+                " to run the program in the background\n"));
+  
+#ifdef ENABLE_NLS
+  /* gpg-agent usdually does not ooutput any messages becuase 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 swicthnonly once instead of for every message and it
+     actually helps when more then one thread is active (avoids
+     required an extra copy step). */
+    bind_textdomain_codeset (PACKAGE, "UTF-8");
+#endif
+
+  /* now start with logging to a file if this is desired */
+  if (logfile)
+    {
+      log_set_file (logfile);
+      log_set_prefix (NULL, 1|2|4);
+    }
+
+  /* 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 */
+      start_command_handler (-1, -1);
+    }
+  else if (!is_daemon)
+    ;
+  else
+    { /* regular server mode */
+      int fd;
+      pid_t pid;
+      int len;
+      struct sockaddr_un serv_addr;
+      char *p;
+
+      /* Remove the DISPLAY variable so that a pinentry does not
+         default to a specific display.  There is still a default
+         display when gpg-agent weas started using --display or a
+         client requested this using an OPTION command. */
+      if (!opt.keep_display)
+        unsetenv ("DISPLAY");
+
+      *socket_name = 0;
+      snprintf (socket_name, DIM(socket_name)-1,
+                "/tmp/gpg-XXXXXX/S.gpg-agent");
+      socket_name[DIM(socket_name)-1] = 0;
+      p = strrchr (socket_name, '/');
+      if (!p)
+        BUG ();
+      *p = 0;;
+      if (!mkdtemp(socket_name))
+        {
+          log_error ("can't create directory `%s': %s\n",
+                    socket_name, strerror(errno) );
+          exit (1);
+        }
+      *p = '/';
+
+      if (strchr (socket_name, ':') )
+        {
+          log_error ("colons are not allowed in the socket name\n");
+          exit (1);
+        }
+      if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) 
+        {
+          log_error ("name of socket too long\n");
+          exit (1);
+        }
+   
+
+      fd = socket (AF_UNIX, SOCK_STREAM, 0);
+      if (fd == -1)
+        {
+          log_error ("can't create socket: %s\n", strerror(errno) );
+          exit (1);
+        }
+
+      memset (&serv_addr, 0, sizeof serv_addr);
+      serv_addr.sun_family = AF_UNIX;
+      strcpy (serv_addr.sun_path, socket_name);
+      len = (offsetof (struct sockaddr_un, sun_path)
+             + strlen(serv_addr.sun_path) + 1);
+
+      if (bind (fd, (struct sockaddr*)&serv_addr, len) == -1)
+        {
+          log_error ("error binding socket to `%s': %s\n",
+                     serv_addr.sun_path, strerror (errno) );
+          close (fd);
+          exit (1);
+        }
+  
+      if (listen (fd, 5 ) == -1)
+        {
+          log_error ("listen() failed: %s\n", strerror (errno));
+          close (fd);
+          exit (1);
+        }
+
+      if (opt.verbose)
+        log_info ("listening on socket `%s'\n", socket_name );
+
+
+      fflush (NULL);
+      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;
+          
+          close (fd);
+          
+          /* 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);
+            }
+          *socket_name = 0; /* don't let cleanup() remove the socket -
+                               the child should do this from now on */
+          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);
+                }
+              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);
+                }
+              else
+                {
+                  printf ( "%s; export GPG_AGENT_INFO;\n", infostr);
+                }
+              free (infostr);
+              exit (0); 
+            }
+          /*NEVER REACHED*/
+        } /* end parent */
+      
+
+      /* this is the child */
+
+      /* detach from tty and put process into a new session */
+      if (!nodetach )
+        { 
+          int i;
+
+          /* close stdin, stdout and stderr unless it is the log stream */
+          for (i=0; i <= 2; i++) 
+            {
+              if ( log_get_fd () != i)
+                close (i);
+            }
+          if (setsid() == -1)
+            {
+              log_error ("setsid() failed: %s\n", strerror(errno) );
+              cleanup ();
+              exit (1);
+            }
+          opt.running_detached = 1;
+        }
+
+      if (chdir("/"))
+        {
+          log_error ("chdir to / failed: %s\n", strerror (errno));
+          exit (1);
+        }
+
+
+#ifdef USE_GNU_PTH
+      if (!disable_pth)
+        {
+         struct sigaction sa;
+
+         sa.sa_handler = SIG_IGN;
+         sigemptyset (&sa.sa_mask);
+         sa.sa_flags = 0;
+         sigaction (SIGPIPE, &sa, NULL);
+          handle_connections (fd);
+        }
+      else
+#endif /*!USE_GNU_PTH*/
+      /* setup signals */
+        {
+          struct sigaction oact, nact;
+          
+          nact.sa_handler = cleanup_sh;
+          sigemptyset (&nact.sa_mask);
+          nact.sa_flags = 0;
+          
+          sigaction (SIGHUP, NULL, &oact);
+          if (oact.sa_handler != SIG_IGN)
+            sigaction (SIGHUP, &nact, NULL);
+          sigaction( SIGTERM, NULL, &oact );
+          if (oact.sa_handler != SIG_IGN)
+            sigaction (SIGTERM, &nact, NULL);
+          nact.sa_handler = SIG_IGN;
+          sigaction (SIGPIPE, &nact, NULL);
+          sigaction (SIGINT, &nact, NULL);
+
+          start_command_handler (fd, -1);
+        }
+      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);
+}
+
+
+void
+agent_init_default_ctrl (struct server_control_s *ctrl)
+{
+  /* Note we ignore malloc errors because we can't do much about it
+     and the request will fail anyway shortly after this
+     initialization. */
+  if (ctrl->display)
+    free (ctrl->display);
+  ctrl->display = default_display? strdup (default_display) : NULL;
+
+  if (ctrl->ttyname)
+    free (ctrl->ttyname);
+  ctrl->ttyname = default_ttyname? strdup (default_ttyname) : NULL;
+
+  if (ctrl->ttytype)
+    free (ctrl->ttytype);
+  ctrl->ttytype = default_ttytype? strdup (default_ttytype) : NULL;
+
+  if (ctrl->lc_ctype)
+    free (ctrl->lc_ctype);
+  ctrl->lc_ctype = default_lc_ctype? strdup (default_lc_ctype) : NULL;
+
+  if (ctrl->lc_messages)
+    free (ctrl->lc_messages);
+  ctrl->lc_messages = default_lc_messages? strdup (default_lc_messages) : NULL;
+}
+
+
+/* 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_error (_("option file `%s': %s\n"),
+                 config_filename, strerror(errno) );
+      return;
+    }
+
+  parse_rereadable_options (NULL); /* 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);
+    }
+  fclose (fp);
+}
+
+
+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)
+    {
+      if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR ))
+        log_error (_("can't create directory `%s': %s\n"),
+                   fname,      strerror(errno) );
+      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 = GNUPG_DEFAULT_HOMEDIR;
+  char *home;
+
+  home  = make_filename (opt.homedir, NULL);
+  if ( stat (home, &statbuf) )
+    {
+      if (errno == ENOENT)
+        {
+          if ( (*defhome == '~'
+                && (strlen (home) >= strlen (defhome+1)
+                    && !strcmp (home + strlen(home)
+                                - strlen (defhome+1), defhome+1)))
+               || (*defhome != '~' && !strcmp (home, defhome) )
+               )
+            {
+              if (mkdir (home, S_IRUSR|S_IWUSR|S_IXUSR ))
+                log_error (_("can't create directory `%s': %s\n"),
+                           home, strerror(errno) );
+              else 
+                {
+                  if (!opt.quiet)
+                    log_info (_("directory `%s' created\n"), home);
+                  create_private_keys_directory (home);
+                }
+            }
+        }
+      else
+        log_error ("error stat-ing `%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);
+}
+
+
+
+#ifdef USE_GNU_PTH
+static void
+handle_signal (int signo)
+{
+  switch (signo)
+    {
+    case SIGHUP:
+      log_info ("SIGHUP received - "
+                "re-reading configuration and flushing cache\n");
+      agent_flush_cache ();
+      reread_configuration ();
+      break;
+      
+    case SIGUSR1:
+      if (opt.verbose < 5)
+        opt.verbose++;
+      log_info ("SIGUSR1 received - verbosity set to %d\n", opt.verbose);
+      break;
+
+    case SIGUSR2:
+      if (opt.verbose)
+        opt.verbose--;
+      log_info ("SIGUSR2 received - verbosity set to %d\n", opt.verbose );
+      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;
+
+    default:
+      log_info ("signal %d received - no action defined\n", signo);
+    }
+}
+
+
+static void *
+start_connection_thread (void *arg)
+{
+  int fd = (int)arg;
+
+  if (opt.verbose)
+    log_info ("handler for fd %d started\n", fd);
+  start_command_handler (-1, fd);
+  if (opt.verbose)
+    log_info ("handler for fd %d terminated\n", fd);
+  
+  return NULL;
+}
+
+
+static void
+handle_connections (int listen_fd)
+{
+  pth_attr_t tattr;
+  pth_event_t ev;
+  sigset_t sigs;
+  int signo;
+  struct sockaddr_un paddr;
+  socklen_t plen = sizeof( paddr );
+  int fd;
+
+  tattr = pth_attr_new();
+  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024);
+  pth_attr_set (tattr, PTH_ATTR_NAME, "gpg-agent");
+
+  sigemptyset (&sigs );
+  sigaddset (&sigs, SIGHUP);
+  sigaddset (&sigs, SIGUSR1);
+  sigaddset (&sigs, SIGUSR2);
+  sigaddset (&sigs, SIGINT);
+  sigaddset (&sigs, SIGTERM);
+  ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+
+  for (;;)
+    {
+      if (shutdown_pending)
+        {
+          if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1)
+            break; /* ready */
+
+          /* Do not accept anymore connections and wait for existing
+             connections to terminate */
+          signo = 0;
+          pth_wait (ev);
+          if (pth_event_occurred (ev) && signo)
+            handle_signal (signo);
+          continue;
+       }
+
+      fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev);
+      if (fd == -1)
+        {
+#ifdef PTH_STATUS_OCCURRED     /* This is Pth 2 */
+          if (pth_event_status (ev) == PTH_STATUS_OCCURRED)
+#else
+          if (pth_event_occurred (ev))
+#endif
+            {
+              handle_signal (signo);
+              continue;
+           }
+          log_error ("accept failed: %s - waiting 1s\n", strerror (errno));
+          pth_sleep(1);
+          continue;
+       }
+
+      if (!pth_spawn (tattr, start_connection_thread, (void*)fd))
+        {
+          log_error ("error spawning connection handler: %s\n",
+                     strerror (errno) );
+          close (fd);
+       }
+    }
+
+  pth_event_free (ev, PTH_FREE_ALL);
+  cleanup ();
+  log_info ("%s %s stopped\n", strusage(11), strusage(13));
+}
+#endif /*USE_GNU_PTH*/
diff --git a/agent/learncard.c b/agent/learncard.c
new file mode 100644 (file)
index 0000000..28a74f9
--- /dev/null
@@ -0,0 +1,448 @@
+/* learncard.c - Handle the LEARN command
+ *     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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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>
+
+struct keypair_info_s {
+  struct keypair_info_s *next;
+  int no_cert;
+  char *id;  /* points into grip */
+  char hexgrip[1];
+};
+typedef struct keypair_info_s *KEYPAIR_INFO;
+
+struct kpinfo_cb_parm_s {
+  int error;
+  KEYPAIR_INFO info;
+};
+
+
+struct certinfo_s {
+  struct certinfo_s *next;
+  int type;  
+  int done;
+  char id[1];
+};
+typedef struct certinfo_s *CERTINFO;
+
+struct certinfo_cb_parm_s {
+  int error;
+  CERTINFO info;
+};
+
+
+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;
+};
+
+
+
+static void
+release_keypair_info (KEYPAIR_INFO info)
+{
+  while (info)
+    {
+      KEYPAIR_INFO tmp = info->next;
+      xfree (info);
+      info = tmp;
+    }
+}
+
+static void
+release_certinfo (CERTINFO info)
+{
+  while (info)
+    {
+      CERTINFO tmp = info->next;
+      xfree (info);
+      info = tmp;
+    }
+}
+
+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 */
+  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 */
+
+  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;
+}
+
+
+/* Create an S-expression with the shadow info.  */
+static unsigned char *
+make_shadow_info (const char *serialno, const char *idstring)
+{
+  const char *s;
+  unsigned char *info, *p;
+  char numbuf[21];
+  int n;
+
+  for (s=serialno, n=0; *s && s[1]; s += 2)
+    n++;
+
+  info = p = xtrymalloc (1 + 21 + n
+                           + 21 + strlen (idstring) + 1 + 1);
+  *p++ = '(';
+  sprintf (numbuf, "%d:", n);
+  p = stpcpy (p, numbuf);
+  for (s=serialno; *s && s[1]; s += 2)
+    *p++ = xtoi_2 (s);
+  sprintf (numbuf, "%d:", strlen (idstring));
+  p = stpcpy (p, numbuf);
+  p = stpcpy (p, idstring);
+  *p++ = ')';
+  *p = 0;
+  return info;
+}
+
+static int
+send_cert_back (const char *id, void *assuan_context)
+{
+  int rc;
+  char *derbuf;
+  size_t derbuflen;
+  
+  rc = agent_card_readcert (id, &derbuf, &derbuflen);
+  if (rc)
+    {
+      log_error ("error reading certificate: %s\n",
+                 gpg_strerror (rc));
+      return 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",
+                 assuan_strerror (rc));
+      return map_assuan_err (rc);
+    }
+  return 0;
+}
+
+/* Perform the learn operation.  If ASSUAN_CONTEXT is not NULL all new
+   certificates are send via Assuan */
+int
+agent_handle_learn (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[] = { 
+    101, /* trusted */
+    102, /* useful */
+    100, /* regular */
+    -1 /* end of list */
+  };
+
+
+  memset (&parm, 0, sizeof parm);
+  memset (&cparm, 0, sizeof cparm);
+  memset (&sparm, 0, sizeof sparm);
+
+  /* Check whether a card is present and get the serial number */
+  rc = agent_card_serialno (&serialno);
+  if (rc)
+    goto leave;
+
+  /* now gather all the available info */
+  rc = agent_card_learn (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 (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 */
+
+      for (p=item->hexgrip, i=0; i < 20; p += 2, i++)
+        grip[i] = xtoi_2 (p);
+      
+      if (!agent_key_available (grip))
+        continue;
+      
+      /* unknown - store it */
+      rc = agent_card_readkey (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 (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 (file)
index 0000000..255fef0
--- /dev/null
@@ -0,0 +1,1140 @@
+/* minip12.c - A 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gcrypt.h>
+
+#undef TEST 
+
+#ifdef TEST
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+#include "../jnlib/logging.h"
+#include "minip12.h"
+
+#ifndef DIM
+#define DIM(v)              (sizeof(v)/sizeof((v)[0]))
+#endif
+
+enum
+{
+  UNIVERSAL = 0,
+  APPLICATION = 1,
+  CONTEXT = 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_pkcs_8ShroudedKeyBag[11] = {
+  0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 };
+static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = {
+  0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 };
+
+static unsigned char const oid_rsaEncryption[9] = {
+  0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
+
+
+static unsigned char const data_3desiter1024[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, 0x04, 0x00 };
+#define DATA_3DESITER1024_SALT_OFF  18
+
+
+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. */
+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;
+}
+
+
+static int 
+string_to_key (int id, char *salt, 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;
+    }
+
+  /* Store salt and password in BUF_I */
+  p = buf_i;
+  for(i=0; i < 64; i++)
+    *p++ = salt [i%8];
+  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, int iter, const char *pw)
+{
+  unsigned char keybuf[24];
+  int rc;
+
+  if (string_to_key (1, salt, iter, pw, 24, keybuf))
+    return -1;
+  rc = gcry_cipher_setkey (chd, keybuf, 24);
+  if (rc)
+    {
+      log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc));
+      return -1;
+    }
+
+  if (string_to_key (2, salt, 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, int iter,
+             const char *pw, int encrypt)
+{
+  gcry_cipher_hd_t chd;
+  int rc;
+
+  rc = gcry_cipher_open (&chd, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0);
+  if (rc)
+    {
+      log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(-1));
+      return;
+    }
+  if (set_key_iv (chd, salt, iter, pw))
+    goto leave;
+
+  rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0)
+              : gcry_cipher_decrypt (chd, buffer, length, NULL, 0);
+
+  if (rc)
+    {
+      log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc));
+      goto leave;
+    }
+
+/*    { */
+/*      FILE *fp = fopen("inner.der", "wb"); */
+/*      fwrite (buffer, 1, length, fp); */
+/*      fclose (fp); */
+/*    } */
+
+ leave:
+  gcry_cipher_close (chd);
+}
+  
+
+
+
+static int
+parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
+                          int startoffset)
+{
+  struct tag_info ti;
+  const unsigned char *p = buffer;
+  size_t n = length;
+  const char *where;
+
+  where = "start";
+  if (parse_tag (&p, &n, &ti))
+    goto bailout;
+  if (ti.class != CONTEXT || 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);
+
+  /* fixme: continue parsing */
+
+  return 0;
+ bailout:
+  log_error ("encrptedData error at \"%s\", offset %u\n",
+             where, (p - buffer)+startoffset);
+  return -1;
+}
+
+static gcry_mpi_t *
+parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
+                const char *pw)
+{
+  int rc;
+  struct tag_info ti;
+  const unsigned char *p = buffer;
+  size_t n = length;
+  const char *where;
+  char salt[8];
+  unsigned int iter;
+  int len;
+  unsigned char *plain = NULL;
+  gcry_mpi_t *result = NULL;
+  int result_count, i;
+
+  where = "start";
+  if (parse_tag (&p, &n, &ti))
+    goto bailout;
+  if (ti.class != CONTEXT || ti.tag)
+    goto bailout;
+  if (parse_tag (&p, &n, &ti))
+    goto bailout;
+  if (ti.class || ti.tag != TAG_OCTET_STRING)
+    goto bailout;
+
+  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 != CONTEXT || 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 )
+    goto bailout;
+  memcpy (salt, p, 8);
+  p += 8;
+  n -= 8;
+  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;
+    }
+  memcpy (plain, p, ti.length);
+  crypt_block (plain, ti.length, salt, iter, pw, 0);
+  n = ti.length;
+  startoffset = 0;
+  buffer = 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;
+
+  return result;
+
+ bailout:
+  gcry_free (plain);
+  if (result)
+    {
+      for (i=0; result[i]; i++)
+        gcry_mpi_release (result[i]);
+      gcry_free (result);
+    }
+  log_error ( "data error at \"%s\", offset %u\n",
+              where, (p - buffer) + startoffset);
+  return NULL;
+}
+
+
+/* Parse a PKCS12 object and return an array of MPI representing the
+   secret key parameters.  This is a very limited inplementation in
+   that it is only able to look for 3DES encoded enctyptedData and
+   tries to extract the first private key object it finds.  In case of
+   an error NULL is returned. */
+gcry_mpi_t *
+p12_parse (const unsigned char *buffer, size_t length, const char *pw)
+{
+  struct tag_info ti;
+  const unsigned char *p = buffer;
+  size_t n = length;
+  const char *where;
+  int bagseqlength, len;
+
+  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 != CONTEXT || ti.tag)
+    goto bailout;
+  if (parse_tag (&p, &n, &ti))
+    goto bailout;
+  if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING)
+    goto bailout;
+
+  where = "bags";
+  if (parse_tag (&p, &n, &ti))
+    goto bailout;
+  if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+    goto bailout;
+  bagseqlength = ti.length;
+  while (bagseqlength)
+    {
+      /*log_debug ( "at offset %u\n", (p - buffer));*/
+      where = "bag-sequence";
+      if (parse_tag (&p, &n, &ti))
+        goto bailout;
+      if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+        goto bailout;
+
+      if (bagseqlength < ti.nhdr)
+        goto bailout;
+      bagseqlength -= ti.nhdr;
+      if (bagseqlength < ti.length)
+        goto bailout;
+      bagseqlength -= ti.length;
+      len = ti.length;
+
+      if (parse_tag (&p, &n, &ti))
+        goto bailout;
+      len -= ti.nhdr;
+      if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData)
+          && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData)))
+        {
+          p += DIM(oid_encryptedData);
+          n -= DIM(oid_encryptedData);
+          len -= DIM(oid_encryptedData);
+          where = "bag.encryptedData";
+          if (parse_bag_encrypted_data (p, n, (p - buffer)))
+            goto bailout;
+        }
+      else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data)
+          && !memcmp (p, oid_data, DIM(oid_data)))
+        {
+          p += DIM(oid_data);
+          n -= DIM(oid_data);
+          len -= DIM(oid_data);
+          return parse_bag_data (p, n, (p-buffer), pw);
+        }
+      else
+        log_info ( "unknown bag type - skipped\n");
+
+      if (len < 0 || len > n)
+        goto bailout;
+      p += len;
+      n -= len;
+    }
+  
+  return NULL;
+ bailout:
+  log_error ("error at \"%s\", offset %u\n", where, (p - buffer));
+  return NULL;
+}
+
+
+\f
+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.  That array is terminated with an NULL object */
+static unsigned char *
+create_final (struct buffer_s *sequences, size_t *r_length)
+{
+  int i;
+  size_t needed = 0;
+  size_t n, outseqlen, notsooutseqlen, out0taglen, octstrlen, inseqlen;
+  unsigned char *result, *p;
+  size_t resultlen;
+
+  for (i=0; sequences[i].buffer; i++)
+    needed += sequences[i].length;
+  /* This goes into a sequences. */
+  inseqlen = needed;
+  n = compute_tag_length (needed);
+  needed += n;
+  /* And encapsulate all in an octet string. */
+  octstrlen = needed;
+  n = compute_tag_length (needed);
+  needed += n;
+  /* And tag it with [0]. */
+  out0taglen = needed;
+  n = compute_tag_length (needed);
+  needed += n;
+  /* Prepend an data OID. */
+  needed += 2 + DIM (oid_data);
+  /* This all into a sequences. */
+  notsooutseqlen = needed;
+  n = compute_tag_length (needed);
+  needed += n;
+  /* Prepend the version integer 3. */
+  needed += 3;
+  /* And the final sequence. */
+  outseqlen = needed;
+  n = compute_tag_length (needed);
+  needed += n;
+
+  result = gcry_malloc (needed);
+  if (!result)
+    {
+      log_error ("error allocating buffer\n");
+      return NULL;
+    }
+  p = result;
+
+  /* Store the very outer sequence. */
+  p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
+  /* Store the version integer 3. */
+  *p++ = TAG_INTEGER;
+  *p++ = 1; 
+  *p++ = 3; 
+  /* Store another sequence. */
+  p = store_tag_length (p, TAG_SEQUENCE, notsooutseqlen);
+  /* 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); 
+  /* Next comes a context tag. */
+  p = store_tag_length (p, 0xa0, out0taglen);
+  /* And an octet string. */
+  p = store_tag_length (p, TAG_OCTET_STRING, octstrlen);
+  /* And the inner sequence. */
+  p = store_tag_length (p, TAG_SEQUENCE, inseqlen);
+  /* And append all the buffers. */
+  for (i=0; sequences[i].buffer; i++)
+    {
+      memcpy (p, sequences[i].buffer, sequences[i].length);
+      p += sequences[i].length;
+    }
+
+  /* Ready. */
+  resultlen = p - result;
+  if (needed != resultlen)
+    log_debug ("length mismatch: %u, %u\n", needed, resultlen);
+
+  *r_length = resultlen;
+  return result;
+}
+
+
+/* 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. */
+unsigned char * 
+p12_build (gcry_mpi_t *kparms, const char *pw, size_t *r_length)
+{
+  int rc, i;
+  size_t needed, n;
+  unsigned char *plain, *p, *cipher;
+  size_t plainlen, cipherlen;
+  size_t outseqlen, oidseqlen, octstrlen, inseqlen;
+  size_t out0taglen, in0taglen, outoctstrlen;
+  size_t aseq1len, aseq2len, aseq3len;
+  char salt[8];
+
+  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 paramters 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 (;(plainlen % 8); plainlen++)
+    *p++ = n;
+
+  {
+    FILE *fp = fopen("inner-out.der", "wb");
+    fwrite (plain, 1, plainlen, fp);
+    fclose (fp);
+  }
+
+
+  /* Encrypt it and prepend a lot of stupid things. */
+  gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+  crypt_block (plain, plainlen, salt, 1024, pw, 1);
+  /* the data goes into an octet string. */
+  needed = compute_tag_length (plainlen);
+  needed += plainlen;
+  /* we prepend the the algorithm identifier (we use a pre-encoded one)*/
+  needed += DIM (data_3desiter1024);
+  /* we put a sequence around. */
+  aseq3len = needed;
+  needed += compute_tag_length (needed);
+  /* Prepend it with a [0] tag. */
+  in0taglen = needed;
+  needed += compute_tag_length (needed);
+  /* Prepend that shroudedKeyBag OID. */
+  needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
+  /* Put it all into two sequence. */
+  aseq2len = needed;
+  needed += compute_tag_length ( needed);
+  aseq1len = needed;
+  needed += compute_tag_length (needed);
+  /* This all goes into an octet string. */
+  outoctstrlen = needed;
+  needed += compute_tag_length (needed);
+  /* Prepend it with a [0] tag. */
+  out0taglen = needed;
+  needed += compute_tag_length (needed);
+  /* Prepend the data OID. */
+  needed += 2 + DIM (oid_data);
+  /* And a sequence. */
+  outseqlen = needed;
+  needed += compute_tag_length (needed);
+
+  cipher = gcry_malloc (needed);
+  if (!cipher)
+    {
+      log_error ("error allocating buffer\n");
+      gcry_free (plain);
+      return NULL;
+    }
+  p = cipher;
+  /* Store the first sequence. */
+  p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
+  /* 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); 
+  /* Next comes a context tag. */
+  p = store_tag_length (p, 0xa0, out0taglen);
+  /* And an octet string. */
+  p = store_tag_length (p, TAG_OCTET_STRING, outoctstrlen);
+  /* Two sequences. */
+  p = store_tag_length (p, TAG_SEQUENCE, aseq1len);
+  p = store_tag_length (p, TAG_SEQUENCE, aseq2len);
+  /* 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); 
+  /* Next comes a context tag. */
+  p = store_tag_length (p, 0xa0, in0taglen);
+  /* And a sequence. */
+  p = store_tag_length (p, TAG_SEQUENCE, aseq3len);
+  /* Now for the pre-encoded algorithm indentifier and the salt. */
+  memcpy (p, data_3desiter1024, DIM (data_3desiter1024));
+  memcpy (p + DATA_3DESITER1024_SALT_OFF, salt, 8);
+  p += DIM (data_3desiter1024);
+  /* And finally the octet string with the encrypted data. */
+  p = store_tag_length (p, TAG_OCTET_STRING, plainlen);
+  memcpy (p, plain, plainlen);
+  p += plainlen;
+  cipherlen = p - cipher;
+  
+  if (needed != cipherlen)
+    log_debug ("length mismatch: %u, %u\n", needed, cipherlen);
+  gcry_free (plain);
+
+  {
+    struct buffer_s seqlist[2];
+
+    seqlist[0].buffer = cipher;
+    seqlist[0].length = cipherlen;
+    seqlist[1].buffer = NULL;
+    seqlist[1].length = 0;
+
+    cipher = create_final (seqlist, &cipherlen);
+    gcry_free (seqlist[0].buffer);
+  }
+
+  *r_length = cipherlen;
+  return cipher;
+}
+
+
+#ifdef TEST
+int
+main (int argc, char **argv)
+{
+  FILE *fp;
+  struct stat st;
+  char *buf;
+  size_t buflen;
+  GcryMPI *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]);
+  if (result)
+    {
+      int i, rc;
+      char *buf;
+
+      for (i=0; result[i]; i++)
+        {
+          rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, (void**)&buf,
+                                NULL, result[i]);
+          if (rc)
+            printf ("%d: [error printing number: %s]\n",
+                    i, gpg_strerror (rc));
+          else
+            {
+              printf ("%d: %s\n", i, buf);
+              gcry_free (buf);
+            }
+        }
+    }
+
+  return 0;
+
+}
+#endif /* TEST */
diff --git a/agent/minip12.h b/agent/minip12.h
new file mode 100644 (file)
index 0000000..1222155
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef MINIP12_H
+#define MINIP12_H
+
+#include <gcrypt.h>
+
+gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length,
+                       const char *pw);
+
+unsigned char *p12_build (gcry_mpi_t *kparms, const char *pw,
+                          size_t *r_length);
+
+
+#endif /*MINIP12_H*/
diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
new file mode 100644 (file)
index 0000000..543a827
--- /dev/null
@@ -0,0 +1,138 @@
+/* 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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 ctrl, const char *ciphertext, size_t ciphertextlen,
+                 FILE *outfp) 
+{
+  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, 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);
+    }
+  s_skey = agent_key_from_file (ctrl, ctrl->keygrip, &shadow_info, 0);
+  if (!s_skey && !shadow_info)
+    {
+      log_error ("failed to read the secret key\n");
+      rc = gpg_error (GPG_ERR_NO_SECKEY);
+      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;
+        }
+      /* FIXME: don't use buffering and change the protocol to return
+         a complete S-expression and not just a part. */
+      fprintf (outfp, "%u:", (unsigned int)len);
+      fwrite (buf, 1, len, outfp);
+      putc (0, outfp);
+    }
+  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));
+          rc = map_gcry_err (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);
+      /* FIXME: we must make sure that no buffering takes place or we are
+         in full control of the buffer memory (easy to do) - should go
+         into assuan. */
+      fwrite (buf, 1, len, outfp);
+    }      
+
+
+ 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 (file)
index 0000000..fba2c65
--- /dev/null
@@ -0,0 +1,185 @@
+/* pksign.c - public key signing (well, acually using a secret key)
+ *     Copyright (C) 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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 unsigned char *digest, size_t digestlen, int algo,
+              unsigned int nbits, gcry_mpi_t *r_val)
+{
+  int nframe = (nbits+7) / 8;
+  byte *frame;
+  int i, n;
+  byte asn[100];
+  size_t asnlen;
+
+  asnlen = DIM(asn);
+  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);
+    }
+
+  if (digestlen + asnlen + 4  > nframe )
+    {
+      log_error ("can't encode a %d bit MD into a %d bits frame\n",
+                 (int)(digestlen*8), (int)nbits);
+      return gpg_error (GPG_ERR_INTERNAL);
+    }
+  
+  /* We encode the MD in this way:
+   *
+   *      0  1 PAD(n bytes)   0  ASN(asnlen bytes)  MD(len bytes)
+   *
+   * PAD consists of FF bytes.
+   */
+  frame = xtrymalloc (nframe);
+  if (!frame)
+    return out_of_core ();
+  n = 0;
+  frame[n++] = 0;
+  frame[n++] = 1; /* block type */
+  i = nframe - digestlen - asnlen -3 ;
+  assert ( i > 1 );
+  memset ( frame+n, 0xff, i ); n += i;
+  frame[n++] = 0;
+  memcpy ( frame+n, asn, asnlen ); n += asnlen;
+  memcpy ( frame+n, digest, digestlen ); n += digestlen;
+  assert ( n == nframe );
+  if (DBG_CRYPTO)
+    log_printhex ("encoded hash:", frame, nframe);
+      
+  gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe);
+  xfree (frame);
+  return 0;
+}
+
+
+/* SIGN whatever information we have accumulated in CTRL and write it
+   back to OUTFP. */
+int
+agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache) 
+{
+  gcry_sexp_t s_skey = NULL, s_hash = NULL, s_sig = NULL;
+  gcry_mpi_t frame = NULL;
+  unsigned char *shadow_info = NULL;
+  int rc;
+  char *buf = NULL;
+  size_t len;
+
+  if (!ctrl->have_keygrip)
+    return gpg_error (GPG_ERR_NO_SECKEY);
+
+  s_skey = agent_key_from_file (ctrl,
+                                ctrl->keygrip, &shadow_info, ignore_cache);
+  if (!s_skey && !shadow_info)
+    {
+      log_error ("failed to read the secret key\n");
+      rc = gpg_error (GPG_ERR_NO_SECKEY);
+      goto leave;
+    }
+
+  if (!s_skey)
+    { /* divert operation to the smartcard */
+      unsigned char *sigbuf;
+
+      rc = divert_pksign (ctrl, 
+                          ctrl->digest.value, 
+                          ctrl->digest.valuelen,
+                          ctrl->digest.algo,
+                          shadow_info, &sigbuf);
+      if (rc)
+        {
+          log_error ("smartcard signing failed: %s\n", gpg_strerror (rc));
+          goto leave;
+        }
+      len = gcry_sexp_canon_len (sigbuf, 0, NULL, NULL);
+      assert (len);
+      buf = sigbuf;
+    }
+  else
+    { /* no smartcard, but a private key */
+
+      /* put the hash into a sexp */
+      rc = do_encode_md (ctrl->digest.value,
+                         ctrl->digest.valuelen,
+                         ctrl->digest.algo,
+                         gcry_pk_get_nbits (s_skey),
+                         &frame);
+      if (rc)
+        goto leave;
+      if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) )
+        BUG ();
+
+      if (DBG_CRYPTO)
+        {
+          log_debug ("skey: ");
+          gcry_sexp_dump (s_skey);
+        }
+
+      /* sign */
+      rc = gcry_pk_sign (&s_sig, s_hash, s_skey);
+      if (rc)
+        {
+          log_error ("signing failed: %s\n", gpg_strerror (rc));
+          rc = map_gcry_err (rc);
+          goto leave;
+        }
+
+      if (DBG_CRYPTO)
+        {
+          log_debug ("result: ");
+          gcry_sexp_dump (s_sig);
+        }
+
+      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);
+    }
+
+  /* FIXME: we must make sure that no buffering takes place or we are
+     in full control of the buffer memory (easy to do) - should go
+     into assuan. */
+  fwrite (buf, 1, len, outfp);
+
+ leave:
+  gcry_sexp_release (s_skey);
+  gcry_sexp_release (s_hash);
+  gcry_sexp_release (s_sig);
+  gcry_mpi_release (frame);
+  xfree (buf);
+  xfree (shadow_info);
+  return rc;
+}
+
+
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
new file mode 100644 (file)
index 0000000..e518c56
--- /dev/null
@@ -0,0 +1,977 @@
+/* protect-tool.c - A tool to test the secret key protection
+ *     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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+#include "minip12.h"
+#include "simple-pwquery.h"
+#include "i18n.h"
+
+enum cmd_and_opt_values 
+{ aNull = 0,
+  oVerbose       = 'v',
+  oArmor          = 'a',
+  oPassphrase     = 'P',
+
+  oProtect        = 'p',
+  oUnprotect      = 'u',
+  
+  oNoVerbose = 500,
+  oShadow,
+  oShowShadowInfo,
+  oShowKeygrip,
+
+  oP12Import,
+  oP12Export,
+  oStore,
+  oForce,
+
+aTest };
+
+struct rsa_secret_key_s 
+  {
+    gcry_mpi_t n;          /* public modulus */
+    gcry_mpi_t e;          /* public exponent */
+    gcry_mpi_t d;          /* exponent */
+    gcry_mpi_t p;          /* prime  p. */
+    gcry_mpi_t q;          /* prime  q. */
+    gcry_mpi_t u;          /* inverse of p mod q. */
+  };
+
+
+static int opt_armor;
+static int opt_store;
+static int opt_force;
+static const char *passphrase;
+
+static const char *get_passphrase (void);
+static int store_private_key (const unsigned char *grip,
+                              const void *buffer, size_t length, int force);
+
+
+static ARGPARSE_OPTS opts[] = {
+  
+  { 301, NULL, 0, N_("@Options:\n ") },
+
+  { oVerbose, "verbose",   0, "verbose" },
+  { oArmor,   "armor",     0, "write output in advanced format" },
+  { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" },
+  { oProtect, "protect",     256, "protect a private key"},
+  { oUnprotect, "unprotect", 256, "unprotect a private key"},
+  { oShadow,  "shadow", 256, "create a shadow entry for a priblic key"},
+  { oShowShadowInfo,  "show-shadow-info", 256, "return the shadow info"},
+  { oShowKeygrip, "show-keygrip", 256, "show the \"keygrip\""},
+
+  { oP12Import, "p12-import", 256, "import a PKCS-12 encoded private key"},
+  { oP12Export, "p12-export", 256, "export a private key PKCS-12 encoded"},
+  { oStore,     "store", 0, "store the created key in the appropriate place"},
+  { oForce,     "force", 0, "force overwriting"},
+  {0}
+};
+
+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 <" PACKAGE_BUGREPORT ">.\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
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+    set_gettext_file( PACKAGE );
+#else
+#ifdef ENABLE_NLS
+    setlocale (LC_ALL, "");
+    bindtextdomain (PACKAGE, LOCALEDIR);
+    textdomain (PACKAGE);
+#endif
+#endif
+}
+
+
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+  /* translate the log levels */
+  switch (level)
+    {
+    case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+    case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+    case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+    case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+    case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+    case GCRY_LOG_BUG:  level = JNLIB_LOG_BUG; break;
+    case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+    default:            level = JNLIB_LOG_ERROR; break;      }
+  log_logv (level, fmt, arg_ptr);
+}
+
+
+/*  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); */
+/*      } */
+/*  } */
+
+
+\f
+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;
+  unsigned char *result;
+
+  rc = gcry_sexp_sscan (&sexp, &erroff, 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;
+  struct stat st;
+  char *buf;
+  size_t buflen;
+  
+  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;
+}
+
+
+\f
+static void
+read_and_protect (const char *fname)
+{
+  int  rc;
+  unsigned char *key;
+  unsigned char *result;
+  size_t resultlen;
+  
+  key = read_key (fname);
+  if (!key)
+    return;
+
+  rc = agent_protect (key, get_passphrase (), &result, &resultlen);
+  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 = 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;
+  
+  key = read_key (fname);
+  if (!key)
+    return;
+
+  rc = agent_unprotect (key, get_passphrase (), &result, &resultlen);
+  xfree (key);
+  if (rc)
+    {
+      log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc));
+      return;
+    }
+  
+  if (opt_armor)
+    {
+      char *p = make_advanced (result, resultlen);
+      xfree (result);
+      if (!p)
+        return;
+      result = p;
+      resultlen = strlen (p);
+    }
+
+  fwrite (result, resultlen, 1, stdout);
+  xfree (result);
+}
+
+
+\f
+static void
+read_and_shadow (const char *fname)
+{
+  int  rc;
+  unsigned char *key;
+  unsigned char *result;
+  size_t resultlen;
+  
+  key = read_key (fname);
+  if (!key)
+    return;
+
+  rc = agent_shadow_key (key, "(8:313233342:43)", &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 = 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);
+
+  p = make_advanced (key, keylen);
+  xfree (key);
+  if (p)
+    {
+      fwrite (p, strlen (p), 1, stdout);
+      xfree (p);
+    }
+}
+
+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');
+}
+
+\f
+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;
+}
+
+
+static void
+import_p12_file (const char *fname)
+{
+  char *buf;
+  unsigned char *result;
+  size_t buflen, resultlen;
+  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];
+
+  /* fixme: we should release some stuff on error */
+  
+  buf = read_file (fname, &buflen);
+  if (!buf)
+    return;
+
+  kparms = p12_parse (buf, buflen, get_passphrase ());
+  xfree (buf);
+  if (!kparms)
+    {
+      log_error ("error parsing or decrypting the PKCS-1 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);
+
+
+  rc = agent_protect (key, get_passphrase (), &result, &resultlen);
+  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 = p;
+      resultlen = strlen (p);
+    }
+
+  if (opt_store)
+    store_private_key (grip, result, resultlen, opt_force);
+  else
+    fwrite (result, resultlen, 1, stdout);
+
+  xfree (result);
+}
+
+\f
+
+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;
+}
+
+
+
+
+static void
+export_p12_file (const char *fname)
+{
+  gcry_mpi_t kparms[9], *kp;
+  unsigned char *key;
+  size_t keylen;
+  gcry_sexp_t private;
+  struct rsa_secret_key_s sk;
+  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);
+
+  kp = sexp_to_kparms (private);
+  gcry_sexp_release (private);
+  if (!kp)
+    {
+      log_error ("error converting key parameters\n");
+      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;
+
+  key = p12_build (kparms, get_passphrase (), &keylen);
+  for (i=0; i < 8; i++)
+    gcry_mpi_release (kparms[i]);
+  if (!key)
+    return;
+  
+  fwrite (key, keylen, 1, stdout);
+  xfree (key);
+}
+
+\f
+int
+main (int argc, char **argv )
+{
+  ARGPARSE_ARGS pargs;
+  int cmd = 0;
+
+  set_strusage (my_strusage);
+  gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+  log_set_prefix ("gpg-protect-tool", 1); 
+  i18n_init ();
+
+  if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+    {
+      log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+                 NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+    }
+
+  gcry_set_log_handler (my_gcry_logger, NULL);
+  
+  gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+  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 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 oPassphrase: passphrase = pargs.r.ret_str; break;
+        case oStore: opt_store = 1; break;
+        case oForce: opt_force = 1; break;
+
+        default : pargs.err = 2; break;
+       }
+    }
+  if (log_get_errorcount(0))
+    exit(2);
+
+  if (argc != 1)
+    usage (1);
+
+  if (cmd == oProtect)
+    read_and_protect (*argv);
+  else if (cmd == oUnprotect)
+    read_and_unprotect (*argv);
+  else if (cmd == oShadow)
+    read_and_shadow (*argv);
+  else if (cmd == oShowShadowInfo)
+    show_shadow_info (*argv);
+  else if (cmd == oShowKeygrip)
+    show_keygrip (*argv);
+  else if (cmd == oP12Import)
+    import_p12_file (*argv);
+  else if (cmd == oP12Export)
+    export_p12_file (*argv);
+  else
+    show_file (*argv);
+
+  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. */
+static const char *
+get_passphrase (void)
+{
+  char *pw;
+  int err;
+
+  if (passphrase)
+    return passphrase;
+
+  pw = simple_pwquery (NULL,NULL, 
+                       _("Enter passphrase:"),
+                       _("Please enter the passphrase or the PIN\n"
+                         "needed to complete this operation."),
+                       &err);
+  if (!pw)
+    {
+      if (err)
+        log_error ("error while asking for the passphrase\n");
+      else
+        log_info ("cancelled\n");
+      agent_exit (0);
+    }
+  passphrase = pw;
+  return passphrase;
+}
+
+
+static int
+store_private_key (const unsigned char *grip,
+                   const void *buffer, size_t length, int force)
+{
+  int i;
+  const char *homedir;
+  char *fname;
+  FILE *fp;
+  char hexgrip[40+4+1];
+  
+  for (i=0; i < 20; i++)
+    sprintf (hexgrip+2*i, "%02X", grip[i]);
+  strcpy (hexgrip+40, ".key");
+
+  homedir = getenv("GNUPGHOME");
+  if (!homedir || !*homedir)
+    homedir = GNUPG_DEFAULT_HOMEDIR;
+
+  fname = make_filename (homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  if (force)
+    fp = fopen (fname, "wb");
+  else
+    {
+      if (!access (fname, F_OK))
+      {
+        log_error ("secret key file `%s' already exists\n", fname);
+        xfree (fname);
+        return -1;
+      }
+      fp = fopen (fname, "wbx");  /* FIXME: the x is a GNU extension - let
+                                     configure check whether this actually
+                                     works */
+    }
+
+  if (!fp) 
+    { 
+      log_error ("can't create `%s': %s\n", fname, strerror (errno));
+      xfree (fname);
+      return -1;
+    }
+
+  if (fwrite (buffer, length, 1, fp) != 1)
+    {
+      log_error ("error writing `%s': %s\n", fname, strerror (errno));
+      fclose (fp);
+      remove (fname);
+      xfree (fname);
+      return -1;
+    }
+  if ( 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);
+
+  xfree (fname);
+  return 0;
+}
diff --git a/agent/protect.c b/agent/protect.c
new file mode 100644 (file)
index 0000000..e438d53
--- /dev/null
@@ -0,0 +1,971 @@
+/* protect.c - Un/Protect a secret key
+ * Copyright (C) 1998, 1999, 2000, 2001, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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 "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 },
+  { NULL }
+};
+
+
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+                 int s2kmode,
+                 const unsigned char *s2ksalt, unsigned long s2kcount,
+                 unsigned char *key, size_t keylen);
+
+
+\f
+/* Calculate the MIC for a private key S-Exp. SHA1HASH should pint 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;
+}
+
+
+\f
+/* 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 ereturn 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 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;
+  char *iv = NULL;
+  int rc;
+  char *outbuf = NULL;
+  char *p;
+  int saltpos, ivpos, encpos;
+
+  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 = gcry_random_bytes (blklen*2+8, GCRY_WEAK_RANDOM);
+      if (!iv)
+        rc = gpg_error (GPG_ERR_ENOMEM);
+      else
+        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, 96, 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 spaces as palceholders.  */
+  asprintf (&p,
+            "(9:protected%d:%s((4:sha18:%n_8bytes_2:96)%d:%n%*s)%d:%n%*s)",
+            (int)strlen (modestr), modestr,
+            &saltpos, 
+            blklen, &ivpos, blklen, "",
+            enclen, &encpos, enclen, "");
+  if (p)
+    { /* asprintf does not use our malloc system */
+      char *psave = p;
+      p = xtrymalloc (strlen (psave)+1);
+      if (p)
+        strcpy (p, psave);
+      free (psave);
+    }
+  if (!p)
+    {
+      gpg_error_t tmperr = out_of_core ();
+      xfree (iv);
+      xfree (outbuf);
+      return tmperr;
+    }
+  *resultlen = strlen (p);
+  *result = 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];
+  unsigned char *protected;
+  size_t protectedlen;
+  int depth = 0;
+  unsigned char *p;
+
+  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;
+
+  gcry_md_hash_buffer (GCRY_MD_SHA1, hashvalue,
+                       hash_begin, hash_end - hash_begin + 1);
+
+  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(" ). */
+  *resultlen = (10
+                + (prot_begin-plainkey)
+                + protectedlen
+                + (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, prot_end+1, real_end - prot_end);
+  p += real_end - prot_end;
+  assert ( p - *result == *resultlen);
+  xfree (protected);
+  return 0;
+}
+
+\f
+/* 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. */
+static int
+merge_lists (const unsigned char *protectedkey,
+             size_t replacepos, 
+             const unsigned char *cleartext,
+             unsigned char *sha1hash, unsigned char **result)
+{
+  size_t n, newlistlen;
+  unsigned char *newlist, *p;
+  const unsigned char *s;
+  const unsigned char *startpos, *endpos;
+  int i, rc;
+  
+  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 (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++;
+  /* short 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 overt the protected list element in the original list */
+  s = protectedkey + replacepos;
+  assert (*s == '(');
+  s++;
+  i = 1;
+  rc = sskip (&s, &i);
+  if (rc)
+    goto failure;
+  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 */
+  memcpy (p, startpos, endpos - startpos);
+  p += endpos - startpos;
+
+  /* ready */
+  *result = newlist;
+  return 0;
+
+ failure:
+  xfree (newlist);
+  return rc;
+
+ invalid_sexp:
+  xfree (newlist);
+  return gpg_error (GPG_ERR_INV_SEXP);
+}
+
+
+
+/* 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)
+{
+  int rc;
+  const unsigned char *s;
+  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;
+  unsigned char *final;
+
+  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); 
+
+  /* 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>)
+   */
+  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);
+  s2kcount = strtoul (s, NULL, 10);
+  if (!s2kcount)
+    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 ony 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);
+  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)
+    {
+      xfree (final);
+      return rc;
+    }
+
+  *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;
+}
+
+
+\f
+/* 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 (a suitable
+   value is 96).
+  
+   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 = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
+              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;
+}
+
+
+\f
+/* 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 canonilized
+  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;
+  unsigned 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 = p = xtrymalloc (n);
+  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;
+}
+
diff --git a/agent/simple-pwquery.c b/agent/simple-pwquery.c
new file mode 100644 (file)
index 0000000..e870122
--- /dev/null
@@ -0,0 +1,486 @@
+/* simple-pwquery.c - A simple password query client for gpg-agent
+ *     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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* This module is intended as a standalone client implementation to
+   gpg-agent's GET_PASSPHRASE command.  In particular it does not use
+   the Assuan library and can only cope with an already running
+   gpg-agent.  Some stuff is configurable in the header file. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#define SIMPLE_PWQUERY_IMPLEMENTATION 1
+#include "simple-pwquery.h"
+
+#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING)
+# undef SPWQ_USE_LOGGING
+#endif
+
+#ifndef _
+#define _(a) (a)
+#endif
+
+#if !defined (hexdigitp) && !defined (xtoi_2)
+#define digitp(p)   (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a)                     \
+                      || (*(a) >= 'A' && *(a) <= 'F')  \
+                      || (*(a) >= 'a' && *(a) <= 'f'))
+#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
+                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+#endif
+
+
+/* Write NBYTES of BUF to file descriptor FD. */
+static int
+writen (int fd, const void *buf, size_t nbytes)
+{
+  size_t nleft = nbytes;
+  int nwritten;
+  
+  while (nleft > 0)
+    {
+      nwritten = write( fd, buf, nleft );
+      if (nwritten < 0)
+        {
+          if (errno == EINTR)
+            nwritten = 0;
+          else {
+#ifdef SPWQ_USE_LOGGING
+            log_error ("write failed: %s\n", strerror (errno));
+#endif
+            return SPWQ_IO_ERROR;
+          }
+        }
+      nleft -= nwritten;
+      buf = (const char*)buf + nwritten;
+    }
+    
+  return 0;
+}
+
+
+/* Read an entire line and return number of bytes read. */
+static int
+readline (int fd, char *buf, size_t buflen)
+{
+  size_t nleft = buflen;
+  char *p;
+  int nread = 0;
+
+  while (nleft > 0)
+    {
+      int n = read (fd, buf, nleft);
+      if (n < 0)
+        {
+          if (errno == EINTR)
+            continue;
+          return -(SPWQ_IO_ERROR);
+        }
+      else if (!n)
+        {
+          return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */
+        }
+      p = buf;
+      nleft -= n;
+      buf += n;
+      nread += n;
+      
+      for (; n && *p != '\n'; n--, p++)
+        ;
+      if (n)
+        {
+          break; /* at least one full line available - that's enough.
+                    This function is just a simple implementation, so
+                    it is okay to forget about pending bytes */
+        }
+    }
+
+  return nread; 
+}
+
+
+/* Send an option to the agent */
+static int
+agent_send_option (int fd, const char *name, const char *value)
+{
+  char buf[200];
+  int nread;
+  char *line;
+  int i; 
+  
+  line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2);
+  if (!line)
+    return SPWQ_OUT_OF_CORE;
+  strcpy (stpcpy (stpcpy (stpcpy (
+                     stpcpy (line, "OPTION "), name), "="), value), "\n");
+  i = writen (fd, line, strlen (line));
+  spwq_free (line);
+  if (i)
+    return i;
+  
+  /* get response */
+  nread = readline (fd, buf, DIM(buf)-1);
+  if (nread < 0)
+    return -nread;
+  if (nread < 3)
+    return SPWQ_PROTOCOL_ERROR;
+  
+  if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n')) 
+    return 0; /* okay */
+
+  return SPWQ_ERR_RESPONSE;
+}
+
+
+/* Send all available options to the agent. */
+static int 
+agent_send_all_options (int fd)
+{
+  char *dft_display = NULL;
+  char *dft_ttyname = NULL;
+  char *dft_ttytype = NULL;
+  int rc = 0;
+
+  dft_display = getenv ("DISPLAY");
+  if (dft_display)
+    {
+      if ((rc = agent_send_option (fd, "display", dft_display)))
+        return rc;
+    }
+
+  dft_ttyname = getenv ("GPG_TTY");
+  if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
+    dft_ttyname = ttyname (0);
+  if (dft_ttyname && *dft_ttyname)
+    {
+      if ((rc=agent_send_option (fd, "ttyname", dft_ttyname)))
+        return rc;
+    }
+
+  dft_ttytype = getenv ("TERM");
+  if (dft_ttyname && dft_ttytype)
+    {
+      if ((rc = agent_send_option (fd, "ttytype", dft_ttytype)))
+        return rc;
+    }
+
+#if defined(HAVE_SETLOCALE) 
+  {
+    char *old_lc = NULL;
+    char *dft_lc = NULL;
+
+#if defined(LC_CTYPE)
+    old_lc = setlocale (LC_CTYPE, NULL);
+    if (old_lc)
+      {
+        char *p = spwq_malloc (strlen (old_lc)+1);
+        if (!p)
+          return SPWQ_OUT_OF_CORE;
+        strcpy (p, old_lc);
+        old_lc = p;
+      }
+    dft_lc = setlocale (LC_CTYPE, "");
+    if (dft_ttyname && dft_lc)
+      rc = agent_send_option (fd, "lc-ctype", dft_lc);
+    if (old_lc)
+      {
+        setlocale (LC_CTYPE, old_lc);
+        spwq_free (old_lc);
+      }
+    if (rc)
+      return rc;
+#endif
+
+#if defined(LC_MESSAGES)
+    old_lc = setlocale (LC_MESSAGES, NULL);
+    if (old_lc)
+      {
+        char *p = spwq_malloc (strlen (old_lc)+1);
+        if (!p)
+          return SPWQ_OUT_OF_CORE;
+        strcpy (p, old_lc);
+        old_lc = p;
+      }
+    dft_lc = setlocale (LC_MESSAGES, "");
+    if (dft_ttyname && dft_lc)
+      rc = agent_send_option (fd, "lc-messages", dft_lc);
+    if (old_lc)
+      {
+        setlocale (LC_MESSAGES, old_lc);
+        spwq_free (old_lc);
+      }
+    if (rc)
+      return rc;
+#endif
+  }
+#endif /*HAVE_SETLOCALE*/
+
+  return 0;
+}
+
+
+
+/* Try to open a connection to the agent, send all options and return
+   the file descriptor for the connection.  Return -1 in case of
+   error. */
+static int
+agent_open (int *rfd)
+{
+  int rc;
+  int fd;
+  char *infostr, *p;
+  struct sockaddr_un client_addr;
+  size_t len;
+  int prot;
+  char line[200];
+  int nread;
+
+  *rfd = -1;
+  infostr = getenv ( "GPG_AGENT_INFO" );
+  if ( !infostr ) 
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error (_("gpg-agent is not available in this session\n"));
+#endif
+      return SPWQ_NO_AGENT;
+    }
+
+  if ( !(p = strchr ( infostr, ':')) || p == infostr
+       || (p-infostr)+1 >= sizeof client_addr.sun_path ) 
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error ( _("malformed GPG_AGENT_INFO environment variable\n"));
+#endif
+      return SPWQ_NO_AGENT;
+    }
+  *p++ = 0;
+
+  while (*p && *p != ':')
+    p++;
+  prot = *p? atoi (p+1) : 0;
+  if ( prot != 1)
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error (_("gpg-agent protocol version %d is not supported\n"),prot);
+#endif
+      return SPWQ_PROTOCOL_ERROR;
+    }
+       
+  if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) 
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error ("can't create socket: %s\n", strerror(errno) );
+#endif
+      return SPWQ_SYS_ERROR;
+    }
+    
+  memset (&client_addr, 0, sizeof client_addr);
+  client_addr.sun_family = AF_UNIX;
+  strcpy (client_addr.sun_path, infostr);
+  len = (offsetof (struct sockaddr_un, sun_path)
+         + strlen(client_addr.sun_path) + 1);
+    
+  if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1)
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno));
+#endif
+      close (fd );
+      return SPWQ_IO_ERROR;
+    }
+
+  nread = readline (fd, line, DIM(line));
+  if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
+                     && (line[2] == '\n' || line[2] == ' ')) ) 
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error ( _("communication problem with gpg-agent\n"));
+#endif
+      close (fd );
+      return SPWQ_PROTOCOL_ERROR;
+    }
+
+  rc = agent_send_all_options (fd);
+  if (rc)
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error (_("problem setting the gpg-agent options\n"));
+#endif
+      close (fd);
+      return rc;
+    }
+
+  *rfd = fd;
+  return 0;
+}
+
+
+/* Copy text to BUFFER and escape as required.  Return a poiinter to
+   the end of the new buffer.  NOte that BUFFER must be large enough
+   to keep the entire text; allocataing it 3 times the size of TEXT
+   is sufficient. */
+static char *
+copy_and_escape (char *buffer, const char *text)
+{
+  int i;
+  char *p = buffer;
+
+  for (i=0; text[i]; i++)
+    {
+      if (text[i] < ' ' || text[i] == '+')
+        {
+          sprintf (p, "%%%02X", text[i]);
+          p += 3;
+        }
+      else if (text[i] == ' ')
+        *p++ = '+';
+      else
+        *p++ = text[i];
+    }
+  return p;
+}
+
+
+/* Ask the gpg-agent for a passphrase and present the user with a
+   DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text.
+   If a CACHEID is not NULL it is used to locate the passphrase in in
+   the cache and store it under this ID.  If ERRORCODE is not NULL it
+   should point a variable receiving an errorcode; thsi errocode might
+   be 0 if the user canceled the operation.  The function returns NULL
+   to indicate an error. */
+char *
+simple_pwquery (const char *cacheid, 
+                const char *tryagain,
+                const char *prompt,
+                const char *description,
+                int *errorcode)
+{
+  int fd = -1;
+  int nread;
+  char *result = NULL;
+  char *pw = NULL;
+  char *p;
+  int rc, i; 
+
+  rc = agent_open (&fd);
+  if (rc)
+    goto leave;
+
+  if (!cacheid)
+    cacheid = "X";
+  if (!tryagain)
+    tryagain = "X";
+  if (!prompt)
+    prompt = "X";
+  if (!description)
+    description = "X";
+
+  {
+    char *line;
+    /* We allocate 3 times the needed space so that there is enough
+       space for escaping. */
+    line = spwq_malloc (15
+                        + 3*strlen (cacheid) + 1
+                        + 3*strlen (tryagain) + 1
+                        + 3*strlen (prompt) + 1
+                        + 3*strlen (description) + 1
+                        + 2);
+    if (!line)
+      {
+        rc = SPWQ_OUT_OF_CORE;
+        goto leave;
+      }
+    strcpy (line, "GET_PASSPHRASE ");
+    p = line+15;
+    p = copy_and_escape (p, cacheid);
+    *p++ = ' ';
+    p = copy_and_escape (p, tryagain);
+    *p++ = ' ';
+    p = copy_and_escape (p, prompt);
+    *p++ = ' ';
+    p = copy_and_escape (p, description);
+    *p++ = '\n';
+    rc = writen (fd, line, p - line);
+    spwq_free (line);
+    if (rc)
+      goto leave;
+  }
+
+  /* get response */
+  pw = spwq_secure_malloc (500);
+  nread = readline (fd, pw, 499);
+  if (nread < 0)
+    {
+      rc = -nread;
+      goto leave;
+    }
+  if (nread < 3)
+    {
+      rc = SPWQ_PROTOCOL_ERROR;
+      goto leave;
+    }
+      
+  if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ') 
+    { /* we got a passphrase - convert it back from hex */
+      size_t pwlen = 0;
+      
+      for (i=3; i < nread && hexdigitp (pw+i); i+=2)
+        pw[pwlen++] = xtoi_2 (pw+i);
+      pw[pwlen] = 0; /* make a C String */
+      result = pw;
+      pw = NULL;
+    }
+  else if (nread > 7 && !memcmp (pw, "ERR 111", 7)
+      && (pw[7] == ' ' || pw[7] == '\n') )
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_info (_("canceled by user\n") );
+#endif
+      *errorcode = 0; /* canceled */
+    }
+  else 
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_error (_("problem with the agent\n"));
+#endif
+      rc = SPWQ_ERR_RESPONSE;
+    }
+        
+ leave:
+  if (errorcode)
+    *errorcode = rc;
+  if (fd != -1)
+    close (fd);
+  if (pw)
+    spwq_free (pw);
+  return result;
+}
diff --git a/common/ChangeLog b/common/ChangeLog
new file mode 100644 (file)
index 0000000..4870a4a
--- /dev/null
@@ -0,0 +1,219 @@
+2003-07-15  Werner Koch  <wk@gnupg.org>
+
+       * simple-pwquery.c, simple-pwquery.h:  New; moved from ../agent.  
+       * Makefile.am (libsimple_pwquery_a_LIBADD): New.
+
+2003-06-25  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_to_assuan_status): Directly map 0 to 0.
+
+2003-06-17  Werner Koch  <wk@gnupg.org>
+
+       * gettime.c (scan_isodatestr,add_days_to_timestamp,strtimevalue)
+       (strtimestamp,asctimestamp): New.  Code taken from gnupg 1.3.2
+       mischelp.c.
+
+       * yesno.c: New.  Code taken from gnupg 1.3.2 mischelp.c
+
+       * miscellaneous.c: New.
+
+       * util.h: Include utf8conf.h
+
+2003-06-16  Werner Koch  <wk@gnupg.org>
+
+       * gettime.c (make_timestamp): New.
+
+       * ttyio.c: New. Taken from gnupg 1.2.
+       * ttyio.h: Move from ../include.
+
+2003-06-13  Werner Koch  <wk@gnupg.org>
+
+       * util.h (seterr): Removed macro.
+       (xmalloc_secure,xcalloc_secure): New.
+
+2003-06-11  Werner Koch  <wk@gnupg.org>
+
+       * iobuf.c (iobuf_writebyte,iobuf_write): Return error code from
+       iobuf_flush.
+       (iobuf_writestr): Ditto.
+
+2003-06-10  Werner Koch  <wk@gnupg.org>
+
+       * iobuf.c, iobuf.h: New. Taken from current gnupg 1.3 CVS.  Run
+       indent on it and adjusted error handling to libgpg-error style.
+       Replaced IOBUF by iobuf_t. Renamed malloc functions.
+
+2003-06-04  Werner Koch  <wk@gnupg.org>
+
+       * errors.h: Removed all error codes.  We keep the status codes for
+       now.
+       * Makefile.am: Do not create errors.c anymore; remove it from the
+       sources.
+
+       * maperror.c: Don't include error.h.  Change all error codes to
+       libgpg-error style.
+       (map_assuan_err): Changed to new Assuan error code convention.
+       (map_to_assuan_status): Likewise.
+       (map_gcry_err,map_kbx_err): Not needed.  For now dummy functions.
+
+       * membuf.c, membuf.h: New.  Code taken from ../sm/call-agent.h.
+       * Makefile.am: Added above.
+
+2003-04-29  Werner Koch  <wk@gnupg.org>
+
+       * util.h (fopencokokie): Removed prototype and struct.
+
+       * fopencookie.c: Removed.
+
+       * maperror.c: Use system assuan.h
+
+2002-10-31  Neal H. Walfield  <neal@g10code.de>
+
+       * isascii.c: New file.
+       * putc_unlocked.c: Likewise.
+
+2002-10-28  Neal H. Walfield  <neal@g10code.de>
+
+       * signal.c (caught_fatal_sig): Remove superfluous zero
+       initializer.
+       (caught_sigusr1): Likewise.
+
+2002-09-04  Neal H. Walfield  <neal@g10code.de>
+
+       * vasprintf.c (vasprintf) [va_copy]: Use va_copy.
+       [!va_copy && __va_copy]: Use __va_copy.
+       [!va_copy && !__va_copy]: Only now fall back to using memcpy.
+
+2002-08-21  Werner Koch  <wk@gnupg.org>
+
+       * errors.h: Added STATUS_IMPORT_PROBLEM.
+
+2002-08-20  Werner Koch  <wk@gnupg.org>
+
+       * vasprintf.c: Hack to handle NULL for %s.
+
+2002-08-09  Werner Koch  <wk@gnupg.org>
+
+       * signal.c: New. Taken from GnuPG 1.1.91.
+
+2002-07-23  Werner Koch  <wk@gnupg.org>
+
+       * util.h (_IO_cookie_io_functions_t): Fixed typo.  Noted by
+       Richard Lefebvre.
+
+2002-07-22  Werner Koch  <wk@gnupg.org>
+
+       * fseeko.c, ftello.c: New.
+
+2002-06-28  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_to_assuan_status): Map more errorcodes to Bad
+       Certificate.
+
+2002-06-26  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_to_assuan_status): Map EOF to No_Data_Available.
+
+2002-06-10  Werner Koch  <wk@gnupg.org>
+
+       * errors.h (gnupg_error_token): Add new prototype.
+       (STATUS_ERROR): New.
+
+       * mkerrtok: New.
+       * Makefile.am: Use it to create the new error token function.
+
+2002-06-04  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_to_assuan_status): Map Bad_CA_Certificate.
+
+2002-05-23  Werner Koch  <wk@gnupg.org>
+
+       * no-pth.c, Makefile.am: Removed.
+
+2002-05-22  Werner Koch  <wk@gnupg.org>
+
+       * mkdtemp.c: Replaced byte by unsigned char because it is no longer
+       defined in gcrypt.h.
+
+2002-05-21  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_gcry_err): Add libgcrypt's new S-expression errors.
+       (map_ksba_err): Add a few mappings.
+
+2002-05-14  Werner Koch  <wk@gnupg.org>
+
+       * gettime.c: New.
+
+2002-05-03  Werner Koch  <wk@gnupg.org>
+
+       * errors.h: Added STARUS_EXPSIG and STATUS_EXPKEYSIG.
+
+2002-04-15  Werner Koch  <wk@gnupg.org>
+
+       * cryptmiss.c: New.
+
+2002-02-14  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c: Add more assuan<->gnupg mappings.
+
+2002-02-12  Werner Koch  <wk@gnupg.org>
+
+       * fopencookie.c: Dummy function.
+
+       * vasprintf.c: New.  Taken from binutils-2.9.1 and dropped all non
+       ANSI-C stuff.  Merged with asprintf version.
+
+       * no-pth.c: New.
+
+2002-01-23  Werner Koch  <wk@gnupg.org>
+
+       * mkdtemp.c: Copied from gnupg-1.0.6c and changed to use libgcrypt.
+
+2002-01-19  Werner Koch  <wk@gnupg.org>
+
+       * sysutils.c: New.  This is the misc.c file from gnupg 1.0.6 with
+       the OpenPGP stuff removed.
+       * sysutils.h: New.
+       
+2002-01-15  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c: Add mapping for Not_Trusted. 
+
+2002-01-11  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_assuan_err): Codes for CRL
+
+2002-01-08  Werner Koch  <wk@gnupg.org>
+
+       * util.h (spacep): New.
+
+2002-01-02  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_to_assuan_status): New.  Merged from ../agent
+       and ../sm.
+
+2001-12-20  Werner Koch  <wk@gnupg.org>
+
+       * maperror.c (map_gcry_err): Add some mappings.
+
+2001-12-18  Werner Koch  <wk@gnupg.org>
+
+       * Makefile.am (AM_CPPFLAGS): Include flags for gcrypt and ksba
+
+2001-12-14  Werner Koch  <wk@gnupg.org>
+
+       * util.h (digitp, hexdigitp): New ctype like macros.
+       (atoi_1,atoi_2,atoi_4,xtoi_1,xtoi_2): New.
+       
+       
+ Copyright 2001, 2002 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/common/Makefile.am b/common/Makefile.am
new file mode 100644 (file)
index 0000000..2b99a19
--- /dev/null
@@ -0,0 +1,58 @@
+# Makefile for common gnupg modules
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+EXTRA_DIST = mkerrors mkerrtok
+#INCLUDES = 
+
+noinst_LIBRARIES = libcommon.a libsimple-pwquery.a
+
+AM_CPPFLAGS =  $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
+
+libcommon_a_SOURCES = \
+       util.h i18n.h \
+       errors.h \
+       maperror.c \
+       sysutils.c sysutils.h \
+       cryptmiss.c \
+       gettime.c \
+       yesno.c \
+       miscellaneous.c \
+       membuf.c membuf.h \
+       iobuf.c iobuf.h \
+       ttyio.c ttyio.h \
+       signal.c
+
+
+libcommon_a_LIBADD = @LIBOBJS@
+
+libsimple_pwquery_a_SOURCES = \
+       simple-pwquery.c simple-pwquery.h
+
+libsimple_pwquery_a_LIBADD = @LIBOBJS@
+
+
+
+
+
+
+
+
+
diff --git a/common/README b/common/README
new file mode 100644 (file)
index 0000000..a90224b
--- /dev/null
@@ -0,0 +1,11 @@
+Stuff used by several modules of GnuPG.
+
+These directories use it:
+
+gpg
+sm
+agent
+
+These directories don't use it:
+
+kbx
\ No newline at end of file
diff --git a/common/errors.h b/common/errors.h
new file mode 100644 (file)
index 0000000..a5643f0
--- /dev/null
@@ -0,0 +1,110 @@
+/* errors.h - Globally used error codes
+ *     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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_COMMON_ERRORS_H
+#define GNUPG_COMMON_ERRORS_H
+
+#include "util.h"
+
+/* Status codes - fixme: should go into another file */
+enum {
+  STATUS_ENTER,
+  STATUS_LEAVE,
+  STATUS_ABORT,
+  STATUS_GOODSIG,
+  STATUS_BADSIG,
+  STATUS_ERRSIG,
+  STATUS_BADARMOR,
+  STATUS_RSA_OR_IDEA,
+  STATUS_SIGEXPIRED,
+  STATUS_KEYREVOKED,
+  STATUS_TRUST_UNDEFINED,
+  STATUS_TRUST_NEVER,
+  STATUS_TRUST_MARGINAL,
+  STATUS_TRUST_FULLY,
+  STATUS_TRUST_ULTIMATE,
+  
+  STATUS_SHM_INFO,
+  STATUS_SHM_GET,
+  STATUS_SHM_GET_BOOL,
+  STATUS_SHM_GET_HIDDEN,
+  
+  STATUS_NEED_PASSPHRASE,
+  STATUS_VALIDSIG,
+  STATUS_SIG_ID,
+  STATUS_ENC_TO,
+  STATUS_NODATA,
+  STATUS_BAD_PASSPHRASE,
+  STATUS_NO_PUBKEY,
+  STATUS_NO_SECKEY,
+  STATUS_NEED_PASSPHRASE_SYM,
+  STATUS_DECRYPTION_FAILED,
+  STATUS_DECRYPTION_OKAY,
+  STATUS_MISSING_PASSPHRASE,
+  STATUS_GOOD_PASSPHRASE,
+  STATUS_GOODMDC,
+  STATUS_BADMDC,
+  STATUS_ERRMDC,
+  STATUS_IMPORTED,
+  STATUS_IMPORT_PROBLEM,
+  STATUS_IMPORT_RES,
+  STATUS_FILE_START,
+  STATUS_FILE_DONE,
+  STATUS_FILE_ERROR,
+  
+  STATUS_BEGIN_DECRYPTION,
+  STATUS_END_DECRYPTION,
+  STATUS_BEGIN_ENCRYPTION,
+  STATUS_END_ENCRYPTION,
+  
+  STATUS_DELETE_PROBLEM,
+  STATUS_GET_BOOL,
+  STATUS_GET_LINE,
+  STATUS_GET_HIDDEN,
+  STATUS_GOT_IT,
+  STATUS_PROGRESS,
+  STATUS_SIG_CREATED,
+  STATUS_SESSION_KEY,
+  STATUS_NOTATION_NAME,
+  STATUS_NOTATION_DATA,
+  STATUS_POLICY_URL,
+  STATUS_BEGIN_STREAM,
+  STATUS_END_STREAM,
+  STATUS_KEY_CREATED,
+  STATUS_USERID_HIN,
+  STATUS_UNEXPECTED,
+  STATUS_INV_RECP,
+  STATUS_NO_RECP,
+  STATUS_ALREADY_SIGNED,
+
+  STATUS_EXPSIG,
+  STATUS_EXPKEYSIG,
+
+  STATUS_TRUNCATED,
+  STATUS_ERROR
+};
+
+
+/*-- errors.c (build by mkerror and mkerrtok) --*/
+const char *gnupg_strerror (int err);
+const char *gnupg_error_token (int err);
+
+
+#endif /*GNUPG_COMMON_ERRORS_H*/
diff --git a/common/gettime.c b/common/gettime.c
new file mode 100644 (file)
index 0000000..a7914d3
--- /dev/null
@@ -0,0 +1,250 @@
+/* gettime.c - Wrapper for time functions
+ *     Copyright (C) 1998, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <time.h>
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+
+#include "util.h"
+
+static unsigned long timewarp;
+static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode;
+
+/* Wrapper for the time(3).  We use this here so we can fake the time
+   for tests */
+time_t 
+gnupg_get_time () 
+{
+  time_t current = time (NULL);
+  if (timemode == NORMAL)
+    return current;
+  else if (timemode == FROZEN)
+    return timewarp;
+  else if (timemode == FUTURE)
+    return current + timewarp;
+  else
+    return current - timewarp;
+}
+
+/* set the time to NEWTIME so that gnupg_get_time returns a time
+   starting with this one.  With FREEZE set to 1 the returned time
+   will never change.  Just for completeness, a value of (time_t)-1
+   for NEWTIME gets you back to rality. Note that this is obviously
+   not thread-safe but this is not required. */
+void
+gnupg_set_time (time_t newtime, int freeze)
+{
+  time_t current = time (NULL);
+
+  if ( newtime == (time_t)-1 || current == newtime)
+    {
+      timemode = NORMAL;
+      timewarp = 0;
+    }
+  else if (freeze)
+    {
+      timemode = FROZEN;
+      timewarp = current;
+    }
+  else if (newtime > current)
+    {
+      timemode = FUTURE;
+      timewarp = newtime - current;
+    }
+  else
+    {
+      timemode = PAST;
+      timewarp = current - newtime;
+    }
+}
+
+/* Returns true when we are in timewarp mode */
+int
+gnupg_faked_time_p (void)
+{
+  return timemode;
+}
+
+
+/* This function is used by gpg because OpenPGP defines the timestamp
+   as an unsigned 32 bit value. */
+u32 
+make_timestamp (void)
+{
+  time_t t = gnupg_get_time ();
+
+  if (t == (time_t)-1)
+    log_fatal ("gnupg_get_time() failed\n");
+  return (u32)t;
+}
+
+
+
+/****************
+ * Scan a date string and return a timestamp.
+ * The only supported format is "yyyy-mm-dd"
+ * Returns 0 for an invalid date.
+ */
+u32
+scan_isodatestr( const char *string )
+{
+    int year, month, day;
+    struct tm tmbuf;
+    time_t stamp;
+    int i;
+
+    if( strlen(string) != 10 || string[4] != '-' || string[7] != '-' )
+       return 0;
+    for( i=0; i < 4; i++ )
+       if( !digitp (string+i) )
+           return 0;
+    if( !digitp (string+5) || !digitp(string+6) )
+       return 0;
+    if( !digitp(string+8) || !digitp(string+9) )
+       return 0;
+    year = atoi(string);
+    month = atoi(string+5);
+    day = atoi(string+8);
+    /* some basic checks */
+    if( year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 )
+       return 0;
+    memset( &tmbuf, 0, sizeof tmbuf );
+    tmbuf.tm_mday = day;
+    tmbuf.tm_mon = month-1;
+    tmbuf.tm_year = year - 1900;
+    tmbuf.tm_isdst = -1;
+    stamp = mktime( &tmbuf );
+    if( stamp == (time_t)-1 )
+       return 0;
+    return stamp;
+}
+
+
+u32
+add_days_to_timestamp( u32 stamp, u16 days )
+{
+    return stamp + days*86400L;
+}
+
+
+/****************
+ * Return a string with a time value in the form: x Y, n D, n H
+ */
+
+const char *
+strtimevalue( u32 value )
+{
+    static char buffer[30];
+    unsigned int years, days, hours, minutes;
+
+    value /= 60;
+    minutes = value % 60;
+    value /= 60;
+    hours = value % 24;
+    value /= 24;
+    days = value % 365;
+    value /= 365;
+    years = value;
+
+    sprintf(buffer,"%uy%ud%uh%um", years, days, hours, minutes );
+    if( years )
+       return buffer;
+    if( days )
+       return strchr( buffer, 'y' ) + 1;
+    return strchr( buffer, 'd' ) + 1;
+}
+
+
+/****************
+ * Note: this function returns GMT
+ */
+const char *
+strtimestamp( u32 stamp )
+{
+    static char buffer[11+5];
+    struct tm *tp;
+    time_t atime = stamp;
+    
+    if (atime < 0) {
+        strcpy (buffer, "????" "-??" "-??");
+    }
+    else {
+        tp = gmtime( &atime );
+        sprintf(buffer,"%04d-%02d-%02d",
+                1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
+    }
+    return buffer;
+}
+
+/****************
+ * Note: this function returns local time
+ */
+const char *
+asctimestamp( u32 stamp )
+{
+    static char buffer[50];
+#if defined (HAVE_STRFTIME) && defined (HAVE_NL_LANGINFO)
+      static char fmt[50];
+#endif
+    struct tm *tp;
+    time_t atime = stamp;
+
+    if (atime < 0) {
+        strcpy (buffer, "????" "-??" "-??");
+        return buffer;
+    }
+
+    tp = localtime( &atime );
+#ifdef HAVE_STRFTIME
+#if defined(HAVE_NL_LANGINFO)
+      mem2str( fmt, nl_langinfo(D_T_FMT), DIM(fmt)-3 );
+      if( strstr( fmt, "%Z" ) == NULL )
+       strcat( fmt, " %Z");
+      strftime( buffer, DIM(buffer)-1, fmt, tp );
+#else
+      /* fixme: we should check whether the locale appends a " %Z"
+       * These locales from glibc don't put the " %Z":
+       * fi_FI hr_HR ja_JP lt_LT lv_LV POSIX ru_RU ru_SU sv_FI sv_SE zh_CN
+       */
+      strftime( buffer, DIM(buffer)-1, "%c %Z", tp );
+#endif
+    buffer[DIM(buffer)-1] = 0;
+#else
+    mem2str( buffer, asctime(tp), DIM(buffer) );
+#endif
+    return buffer;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/iobuf.c b/common/iobuf.c
new file mode 100644 (file)
index 0000000..773e299
--- /dev/null
@@ -0,0 +1,2415 @@
+/* iobuf.c  -  file handling
+ * Copyright (C) 1998, 1999, 2000, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_DOSISH_SYSTEM
+#include <windows.h>
+#endif
+#ifdef __riscos__
+#include <kernel.h>
+#include <swis.h>
+#endif /* __riscos__ */
+
+#include "memory.h"
+#include "util.h"
+#include "iobuf.h"
+
+#undef FILE_FILTER_USES_STDIO
+
+#ifdef HAVE_DOSISH_SYSTEM
+#define USE_SETMODE 1
+#endif
+
+#ifdef FILE_FILTER_USES_STDIO
+#define my_fileno(a)  fileno ((a))
+#define my_fopen_ro(a,b) fopen ((a),(b))
+#define my_fopen(a,b)    fopen ((a),(b))
+typedef FILE *FILEP_OR_FD;
+#define INVALID_FP    NULL
+#define FILEP_OR_FD_FOR_STDIN  (stdin)
+#define FILEP_OR_FD_FOR_STDOUT  (stdout)
+typedef struct
+{
+  FILE *fp;                    /* open file handle */
+  int keep_open;
+  int no_cache;
+  int print_only_name;         /* flags indicating that fname is not a real file */
+  char fname[1];               /* name of the file */
+}
+file_filter_ctx_t;
+#else
+#define my_fileno(a)  (a)
+#define my_fopen_ro(a,b) fd_cache_open ((a),(b))
+#define my_fopen(a,b) direct_open ((a),(b))
+#ifdef HAVE_DOSISH_SYSTEM
+typedef HANDLE FILEP_OR_FD;
+#define INVALID_FP  ((HANDLE)-1)
+#define FILEP_OR_FD_FOR_STDIN  (GetStdHandle (STD_INPUT_HANDLE))
+#define FILEP_OR_FD_FOR_STDOUT (GetStdHandle (STD_OUTPUT_HANDLE))
+#undef USE_SETMODE
+#else
+typedef int FILEP_OR_FD;
+#define INVALID_FP  (-1)
+#define FILEP_OR_FD_FOR_STDIN  (0)
+#define FILEP_OR_FD_FOR_STDOUT (1)
+#endif
+typedef struct
+{
+  FILEP_OR_FD fp;              /* open file handle */
+  int keep_open;
+  int no_cache;
+  int eof_seen;
+  int print_only_name;         /* flags indicating that fname is not a real file */
+  char fname[1];               /* name of the file */
+}
+file_filter_ctx_t;
+
+struct close_cache_s
+{
+  struct close_cache_s *next;
+  FILEP_OR_FD fp;
+  char fname[1];
+};
+typedef struct close_cache_s *CLOSE_CACHE;
+static CLOSE_CACHE close_cache;
+#endif
+
+#ifdef __MINGW32__
+typedef struct
+{
+  int sock;
+  int keep_open;
+  int no_cache;
+  int eof_seen;
+  int print_only_name;         /* flags indicating that fname is not a real file */
+  char fname[1];               /* name of the file */
+}
+sock_filter_ctx_t;
+#endif /*__MINGW32__*/
+
+/* The first partial length header block must be of size 512
+ * to make it easier (and efficienter) we use a min. block size of 512
+ * for all chunks (but the last one) */
+#define OP_MIN_PARTIAL_CHUNK     512
+#define OP_MIN_PARTIAL_CHUNK_2POW 9
+
+typedef struct
+{
+  int use;
+  size_t size;
+  size_t count;
+  int partial;                 /* 1 = partial header, 2 in last partial packet */
+  char *buffer;                        /* used for partial header */
+  size_t buflen;               /* used size of buffer */
+  int first_c;                 /* of partial header (which is > 0) */
+  int eof;
+}
+block_filter_ctx_t;
+
+static int special_names_enabled;
+
+static int underflow (iobuf_t a);
+static int translate_file_handle (int fd, int for_write);
+
+#ifndef FILE_FILTER_USES_STDIO
+
+/*
+ * Invalidate (i.e. close) a cached iobuf
+ */
+static void
+fd_cache_invalidate (const char *fname)
+{
+  CLOSE_CACHE cc;
+
+  assert (fname);
+  if (DBG_IOBUF)
+    log_debug ("fd_cache_invalidate (%s)\n", fname);
+
+  for (cc = close_cache; cc; cc = cc->next)
+    {
+      if (cc->fp != INVALID_FP && !strcmp (cc->fname, fname))
+       {
+         if (DBG_IOBUF)
+           log_debug ("                did (%s)\n", cc->fname);
+#ifdef HAVE_DOSISH_SYSTEM
+         CloseHandle (cc->fp);
+#else
+         close (cc->fp);
+#endif
+         cc->fp = INVALID_FP;
+       }
+    }
+}
+
+
+
+static FILEP_OR_FD
+direct_open (const char *fname, const char *mode)
+{
+#ifdef HAVE_DOSISH_SYSTEM
+  unsigned long da, cd, sm;
+  HANDLE hfile;
+
+  /* Note, that we do not handle all mode combinations */
+
+  /* According to the ReactOS source it seems that open() of the
+   * standard MSW32 crt does open the file in share mode which is
+   * something new for MS applications ;-)
+   */
+  if (strchr (mode, '+'))
+    {
+      fd_cache_invalidate (fname);
+      da = GENERIC_READ | GENERIC_WRITE;
+      cd = OPEN_EXISTING;
+      sm = FILE_SHARE_READ | FILE_SHARE_WRITE;
+    }
+  else if (strchr (mode, 'w'))
+    {
+      fd_cache_invalidate (fname);
+      da = GENERIC_WRITE;
+      cd = CREATE_ALWAYS;
+      sm = FILE_SHARE_WRITE;
+    }
+  else
+    {
+      da = GENERIC_READ;
+      cd = OPEN_EXISTING;
+      sm = FILE_SHARE_READ;
+    }
+
+  hfile = CreateFile (fname, da, sm, NULL, cd, FILE_ATTRIBUTE_NORMAL, NULL);
+  return hfile;
+#else
+  int oflag;
+  int cflag = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+
+  /* Note, that we do not handle all mode combinations */
+  if (strchr (mode, '+'))
+    {
+      fd_cache_invalidate (fname);
+      oflag = O_RDWR;
+    }
+  else if (strchr (mode, 'w'))
+    {
+      fd_cache_invalidate (fname);
+      oflag = O_WRONLY | O_CREAT | O_TRUNC;
+    }
+  else
+    {
+      oflag = O_RDONLY;
+    }
+#ifdef O_BINARY
+  if (strchr (mode, 'b'))
+    oflag |= O_BINARY;
+#endif
+#ifndef __riscos__
+  return open (fname, oflag, cflag);
+#else
+  {
+    struct stat buf;
+    int rc = stat (fname, &buf);
+
+    /* Don't allow iobufs on directories */
+    if (!rc && S_ISDIR (buf.st_mode) && !S_ISREG (buf.st_mode))
+      return __set_errno (EISDIR);
+    else
+      return open (fname, oflag, cflag);
+  }
+#endif
+#endif
+}
+
+
+/*
+ * Instead of closing an FD we keep it open and cache it for later reuse 
+ * Note that this caching strategy only works if the process does not chdir.
+ */
+static void
+fd_cache_close (const char *fname, FILEP_OR_FD fp)
+{
+  CLOSE_CACHE cc;
+
+  assert (fp);
+  if (!fname || !*fname)
+    {
+#ifdef HAVE_DOSISH_SYSTEM
+      CloseHandle (fp);
+#else
+      close (fp);
+#endif
+      if (DBG_IOBUF)
+       log_debug ("fd_cache_close (%p) real\n", (void *) fp);
+      return;
+    }
+  /* try to reuse a slot */
+  for (cc = close_cache; cc; cc = cc->next)
+    {
+      if (cc->fp == INVALID_FP && !strcmp (cc->fname, fname))
+       {
+         cc->fp = fp;
+         if (DBG_IOBUF)
+           log_debug ("fd_cache_close (%s) used existing slot\n", fname);
+         return;
+       }
+    }
+  /* add a new one */
+  if (DBG_IOBUF)
+    log_debug ("fd_cache_close (%s) new slot created\n", fname);
+  cc = xcalloc (1, sizeof *cc + strlen (fname));
+  strcpy (cc->fname, fname);
+  cc->fp = fp;
+  cc->next = close_cache;
+  close_cache = cc;
+}
+
+/*
+ * Do an direct_open on FNAME but first try to reuse one from the fd_cache
+ */
+static FILEP_OR_FD
+fd_cache_open (const char *fname, const char *mode)
+{
+  CLOSE_CACHE cc;
+
+  assert (fname);
+  for (cc = close_cache; cc; cc = cc->next)
+    {
+      if (cc->fp != INVALID_FP && !strcmp (cc->fname, fname))
+       {
+         FILEP_OR_FD fp = cc->fp;
+         cc->fp = INVALID_FP;
+         if (DBG_IOBUF)
+           log_debug ("fd_cache_open (%s) using cached fp\n", fname);
+#ifdef HAVE_DOSISH_SYSTEM
+         if (SetFilePointer (fp, 0, NULL, FILE_BEGIN) == 0xffffffff)
+           {
+             log_error ("rewind file failed on handle %p: ec=%d\n",
+                        fp, (int) GetLastError ());
+             fp = INVALID_FP;
+           }
+#else
+         if (lseek (fp, 0, SEEK_SET) == (off_t) - 1)
+           {
+             log_error ("can't rewind fd %d: %s\n", fp, strerror (errno));
+             fp = INVALID_FP;
+           }
+#endif
+         return fp;
+       }
+    }
+  if (DBG_IOBUF)
+    log_debug ("fd_cache_open (%s) not cached\n", fname);
+  return direct_open (fname, mode);
+}
+
+
+#endif /*FILE_FILTER_USES_STDIO */
+
+
+/****************
+ * Read data from a file into buf which has an allocated length of *LEN.
+ * return the number of read bytes in *LEN. OPAQUE is the FILE * of
+ * the stream. A is not used.
+ * control may be:
+ * IOBUFCTRL_INIT: called just before the function is linked into the
+ *                list of function. This can be used to prepare internal
+ *                data structures of the function.
+ * IOBUFCTRL_FREE: called just before the function is removed from the
+ *                 list of functions and can be used to release internal
+ *                 data structures or close a file etc.
+ * IOBUFCTRL_UNDERFLOW: called by iobuf_underflow to fill the buffer
+ *                 with new stuff. *RET_LEN is the available size of the
+ *                 buffer, and should be set to the number of bytes
+ *                 which were put into the buffer. The function
+ *                 returns 0 to indicate success, -1 on EOF and
+ *                 GPG_ERR_xxxxx for other errors.
+ *
+ * IOBUFCTRL_FLUSH: called by iobuf_flush() to write out the collected stuff.
+ *                 *RET_LAN is the number of bytes in BUF.
+ *
+ * IOBUFCTRL_CANCEL: send to all filters on behalf of iobuf_cancel.  The
+ *                 filter may take appropriate action on this message.
+ */
+static int
+file_filter (void *opaque, int control, iobuf_t chain, byte * buf,
+            size_t * ret_len)
+{
+  file_filter_ctx_t *a = opaque;
+  FILEP_OR_FD f = a->fp;
+  size_t size = *ret_len;
+  size_t nbytes = 0;
+  int rc = 0;
+
+#ifdef FILE_FILTER_USES_STDIO
+  if (control == IOBUFCTRL_UNDERFLOW)
+    {
+      assert (size);           /* need a buffer */
+      if (feof (f))
+       {                       /* On terminals you could easiely read as many EOFs as you call         */
+         rc = -1;              /* fread() or fgetc() repeatly. Every call will block until you press   */
+         *ret_len = 0;         /* CTRL-D. So we catch this case before we call fread() again.          */
+       }
+      else
+       {
+         clearerr (f);
+         nbytes = fread (buf, 1, size, f);
+         if (feof (f) && !nbytes)
+           {
+             rc = -1;          /* okay: we can return EOF now. */
+           }
+         else if (ferror (f) && errno != EPIPE)
+           {
+             rc = gpg_error_from_errno (errno);
+             log_error ("%s: read error: %s\n", a->fname, strerror (errno));
+           }
+         *ret_len = nbytes;
+       }
+    }
+  else if (control == IOBUFCTRL_FLUSH)
+    {
+      if (size)
+       {
+         clearerr (f);
+         nbytes = fwrite (buf, 1, size, f);
+         if (ferror (f))
+           {
+             rc = gpg_error_from_errno (errno);
+             log_error ("%s: write error: %s\n", a->fname, strerror (errno));
+           }
+       }
+      *ret_len = nbytes;
+    }
+  else if (control == IOBUFCTRL_INIT)
+    {
+      a->keep_open = a->no_cache = 0;
+    }
+  else if (control == IOBUFCTRL_DESC)
+    {
+      *(char **) buf = "file_filter";
+    }
+  else if (control == IOBUFCTRL_FREE)
+    {
+      if (f != stdin && f != stdout)
+       {
+         if (DBG_IOBUF)
+           log_debug ("%s: close fd %d\n", a->fname, fileno (f));
+         if (!a->keep_open)
+           fclose (f);
+       }
+      f = NULL;
+      xfree (a);               /* we can free our context now */
+    }
+#else /* !stdio implementation */
+
+  if (control == IOBUFCTRL_UNDERFLOW)
+    {
+      assert (size);           /* need a buffer */
+      if (a->eof_seen)
+       {
+         rc = -1;
+         *ret_len = 0;
+       }
+      else
+       {
+#ifdef HAVE_DOSISH_SYSTEM
+         unsigned long nread;
+
+         nbytes = 0;
+         if (!ReadFile (f, buf, size, &nread, NULL))
+           {
+             int ec = (int) GetLastError ();
+             if (ec != ERROR_BROKEN_PIPE)
+               {
+                 rc = gpg_error_from_errno (ec);
+                 log_error ("%s: read error: ec=%d\n", a->fname, ec);
+               }
+           }
+         else if (!nread)
+           {
+             a->eof_seen = 1;
+             rc = -1;
+           }
+         else
+           {
+             nbytes = nread;
+           }
+
+#else
+
+         int n;
+
+         nbytes = 0;
+         do
+           {
+             n = read (f, buf, size);
+           }
+         while (n == -1 && errno == EINTR);
+         if (n == -1)
+           {                   /* error */
+             if (errno != EPIPE)
+               {
+                 rc = gpg_error_from_errno (errno);
+                 log_error ("%s: read error: %s\n",
+                            a->fname, strerror (errno));
+               }
+           }
+         else if (!n)
+           {                   /* eof */
+             a->eof_seen = 1;
+             rc = -1;
+           }
+         else
+           {
+             nbytes = n;
+           }
+#endif
+         *ret_len = nbytes;
+       }
+    }
+  else if (control == IOBUFCTRL_FLUSH)
+    {
+      if (size)
+       {
+#ifdef HAVE_DOSISH_SYSTEM
+         byte *p = buf;
+         unsigned long n;
+
+         nbytes = size;
+         do
+           {
+             if (size && !WriteFile (f, p, nbytes, &n, NULL))
+               {
+                 int ec = (int) GetLastError ();
+                 rc = gpg_error_from_errno (ec);
+                 log_error ("%s: write error: ec=%d\n", a->fname, ec);
+                 break;
+               }
+             p += n;
+             nbytes -= n;
+           }
+         while (nbytes);
+         nbytes = p - buf;
+#else
+         byte *p = buf;
+         int n;
+
+         nbytes = size;
+         do
+           {
+             do
+               {
+                 n = write (f, p, nbytes);
+               }
+             while (n == -1 && errno == EINTR);
+             if (n > 0)
+               {
+                 p += n;
+                 nbytes -= n;
+               }
+           }
+         while (n != -1 && nbytes);
+         if (n == -1)
+           {
+             rc = gpg_error_from_errno (errno);
+             log_error ("%s: write error: %s\n", a->fname, strerror (errno));
+           }
+         nbytes = p - buf;
+#endif
+       }
+      *ret_len = nbytes;
+    }
+  else if (control == IOBUFCTRL_INIT)
+    {
+      a->eof_seen = 0;
+      a->keep_open = 0;
+      a->no_cache = 0;
+    }
+  else if (control == IOBUFCTRL_DESC)
+    {
+      *(char **) buf = "file_filter(fd)";
+    }
+  else if (control == IOBUFCTRL_FREE)
+    {
+#ifdef HAVE_DOSISH_SYSTEM
+      if (f != FILEP_OR_FD_FOR_STDIN && f != FILEP_OR_FD_FOR_STDOUT)
+       {
+         if (DBG_IOBUF)
+           log_debug ("%s: close handle %p\n", a->fname, f);
+         if (!a->keep_open)
+           fd_cache_close (a->no_cache ? NULL : a->fname, f);
+       }
+#else
+      if ((int) f != 0 && (int) f != 1)
+       {
+         if (DBG_IOBUF)
+           log_debug ("%s: close fd %d\n", a->fname, f);
+         if (!a->keep_open)
+           fd_cache_close (a->no_cache ? NULL : a->fname, f);
+       }
+      f = INVALID_FP;
+#endif
+      xfree (a);               /* we can free our context now */
+    }
+#endif /* !stdio implementation */
+  return rc;
+}
+
+#ifdef __MINGW32__
+/* Becuase sockets are an special object under Lose32 we have to
+ * use a special filter */
+static int
+sock_filter (void *opaque, int control, iobuf_t chain, byte * buf,
+            size_t * ret_len)
+{
+  sock_filter_ctx_t *a = opaque;
+  size_t size = *ret_len;
+  size_t nbytes = 0;
+  int rc = 0;
+
+  if (control == IOBUFCTRL_UNDERFLOW)
+    {
+      assert (size);           /* need a buffer */
+      if (a->eof_seen)
+       {
+         rc = -1;
+         *ret_len = 0;
+       }
+      else
+       {
+         int nread;
+
+         nread = recv (a->sock, buf, size, 0);
+         if (nread == SOCKET_ERROR)
+           {
+             int ec = (int) WSAGetLastError ();
+             rc = gpg_error_from_errno (ec);
+             log_error ("socket read error: ec=%d\n", ec);
+           }
+         else if (!nread)
+           {
+             a->eof_seen = 1;
+             rc = -1;
+           }
+         else
+           {
+             nbytes = nread;
+           }
+         *ret_len = nbytes;
+       }
+    }
+  else if (control == IOBUFCTRL_FLUSH)
+    {
+      if (size)
+       {
+         byte *p = buf;
+         int n;
+
+         nbytes = size;
+         do
+           {
+             n = send (a->sock, p, nbytes, 0);
+             if (n == SOCKET_ERROR)
+               {
+                 int ec = (int) WSAGetLastError ();
+                 rc = gpg_error_from_errno (ec);
+                 log_error ("socket write error: ec=%d\n", ec);
+                 break;
+               }
+             p += n;
+             nbytes -= n;
+           }
+         while (nbytes);
+         nbytes = p - buf;
+       }
+      *ret_len = nbytes;
+    }
+  else if (control == IOBUFCTRL_INIT)
+    {
+      a->eof_seen = 0;
+      a->keep_open = 0;
+      a->no_cache = 0;
+    }
+  else if (control == IOBUFCTRL_DESC)
+    {
+      *(char **) buf = "sock_filter";
+    }
+  else if (control == IOBUFCTRL_FREE)
+    {
+      if (!a->keep_open)
+       closesocket (a->sock);
+      xfree (a);               /* we can free our context now */
+    }
+  return rc;
+}
+#endif /*__MINGW32__*/
+
+/****************
+ * This is used to implement the block write mode.
+ * Block reading is done on a byte by byte basis in readbyte(),
+ * without a filter
+ */
+static int
+block_filter (void *opaque, int control, iobuf_t chain, byte * buf,
+             size_t * ret_len)
+{
+  block_filter_ctx_t *a = opaque;
+  size_t size = *ret_len;
+  int c, needed, rc = 0;
+  char *p;
+
+  if (control == IOBUFCTRL_UNDERFLOW)
+    {
+      size_t n = 0;
+
+      p = buf;
+      assert (size);           /* need a buffer */
+      if (a->eof)              /* don't read any further */
+       rc = -1;
+      while (!rc && size)
+       {
+         if (!a->size)
+           {                   /* get the length bytes */
+             if (a->partial == 2)
+               {
+                 a->eof = 1;
+                 if (!n)
+                   rc = -1;
+                 break;
+               }
+             else if (a->partial)
+               {
+                 /* These OpenPGP introduced huffman like encoded length
+                  * bytes are really a mess :-( */
+                 if (a->first_c)
+                   {
+                     c = a->first_c;
+                     a->first_c = 0;
+                   }
+                 else if ((c = iobuf_get (chain)) == -1)
+                   {
+                     log_error ("block_filter: 1st length byte missing\n");
+                     rc = GPG_ERR_BAD_DATA;
+                     break;
+                   }
+                 if (c < 192)
+                   {
+                     a->size = c;
+                     a->partial = 2;
+                     if (!a->size)
+                       {
+                         a->eof = 1;
+                         if (!n)
+                           rc = -1;
+                         break;
+                       }
+                   }
+                 else if (c < 224)
+                   {
+                     a->size = (c - 192) * 256;
+                     if ((c = iobuf_get (chain)) == -1)
+                       {
+                         log_error
+                           ("block_filter: 2nd length byte missing\n");
+                         rc = GPG_ERR_BAD_DATA;
+                         break;
+                       }
+                     a->size += c + 192;
+                     a->partial = 2;
+                     if (!a->size)
+                       {
+                         a->eof = 1;
+                         if (!n)
+                           rc = -1;
+                         break;
+                       }
+                   }
+                 else if (c == 255)
+                   {
+                     a->size = iobuf_get (chain) << 24;
+                     a->size |= iobuf_get (chain) << 16;
+                     a->size |= iobuf_get (chain) << 8;
+                     if ((c = iobuf_get (chain)) == -1)
+                       {
+                         log_error ("block_filter: invalid 4 byte length\n");
+                         rc = GPG_ERR_BAD_DATA;
+                         break;
+                       }
+                     a->size |= c;
+                   }
+                 else
+                   {           /* next partial body length */
+                     a->size = 1 << (c & 0x1f);
+                   }
+                 /*  log_debug("partial: ctx=%p c=%02x size=%u\n", a, c, a->size); */
+               }
+             else
+               {               /* the gnupg partial length scheme - much better :-) */
+                 c = iobuf_get (chain);
+                 a->size = c << 8;
+                 c = iobuf_get (chain);
+                 a->size |= c;
+                 if (c == -1)
+                   {
+                     log_error ("block_filter: error reading length info\n");
+                     rc = GPG_ERR_BAD_DATA;
+                   }
+                 if (!a->size)
+                   {
+                     a->eof = 1;
+                     if (!n)
+                       rc = -1;
+                     break;
+                   }
+               }
+           }
+
+         while (!rc && size && a->size)
+           {
+             needed = size < a->size ? size : a->size;
+             c = iobuf_read (chain, p, needed);
+             if (c < needed)
+               {
+                 if (c == -1)
+                   c = 0;
+                 log_error
+                   ("block_filter %p: read error (size=%lu,a->size=%lu)\n",
+                    a, (ulong) size + c, (ulong) a->size + c);
+                 rc = GPG_ERR_BAD_DATA;
+               }
+             else
+               {
+                 size -= c;
+                 a->size -= c;
+                 p += c;
+                 n += c;
+               }
+           }
+       }
+      *ret_len = n;
+    }
+  else if (control == IOBUFCTRL_FLUSH)
+    {
+      if (a->partial)
+       {                       /* the complicated openpgp scheme */
+         size_t blen, n, nbytes = size + a->buflen;
+
+         assert (a->buflen <= OP_MIN_PARTIAL_CHUNK);
+         if (nbytes < OP_MIN_PARTIAL_CHUNK)
+           {
+             /* not enough to write a partial block out; so we store it */
+             if (!a->buffer)
+               a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK);
+             memcpy (a->buffer + a->buflen, buf, size);
+             a->buflen += size;
+           }
+         else
+           {                   /* okay, we can write out something */
+             /* do this in a loop to use the most efficient block lengths */
+             p = buf;
+             do
+               {
+                 /* find the best matching block length - this is limited
+                  * by the size of the internal buffering */
+                 for (blen = OP_MIN_PARTIAL_CHUNK * 2,
+                      c = OP_MIN_PARTIAL_CHUNK_2POW + 1; blen <= nbytes;
+                      blen *= 2, c++)
+                   ;
+                 blen /= 2;
+                 c--;
+                 /* write the partial length header */
+                 assert (c <= 0x1f);   /*;-) */
+                 c |= 0xe0;
+                 iobuf_put (chain, c);
+                 if ((n = a->buflen))
+                   {           /* write stuff from the buffer */
+                     assert (n == OP_MIN_PARTIAL_CHUNK);
+                     if (iobuf_write (chain, a->buffer, n))
+                       rc = gpg_error_from_errno (errno);
+                     a->buflen = 0;
+                     nbytes -= n;
+                   }
+                 if ((n = nbytes) > blen)
+                   n = blen;
+                 if (n && iobuf_write (chain, p, n))
+                   rc = gpg_error_from_errno (errno);
+                 p += n;
+                 nbytes -= n;
+               }
+             while (!rc && nbytes >= OP_MIN_PARTIAL_CHUNK);
+             /* store the rest in the buffer */
+             if (!rc && nbytes)
+               {
+                 assert (!a->buflen);
+                 assert (nbytes < OP_MIN_PARTIAL_CHUNK);
+                 if (!a->buffer)
+                   a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK);
+                 memcpy (a->buffer, p, nbytes);
+                 a->buflen = nbytes;
+               }
+           }
+       }
+      else
+       {                       /* the gnupg scheme (which is not openpgp compliant) */
+         size_t avail, n;
+
+         for (p = buf; !rc && size;)
+           {
+             n = size;
+             avail = a->size - a->count;
+             if (!avail)
+               {
+                 if (n > a->size)
+                   {
+                     iobuf_put (chain, (a->size >> 8) & 0xff);
+                     iobuf_put (chain, a->size & 0xff);
+                     avail = a->size;
+                     a->count = 0;
+                   }
+                 else
+                   {
+                     iobuf_put (chain, (n >> 8) & 0xff);
+                     iobuf_put (chain, n & 0xff);
+                     avail = n;
+                     a->count = a->size - n;
+                   }
+               }
+             if (n > avail)
+               n = avail;
+             if (iobuf_write (chain, p, n))
+               rc = gpg_error_from_errno (errno);
+             a->count += n;
+             p += n;
+             size -= n;
+           }
+       }
+    }
+  else if (control == IOBUFCTRL_INIT)
+    {
+      if (DBG_IOBUF)
+       log_debug ("init block_filter %p\n", a);
+      if (a->partial)
+       a->count = 0;
+      else if (a->use == 1)
+       a->count = a->size = 0;
+      else
+       a->count = a->size;     /* force first length bytes */
+      a->eof = 0;
+      a->buffer = NULL;
+      a->buflen = 0;
+    }
+  else if (control == IOBUFCTRL_DESC)
+    {
+      *(char **) buf = "block_filter";
+    }
+  else if (control == IOBUFCTRL_FREE)
+    {
+      if (a->use == 2)
+       {                       /* write the end markers */
+         if (a->partial)
+           {
+             u32 len;
+             /* write out the remaining bytes without a partial header
+              * the length of this header may be 0 - but if it is
+              * the first block we are not allowed to use a partial header
+              * and frankly we can't do so, because this length must be
+              * a power of 2. This is _really_ complicated because we
+              * have to check the possible length of a packet prior
+              * to it's creation: a chain of filters becomes complicated
+              * and we need a lot of code to handle compressed packets etc.
+              *   :-(((((((
+              */
+             /* construct header */
+             len = a->buflen;
+             /*log_debug("partial: remaining length=%u\n", len ); */
+             if (len < 192)
+               rc = iobuf_put (chain, len);
+             else if (len < 8384)
+               {
+                 if (!(rc = iobuf_put (chain, ((len - 192) / 256) + 192)))
+                   rc = iobuf_put (chain, ((len - 192) % 256));
+               }
+             else
+               {               /* use a 4 byte header */
+                 if (!(rc = iobuf_put (chain, 0xff)))
+                   if (!(rc = iobuf_put (chain, (len >> 24) & 0xff)))
+                     if (!(rc = iobuf_put (chain, (len >> 16) & 0xff)))
+                       if (!(rc = iobuf_put (chain, (len >> 8) & 0xff)))
+                         rc = iobuf_put (chain, len & 0xff);
+               }
+             if (!rc && len)
+               rc = iobuf_write (chain, a->buffer, len);
+             if (rc)
+               {
+                 log_error ("block_filter: write error: %s\n",
+                            strerror (errno));
+                 rc = gpg_error_from_errno (errno);
+               }
+             xfree (a->buffer);
+             a->buffer = NULL;
+             a->buflen = 0;
+           }
+         else
+           {
+             iobuf_writebyte (chain, 0);
+             iobuf_writebyte (chain, 0);
+           }
+       }
+      else if (a->size)
+       {
+         log_error ("block_filter: pending bytes!\n");
+       }
+      if (DBG_IOBUF)
+       log_debug ("free block_filter %p\n", a);
+      xfree (a);               /* we can free our context now */
+    }
+
+  return rc;
+}
+
+
+static void
+print_chain (iobuf_t a)
+{
+  if (!DBG_IOBUF)
+    return;
+  for (; a; a = a->chain)
+    {
+      size_t dummy_len = 0;
+      const char *desc = "[none]";
+
+      if (a->filter)
+       a->filter (a->filter_ov, IOBUFCTRL_DESC, NULL,
+                  (byte *) & desc, &dummy_len);
+
+      log_debug ("iobuf chain: %d.%d `%s' filter_eof=%d start=%d len=%d\n",
+                a->no, a->subno, desc, a->filter_eof,
+                (int) a->d.start, (int) a->d.len);
+    }
+}
+
+int
+iobuf_print_chain (iobuf_t a)
+{
+  print_chain (a);
+  return 0;
+}
+
+/****************
+ * Allocate a new io buffer, with no function assigned.
+ * Use is the desired usage: 1 for input, 2 for output, 3 for temp buffer
+ * BUFSIZE is a suggested buffer size.
+ */
+iobuf_t
+iobuf_alloc (int use, size_t bufsize)
+{
+  iobuf_t a;
+  static int number = 0;
+
+  a = xcalloc (1, sizeof *a);
+  a->use = use;
+  a->d.buf = xmalloc (bufsize);
+  a->d.size = bufsize;
+  a->no = ++number;
+  a->subno = 0;
+  a->opaque = NULL;
+  a->real_fname = NULL;
+  return a;
+}
+
+int
+iobuf_close (iobuf_t a)
+{
+  iobuf_t a2;
+  size_t dummy_len = 0;
+  int rc = 0;
+
+  if (a && a->directfp)
+    {
+      fclose (a->directfp);
+      xfree (a->real_fname);
+      if (DBG_IOBUF)
+       log_debug ("iobuf_close -> %p\n", a->directfp);
+      return 0;
+    }
+
+  for (; a && !rc; a = a2)
+    {
+      a2 = a->chain;
+      if (a->use == 2 && (rc = iobuf_flush (a)))
+       log_error ("iobuf_flush failed on close: %s\n", gpg_strerror (rc));
+
+      if (DBG_IOBUF)
+       log_debug ("iobuf-%d.%d: close `%s'\n", a->no, a->subno, a->desc);
+      if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_FREE,
+                                       a->chain, NULL, &dummy_len)))
+       log_error ("IOBUFCTRL_FREE failed on close: %s\n", gpg_strerror (rc));
+      xfree (a->real_fname);
+      if (a->d.buf)
+       {
+         memset (a->d.buf, 0, a->d.size);      /* erase the buffer */
+         xfree (a->d.buf);
+       }
+      xfree (a);
+    }
+  return rc;
+}
+
+int
+iobuf_cancel (iobuf_t a)
+{
+  const char *s;
+  iobuf_t a2;
+  int rc;
+#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
+  char *remove_name = NULL;
+#endif
+
+  if (a && a->use == 2)
+    {
+      s = iobuf_get_real_fname (a);
+      if (s && *s)
+       {
+#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
+         remove_name = m_strdup (s);
+#else
+         remove (s);
+#endif
+       }
+    }
+
+  /* send a cancel message to all filters */
+  for (a2 = a; a2; a2 = a2->chain)
+    {
+      size_t dummy;
+      if (a2->filter)
+       a2->filter (a2->filter_ov, IOBUFCTRL_CANCEL, a2->chain, NULL, &dummy);
+    }
+
+  rc = iobuf_close (a);
+#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
+  if (remove_name)
+    {
+      /* Argg, MSDOS does not allow to remove open files.  So
+       * we have to do it here */
+      remove (remove_name);
+      xfree (remove_name);
+    }
+#endif
+  return rc;
+}
+
+
+/****************
+ * create a temporary iobuf, which can be used to collect stuff
+ * in an iobuf and later be written by iobuf_write_temp() to another
+ * iobuf.
+ */
+iobuf_t
+iobuf_temp ()
+{
+  iobuf_t a;
+
+  a = iobuf_alloc (3, 8192);
+
+  return a;
+}
+
+iobuf_t
+iobuf_temp_with_content (const char *buffer, size_t length)
+{
+  iobuf_t a;
+
+  a = iobuf_alloc (3, length);
+  memcpy (a->d.buf, buffer, length);
+  a->d.len = length;
+
+  return a;
+}
+
+void
+iobuf_enable_special_filenames (int yes)
+{
+  special_names_enabled = yes;
+}
+
+/*
+ * see whether the filename has the for "-&nnnn", where n is a
+ * non-zero number.
+ * Returns this number or -1 if it is not the case.
+ */
+static int
+check_special_filename (const char *fname)
+{
+  if (special_names_enabled && fname && *fname == '-' && fname[1] == '&')
+    {
+      int i;
+
+      fname += 2;
+      for (i = 0; isdigit (fname[i]); i++)
+       ;
+      if (!fname[i])
+       return atoi (fname);
+    }
+  return -1;
+}
+
+/****************
+ * Create a head iobuf for reading from a file
+ * returns: NULL if an error occures and sets errno
+ */
+iobuf_t
+iobuf_open (const char *fname)
+{
+  iobuf_t a;
+  FILEP_OR_FD fp;
+  file_filter_ctx_t *fcx;
+  size_t len;
+  int print_only = 0;
+  int fd;
+
+  if (!fname || (*fname == '-' && !fname[1]))
+    {
+      fp = FILEP_OR_FD_FOR_STDIN;
+#ifdef USE_SETMODE
+      setmode (my_fileno (fp), O_BINARY);
+#endif
+      fname = "[stdin]";
+      print_only = 1;
+    }
+  else if ((fd = check_special_filename (fname)) != -1)
+    return iobuf_fdopen (translate_file_handle (fd, 0), "rb");
+  else if ((fp = my_fopen_ro (fname, "rb")) == INVALID_FP)
+    return NULL;
+  a = iobuf_alloc (1, 8192);
+  fcx = xmalloc (sizeof *fcx + strlen (fname));
+  fcx->fp = fp;
+  fcx->print_only_name = print_only;
+  strcpy (fcx->fname, fname);
+  if (!print_only)
+    a->real_fname = xstrdup (fname);
+  a->filter = file_filter;
+  a->filter_ov = fcx;
+  file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+  file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+  if (DBG_IOBUF)
+    log_debug ("iobuf-%d.%d: open `%s' fd=%d\n",
+              a->no, a->subno, fname, (int) my_fileno (fcx->fp));
+
+  return a;
+}
+
+/****************
+ * Create a head iobuf for reading from a file
+ * returns: NULL if an error occures and sets errno
+ */
+iobuf_t
+iobuf_fdopen (int fd, const char *mode)
+{
+  iobuf_t a;
+  FILEP_OR_FD fp;
+  file_filter_ctx_t *fcx;
+  size_t len;
+
+#ifdef FILE_FILTER_USES_STDIO
+  if (!(fp = fdopen (fd, mode)))
+    return NULL;
+#else
+  fp = (FILEP_OR_FD) fd;
+#endif
+  a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192);
+  fcx = xmalloc (sizeof *fcx + 20);
+  fcx->fp = fp;
+  fcx->print_only_name = 1;
+  sprintf (fcx->fname, "[fd %d]", fd);
+  a->filter = file_filter;
+  a->filter_ov = fcx;
+  file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+  file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+  if (DBG_IOBUF)
+    log_debug ("iobuf-%d.%d: fdopen `%s'\n", a->no, a->subno, fcx->fname);
+  iobuf_ioctl (a, 3, 1, NULL); /* disable fd caching */
+  return a;
+}
+
+
+iobuf_t
+iobuf_sockopen (int fd, const char *mode)
+{
+  iobuf_t a;
+#ifdef __MINGW32__
+  sock_filter_ctx_t *scx;
+  size_t len;
+
+  a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192);
+  scx = m_alloc (sizeof *scx + 25);
+  scx->sock = fd;
+  scx->print_only_name = 1;
+  sprintf (scx->fname, "[sock %d]", fd);
+  a->filter = sock_filter;
+  a->filter_ov = scx;
+  sock_filter (scx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+  sock_filter (scx, IOBUFCTRL_INIT, NULL, NULL, &len);
+  if (DBG_IOBUF)
+    log_debug ("iobuf-%d.%d: sockopen `%s'\n", a->no, a->subno, scx->fname);
+  iobuf_ioctl (a, 3, 1, NULL); /* disable fd caching */
+#else
+  a = iobuf_fdopen (fd, mode);
+#endif
+  return a;
+}
+
+/****************
+ * create an iobuf for writing to a file; the file will be created.
+ */
+iobuf_t
+iobuf_create (const char *fname)
+{
+  iobuf_t a;
+  FILEP_OR_FD fp;
+  file_filter_ctx_t *fcx;
+  size_t len;
+  int print_only = 0;
+  int fd;
+
+  if (!fname || (*fname == '-' && !fname[1]))
+    {
+      fp = FILEP_OR_FD_FOR_STDOUT;
+#ifdef USE_SETMODE
+      setmode (my_fileno (fp), O_BINARY);
+#endif
+      fname = "[stdout]";
+      print_only = 1;
+    }
+  else if ((fd = check_special_filename (fname)) != -1)
+    return iobuf_fdopen (translate_file_handle (fd, 1), "wb");
+  else if ((fp = my_fopen (fname, "wb")) == INVALID_FP)
+    return NULL;
+  a = iobuf_alloc (2, 8192);
+  fcx = xmalloc (sizeof *fcx + strlen (fname));
+  fcx->fp = fp;
+  fcx->print_only_name = print_only;
+  strcpy (fcx->fname, fname);
+  if (!print_only)
+    a->real_fname = xstrdup (fname);
+  a->filter = file_filter;
+  a->filter_ov = fcx;
+  file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+  file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+  if (DBG_IOBUF)
+    log_debug ("iobuf-%d.%d: create `%s'\n", a->no, a->subno, a->desc);
+
+  return a;
+}
+
+/****************
+ * append to an iobuf; if the file does not exist, create it.
+ * cannot be used for stdout.
+ * Note: This is not used.
+ */
+#if 0                          /* not used */
+iobuf_t
+iobuf_append (const char *fname)
+{
+  iobuf_t a;
+  FILE *fp;
+  file_filter_ctx_t *fcx;
+  size_t len;
+
+  if (!fname)
+    return NULL;
+  else if (!(fp = my_fopen (fname, "ab")))
+    return NULL;
+  a = iobuf_alloc (2, 8192);
+  fcx = m_alloc (sizeof *fcx + strlen (fname));
+  fcx->fp = fp;
+  strcpy (fcx->fname, fname);
+  a->real_fname = m_strdup (fname);
+  a->filter = file_filter;
+  a->filter_ov = fcx;
+  file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+  file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+  if (DBG_IOBUF)
+    log_debug ("iobuf-%d.%d: append `%s'\n", a->no, a->subno, a->desc);
+
+  return a;
+}
+#endif
+
+iobuf_t
+iobuf_openrw (const char *fname)
+{
+  iobuf_t a;
+  FILEP_OR_FD fp;
+  file_filter_ctx_t *fcx;
+  size_t len;
+
+  if (!fname)
+    return NULL;
+  else if ((fp = my_fopen (fname, "r+b")) == INVALID_FP)
+    return NULL;
+  a = iobuf_alloc (2, 8192);
+  fcx = xmalloc (sizeof *fcx + strlen (fname));
+  fcx->fp = fp;
+  strcpy (fcx->fname, fname);
+  a->real_fname = xstrdup (fname);
+  a->filter = file_filter;
+  a->filter_ov = fcx;
+  file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+  file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+  if (DBG_IOBUF)
+    log_debug ("iobuf-%d.%d: openrw `%s'\n", a->no, a->subno, a->desc);
+
+  return a;
+}
+
+
+int
+iobuf_ioctl (iobuf_t a, int cmd, int intval, void *ptrval)
+{
+  if (cmd == 1)
+    {                          /* keep system filepointer/descriptor open */
+      if (DBG_IOBUF)
+       log_debug ("iobuf-%d.%d: ioctl `%s' keep=%d\n",
+                  a ? a->no : -1, a ? a->subno : -1, a ? a->desc : "?",
+                  intval);
+      for (; a; a = a->chain)
+       if (!a->chain && a->filter == file_filter)
+         {
+           file_filter_ctx_t *b = a->filter_ov;
+           b->keep_open = intval;
+           return 0;
+         }
+#ifdef __MINGW32__
+       else if (!a->chain && a->filter == sock_filter)
+         {
+           sock_filter_ctx_t *b = a->filter_ov;
+           b->keep_open = intval;
+           return 0;
+         }
+#endif
+    }
+  else if (cmd == 2)
+    {                          /* invalidate cache */
+      if (DBG_IOBUF)
+       log_debug ("iobuf-*.*: ioctl `%s' invalidate\n",
+                  ptrval ? (char *) ptrval : "?");
+      if (!a && !intval && ptrval)
+       {
+#ifndef FILE_FILTER_USES_STDIO
+         fd_cache_invalidate (ptrval);
+#endif
+         return 0;
+       }
+    }
+  else if (cmd == 3)
+    {                          /* disallow/allow caching */
+      if (DBG_IOBUF)
+       log_debug ("iobuf-%d.%d: ioctl `%s' no_cache=%d\n",
+                  a ? a->no : -1, a ? a->subno : -1, a ? a->desc : "?",
+                  intval);
+      for (; a; a = a->chain)
+       if (!a->chain && a->filter == file_filter)
+         {
+           file_filter_ctx_t *b = a->filter_ov;
+           b->no_cache = intval;
+           return 0;
+         }
+#ifdef __MINGW32__
+       else if (!a->chain && a->filter == sock_filter)
+         {
+           sock_filter_ctx_t *b = a->filter_ov;
+           b->no_cache = intval;
+           return 0;
+         }
+#endif
+    }
+
+  return -1;
+}
+
+
+/****************
+ * Register an i/o filter.
+ */
+int
+iobuf_push_filter (iobuf_t a,
+                  int (*f) (void *opaque, int control,
+                            iobuf_t chain, byte * buf, size_t * len),
+                   void *ov)
+{
+  return iobuf_push_filter2 (a, f, ov, 0);
+}
+
+int
+iobuf_push_filter2 (iobuf_t a,
+                   int (*f) (void *opaque, int control,
+                             iobuf_t chain, byte * buf, size_t * len),
+                   void *ov, int rel_ov)
+{
+  iobuf_t b;
+  size_t dummy_len = 0;
+  int rc = 0;
+
+  if (a->directfp)
+    BUG ();
+
+  if (a->use == 2 && (rc = iobuf_flush (a)))
+    return rc;
+  /* make a copy of the current stream, so that
+   * A is the new stream and B the original one.
+   * The contents of the buffers are transferred to the
+   * new stream.
+   */
+  b = xmalloc (sizeof *b);
+  memcpy (b, a, sizeof *b);
+  /* fixme: it is stupid to keep a copy of the name at every level
+   * but we need the name somewhere because the name known by file_filter
+   * may have been released when we need the name of the file */
+  b->real_fname = a->real_fname ? xstrdup (a->real_fname) : NULL;
+  /* remove the filter stuff from the new stream */
+  a->filter = NULL;
+  a->filter_ov = NULL;
+  a->filter_ov_owner = 0;
+  a->filter_eof = 0;
+  if (a->use == 3)
+    a->use = 2;                        /* make a write stream from a temp stream */
+
+  if (a->use == 2)
+    {                          /* allocate a fresh buffer for the
+                                   original stream */
+      b->d.buf = xmalloc (a->d.size);
+      b->d.len = 0;
+      b->d.start = 0;
+    }
+  else
+    {                          /* allocate a fresh buffer for the new
+                                   stream */
+      a->d.buf = xmalloc (a->d.size);
+      a->d.len = 0;
+      a->d.start = 0;
+    }
+  /* disable nlimit for the new stream */
+  a->ntotal = b->ntotal + b->nbytes;
+  a->nlimit = a->nbytes = 0;
+  a->nofast &= ~1;
+  /* make a link from the new stream to the original stream */
+  a->chain = b;