diff --git a/Makefile.am b/Makefile.am index 9fafb1102..0c5fbe4c3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,78 +1,79 @@ # Makefile.am - main makefile for NewPG/GnuPG # Copyright (C) 2001, 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in ACLOCAL_AMFLAGS = -I m4 -I gl/m4 AUTOMAKE_OPTIONS = dist-bzip2 EXTRA_DIST = scripts/config.rpath autogen.sh README.CVS DISTCLEANFILES = g10defs.h if BUILD_GPGSM kbx = kbx else kbx = endif if BUILD_GPG gpg = g10 else gpg = endif if BUILD_GPGSM sm = sm else sm = endif if BUILD_AGENT agent = agent else agent = endif if BUILD_SCDAEMON scd = scd else scd = endif if HAVE_W32_SYSTEM tests = else tests = tests endif SUBDIRS = m4 intl gl jnlib common ${kbx} \ ${gpg} ${sm} ${agent} ${scd} tools po doc ${tests} dist-hook: @set -e; \ for file in `cd $(top_srcdir); \ find scripts include -type f -name distfiles`; do \ dir=`dirname $$file` ; $(mkinstalldirs) $(distdir)/$$dir ; \ for i in distfiles `cat $(top_srcdir)/$$file` ; do \ ln $(top_srcdir)/$$dir/$$i $(distdir)/$$dir/$$i 2> /dev/null \ || cp -p $(top_srcdir)/$$dir/$$i $(distdir)/$$dir/$$i; \ done ; \ done echo "$(VERSION)" > $(distdir)/VERSION diff --git a/NEWS b/NEWS index 6413242c6..679bf7d5b 100644 --- a/NEWS +++ b/NEWS @@ -1,318 +1,321 @@ Noteworthy changes in version 1.9.21 ------------------------------------------------- * [scdaemon] New command APDU. * [scdaemon] Support for keypads of some readers. Tested only with SPR532. New option --disable-keypad. * [scdaemon] Support for CardMan 4040 PCMCIA reader. * [scdaemon] Cards are not anymore reseted at the end of a connection. * [gpgsm] Kludge to allow use of Bundesnetzagentur issued certificates. * [scdaemon] Added --hash=xxx option to the PKSIGN command. + * [gpg-protect-tool] Does now create a MAC for P12 files. This is for + better interoperability. + Noteworthy changes in version 1.9.20 (2005-12-20) ------------------------------------------------- * Importing pkcs#12 files created be recent versions of Mozilla works again. * Basic support for qualified signatures. * New debug tool gpgparsemail. Noteworthy changes in version 1.9.19 (2005-09-12) ------------------------------------------------- * The Belgian eID card is now supported for signatures and ssh. Other pkcs#15 cards should work as well. * Fixed bug in --export-secret-key-p12 so that certificates are again included. Noteworthy changes in version 1.9.18 (2005-08-01) ------------------------------------------------- * [gpgsm] Now allows for more than one email address as well as URIs and dnsNames in certificate request generation. A keygrip may be given to create a request from an existing key. * A couple of minor bug fixes. Noteworthy changes in version 1.9.17 (2005-06-20) ------------------------------------------------- * gpg-connect-agent has now features to handle Assuan INQUIRE commands. * Internal changes for OpenPGP cards. New Assuan command WRITEKEY. * GNU Pth is now a hard requirement. * [scdaemon] Support for OpenSC has been removed. Instead a new and straightforward pkcs#15 modules has been written. As of now it does allows only signing using TCOS cards but we are going to enhance it to match all the old capabilities. * [gpg-agent] New option --write-env-file and Assuan command UPDATESTARTUPTTY. * [gpg-agent] New option --default-cache-ttl-ssh to set the TTL for SSH passphrase caching independent from the other passphrases. Noteworthy changes in version 1.9.16 (2005-04-21) ------------------------------------------------- * gpg-agent does now support the ssh-agent protocol and thus allows to use the pinentry as well as the OpenPGP smartcard with ssh. * New tool gpg-connect-agent as a general client for the gpg-agent. * New tool symcryptrun as a wrapper for certain encryption tools. * The gpg tool is not anymore build by default because those gpg versions available in the gnupg 1.4 series are far more matured. Noteworthy changes in version 1.9.15 (2005-01-13) ------------------------------------------------- * Fixed passphrase caching bug. * Better support for CCID readers; the reader from Cherry RS 6700 USB does now work. Noteworthy changes in version 1.9.14 (2004-12-22) ------------------------------------------------- * [gpg-agent] New option --use-standard-socket to allow the use of a fixed socket. gpgsm falls back to this socket if GPG_AGENT_INFO has not been set. * Ported to MS Windows with some functional limitations. * New tool gpg-preset-passphrase. Noteworthy changes in version 1.9.13 (2004-12-03) ------------------------------------------------- * [gpgsm] New option --prefer-system-dirmngr. * Minor cleanups and debugging aids. Noteworthy changes in version 1.9.12 (2004-10-22) ------------------------------------------------- * [scdaemon] Partly rewrote the PC/SC code. * Removed the sc-investigate tool. It is now in a separate package available at ftp://ftp.g10code.com/g10code/gscutils/ . * [gpg-agent] Fixed logging problem. Noteworthy changes in version 1.9.11 (2004-10-01) ------------------------------------------------- * When using --import along with --with-validation, the imported certificates are validated and only imported if they are fully valid. * [gpg-agent] New option --max-cache-ttl. * [gpg-agent] When used without --daemon or --server, gpg-agent now check whether a agent is already running and usable. * Fixed some i18n problems. Noteworthy changes in version 1.9.10 (2004-07-22) ------------------------------------------------- * Fixed a serious bug in the checking of trusted root certificates. * New configure option --enable-agent-pnly allows to build and install just the agent. * Fixed a problem with the log file handling. Noteworthy changes in version 1.9.9 (2004-06-08) ------------------------------------------------ * [gpg-agent] The new option --allow-mark-trusted is now required to allow gpg-agent to add a key to the trustlist.txt after user confirmation. * Creating PKCS#10 requests does now honor the key usage. Noteworthy changes in version 1.9.8 (2004-04-29) ------------------------------------------------ * [scdaemon] Overhauled the internal CCID driver. * [scdaemon] Status files named ~/.gnupg/reader_.status are now written when using the internal CCID driver. * [gpgsm] New commands --dump-{,secret,external}-keys to show a very detailed view of the certificates. * The keybox gets now compressed after 3 hours and ephemeral stored certificates are deleted after about a day. * [gpg] Usability fixes for --card-edit. Note, that this has already been ported back to gnupg-1.3 Noteworthy changes in version 1.9.7 (2004-04-06) ------------------------------------------------ * Instrumented the modules for gpgconf. * Added support for DINSIG card applications. * Include the smimeCapabilities attribute with signed messages. * Now uses the gettext domain "gnupg2" to avoid conflicts with gnupg versions < 1.9. Noteworthy changes in version 1.9.6 (2004-03-06) ------------------------------------------------ * Code cleanups and bug fixes. Noteworthy changes in version 1.9.5 (2004-02-21) ------------------------------------------------ * gpg-protect-tool gets now installed into libexec as it ought to be. Cleaned up the build system to better comply with the coding standards. * [gpgsm] The --import command is now able to autodetect pkcs#12 files and import secret and private keys from this file format. A new command --export-secret-key-p12 is provided to allow exporting of secret keys in PKCS\#12 format. * [gpgsm] The pinentry will now present a description of the key for whom the passphrase is requested. * [gpgsm] New option --with-validation to check the validity of key while listing it. * New option --debug-level={none,basic,advanced,expert,guru} to map the debug flags to sensitive levels on a per program base. Noteworthy changes in version 1.9.4 (2004-01-30) ------------------------------------------------ * Added support for the Telesec NKS 2.0 card application. * Added simple tool addgnupghome to create .gnupg directories from /etc/skel/.gnupg. * Various minor bug fixes and cleanups; mainly gpgsm and gpg-agent related. Noteworthy changes in version 1.9.3 (2003-12-23) ------------------------------------------------ * New gpgsm options --{enable,disable}-ocsp to validate keys using OCSP. This option requires a not yet released DirMngr version. Default is disabled. * The --log-file option may now be used to print logs to a socket. Prefix the socket name with "socket://" to enable this. This does not work on all systems and falls back to stderr if there is a problem with the socket. * The options --encrypt-to and --no-encrypt-to now work the same in gpgsm as in gpg. Note, they are also used in server mode. * Duplicated recipients are now silently removed in gpgsm. Noteworthy changes in version 1.9.2 (2003-11-17) ------------------------------------------------ * On card key generation is no longer done using the --gen-key command but from the menu provided by the new --card-edit command. * PINs are now properly cached and there are only 2 PINs visible. The 3rd PIN (CHV2) is internally syncronized with the regular PIN. * All kind of other internal stuff. Noteworthy changes in version 1.9.1 (2003-09-06) ------------------------------------------------ * Support for OpenSC is back. scdaemon supports a --disable-opensc to disable OpenSC use at runtime, so that PC/SC or ct-API can still be used directly. * Rudimentary support for the SCR335 smartcard reader using an internal driver. Requires current libusb from CVS. * Bug fixes. Noteworthy changes in version 1.9.0 (2003-08-05) ------------------------------------------------ ====== PLEASE SEE README-alpha ======= * gpg has been renamed to gpg2 and gpgv to gpgv2. This is a temporary change to allow co-existing with stable gpg versions. * ~/.gnupg/gpg.conf-1.9.0 is fist tried as config file before the usual gpg.conf. * Removed the -k, -kv and -kvv commands. -k is now an alias to --list-keys. New command -K as alias for --list-secret-keys. * Removed --run-as-shm-coprocess feature. * gpg does now also use libgcrypt, libgpg-error is required. * New gpgsm commands --call-dirmngr and --call-protect-tool. * Changing a passphrase is now possible using "gpgsm --passwd" * The content-type attribute is now recognized and created. * The agent does now reread certain options on receiving a HUP. * The pinentry is now forked for each request so that clients with different environments are supported. When running in daemon mode and --keep-display is not used the DISPLAY variable is ignored. * Merged stuff from the newpg branch and started this new development branch. Copyright 2002, 2003, 2004, 2005 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/TODO b/TODO index 7958ed18e..da3a76e06 100644 --- a/TODO +++ b/TODO @@ -1,120 +1,119 @@ -*- outline -*- * src/base64 ** Make parsing more robust Currently we don't cope with overlong lines in the best way. ** Check that we really release the ksba reader/writer objects. * sm/call-agent.c ** The protocol uses an incomplete S-expression We should always use valid S-Exp and not just parts. ** Some code should go into import.c ** When we allow concurrent service request in gpgsm, we might want to have an agent context for each service request (i.e. Assuan context). * sm/certreqgen.c ** Improve error reporting ** Do some basic checks on the supplied DNs * sm/certchain.c ** When a certificate chain was sucessfully verified, make ephemeral certs used in this chain permanent. ** Try to keep certificate references somewhere This will help with some of our caching code. We also need to test - that cachining; in particular "regtp_ca_chainlen". + that caching; in particular "regtp_ca_chainlen". * sm/decrypt.c ** replace leading zero in integer hack by a cleaner solution * sm/gpgsm.c ** Support --output for all commands ** mark all unimplemented commands and options. ** Implement --default-key ** support the anyPolicy semantic ** Check that we are really following the verification procedures in rfc3280. ** Implement a --card-status command. This is useful to check whether a card is supported at all. * sm/keydb.c ** Check file permissions ** Check that all error code mapping is done. ** Remove the inter-module dependencies between gpgsm and keybox ** Add an source_of_key field * agent/command.c ** Make sure that secure memory is used where appropriate * agent/pkdecrypt.c, agent/pksign.c ** Don't use stdio to return results. ** Support DSA * Move pkcs-1 encoding into libgcrypt. * Use a MAC to protect sensitive files. The problem here is that we need yet another key and it is unlikely that users are willing to remember that key too. It is possible to do this with a smartcard, though. * sm/export.c ** Return an error code or a status info per user ID. * scd/tlv.c The parse_sexp fucntion should not go into this file. Check whether we can change all S-expression handling code to make use of this function. * scd ** Application context vs. reader slot We have 2 concurrent method of tracking whether a read is in use: Using the session_list in command.c and the lock_table in app.c. IT would be better to do this just at one place. First we need to see how we can support cards with multiple applications. ** Detecting a removed card works only after the ticker detected it. We should check the card status in open-card to make this smoother. Needs to be integrated with the status file update, though. It is not a real problem because application will get a card removed status and should the send a reset to try solving the problem. * tests ** Makefile.am We use printf(1) to setup the library path, this is not portable. Furthermore LD_LIBRARY_PATH is not used on all systems. It doesn't matter for now, because we use some GNU/*BSDish features anyway. ** Add a test to check the extkeyusage. * doc/ ** Explain how to setup a root CA key as trusted ** Explain how trustlist.txt might be managed. ** Write a script to generate man pages from texi. In progress (yatm) * Windows port ** gpgsm's LISTKEYS does not yet work Fix is to change everything to libestream ** Signals are not support This means we can't reread a configuration ** No card status notifications. * sm/ -** --include-certs is as of now still a dummy command line option ** check that we issue NO_SECKEY xxx if a -u key was not found * gpg/ ** issue a NO_SECKEY xxxx if a -u key was not found. ** Replace DIGEST_ALGO_SHA224 We can't do that right now because it is only defined by newer versions of libgcrypt. Changes this if we require libgcrypt 1.3 anyway. ** skclist.c:random_is_faked Remove the whole stuff? * common/ ** ttyio Add completion support. ** yesno - Update to gpg 1.4.3 version \ No newline at end of file + Update to gpg 1.4.3 version diff --git a/agent/Makefile.am b/agent/Makefile.am index bc96531e0..961f0bb97 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -1,89 +1,90 @@ # Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in bin_PROGRAMS = gpg-agent libexec_PROGRAMS = gpg-protect-tool gpg-preset-passphrase noinst_PROGRAMS = $(TESTS) AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common -I$(top_srcdir)/intl include $(top_srcdir)/am/cmacros.am AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS) gpg_agent_SOURCES = \ gpg-agent.c agent.h \ command.c command-ssh.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 gpg_agent_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \ $(LIBGCRYPT_LIBS) $(PTH_LIBS) $(LIBASSUAN_LIBS) \ -lgpg-error @LIBINTL@ $(NETLIBS) gpg_protect_tool_SOURCES = \ protect-tool.c \ protect.c \ minip12.c minip12.h # Needs $(NETLIBS) for libsimple-pwquery.la. gpg_protect_tool_LDADD = ../common/libsimple-pwquery.a \ ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \ $(LIBGCRYPT_LIBS) -lgpg-error @LIBINTL@ $(NETLIBS) if HAVE_W32_SYSTEM gpg_protect_tool_LDADD += -lwsock32 endif gpg_preset_passphrase_SOURCES = \ preset-passphrase.c # Needs $(NETLIBS) for libsimple-pwquery.la. gpg_preset_passphrase_LDADD = ../common/libsimple-pwquery.a \ ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \ $(LIBGCRYPT_LIBS) -lgpg-error @LIBINTL@ $(NETLIBS) if HAVE_W32_SYSTEM gpg_preset_passphrase_LDADD += -lwsock32 endif # # Module tests # TESTS = t-protect t_common_ldadd = ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \ $(LIBGCRYPT_LIBS) -lgpg-error @LIBINTL@ t_protect_SOURCES = t-protect.c protect.c t_protect_LDADD = $(t_common_ldadd) diff --git a/agent/agent.h b/agent/agent.h index 1542d6b9f..fdfe510fb 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -1,318 +1,319 @@ /* agent.h - Global definitions for the agent * Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * 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 #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include #include "../common/util.h" #include "../common/errors.h" #include "membuf.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 */ /* Environment setting gathered at program start or changed using the Assuan command UPDATESTARTUPTTY. */ char *startup_display; char *startup_ttyname; char *startup_ttytype; char *startup_lc_ctype; char *startup_lc_messages; const char *pinentry_program; /* Filename of the program to start as pinentry. */ const char *scdaemon_program; /* Filename of the program to handle smartcard tasks. */ int disable_scdaemon; /* Never use the SCdaemon. */ int no_grab; /* Don't let the pinentry grab the keyboard */ /* The default and maximum TTL of cache entries. */ unsigned long def_cache_ttl; /* Default. */ unsigned long def_cache_ttl_ssh; /* for SSH. */ unsigned long max_cache_ttl; /* Default. */ unsigned long max_cache_ttl_ssh; /* for SSH. */ int running_detached; /* We are running detached from the tty. */ int ignore_cache_for_signing; int allow_mark_trusted; int allow_preset_passphrase; int keep_tty; /* Don't switch the TTY (for pinentry) on request */ int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */ int ssh_support; /* Enable ssh-agent emulation. */ } opt; #define DBG_COMMAND_VALUE 1 /* debug commands i/o */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_ASSUAN_VALUE 1024 #define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) struct server_local_s; struct scd_local_s; /* Collection of data per session (aka connection). */ struct server_control_s { /* Private data of the server (command.c). */ struct server_local_s *server_local; /* Private data of the SCdaemon (call-scd.c). */ struct scd_local_s *scd_local; int connection_fd; /* -1 or an identifier for the current connection. */ char *display; char *ttyname; char *ttytype; char *lc_ctype; char *lc_messages; struct { int algo; unsigned char value[MAX_DIGEST_LEN]; int valuelen; int raw_value: 1; } digest; unsigned char keygrip[20]; int have_keygrip; int use_auth_call; /* Hack to send the PKAUTH command instead of the PKSIGN command to the scdaemon. */ }; typedef struct server_control_s *CTRL; typedef struct server_control_s *ctrl_t; 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 }; /* Values for the cache_mode arguments. */ typedef enum { CACHE_MODE_IGNORE = 0, /* Special mode to by pass the cache. */ CACHE_MODE_ANY, /* Any mode except ignore matches. */ CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */ CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */ CACHE_MODE_SSH /* SSH related cache. */ } cache_mode_t; /*-- gpg-agent.c --*/ void agent_exit (int rc) JNLIB_GCC_A_NR; /* Also implemented in other tools */ void agent_init_default_ctrl (struct server_control_s *ctrl); /*-- command.c --*/ void start_command_handler (int, int); /*-- command-ssh.c --*/ void start_command_handler_ssh (int); /*-- findkey.c --*/ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force); gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, gcry_sexp_t *result); gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); int agent_key_available (const unsigned char *grip); /*-- query.c --*/ void initialize_module_query (void); void agent_query_dump_state (void); void agent_reset_query (ctrl_t ctrl); int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *inital_errtext, struct pin_entry_info_s *pininfo); int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext); int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *cancel); int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn, const char *cancel_btn); void agent_popup_message_stop (ctrl_t ctrl); /*-- cache.c --*/ void agent_flush_cache (void); int agent_put_cache (const char *key, cache_mode_t cache_mode, const char *data, int ttl); const char *agent_get_cache (const char *key, cache_mode_t cache_mode, void **cache_id); void agent_unlock_cache_entry (void **cache_id); /*-- pksign.c --*/ int agent_pksign_do (ctrl_t ctrl, const char *desc_text, gcry_sexp_t *signature_sexp, cache_mode_t cache_mode); int agent_pksign (ctrl_t ctrl, const char *desc_text, membuf_t *outbuf, cache_mode_t cache_mode); /*-- pkdecrypt.c --*/ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf); /*-- genkey.c --*/ int agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparmlen, membuf_t *outbuf); int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey); /*-- protect.c --*/ 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); unsigned char *make_shadow_info (const char *serialno, const char *idstring); int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result); int agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info); /*-- trustlist.c --*/ int agent_istrusted (const char *fpr); int agent_listtrusted (void *assuan_context); int agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_trustlist_housekeeping (void); void agent_reload_trustlist (void); /*-- divert-scd.c --*/ int divert_pksign (ctrl_t ctrl, const unsigned char *digest, size_t digestlen, int algo, const unsigned char *shadow_info, unsigned char **r_sig); int divert_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, const unsigned char *shadow_info, char **r_buf, size_t *r_len); int divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context); /*-- call-scd.c --*/ void initialize_module_call_scd (void); void agent_scd_dump_state (void); void agent_scd_check_aliveness (void); int agent_reset_scd (ctrl_t ctrl); int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg); int agent_card_serialno (ctrl_t ctrl, char **r_serialno); int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, unsigned char **r_buf, size_t *r_buflen); int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*,size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen); int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen); int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf); gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result); int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context); /*-- learncard.c --*/ int agent_handle_learn (ctrl_t ctrl, void *assuan_context); #endif /*AGENT_H*/ diff --git a/agent/cache.c b/agent/cache.c index 32b6ac0c7..2f468396d 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -1,341 +1,342 @@ /* cache.c - keep a cache of passphrases * Copyright (C) 2002 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "agent.h" struct secret_data_s { int totallen; /* this includes the padding */ int datalen; /* actual data length */ char data[1]; }; typedef struct cache_item_s *ITEM; struct cache_item_s { ITEM next; time_t created; time_t accessed; int ttl; /* max. lifetime given in seconds, -1 one means infinite */ int lockcount; struct secret_data_s *pw; cache_mode_t cache_mode; char key[1]; }; static ITEM thecache; static void release_data (struct secret_data_s *data) { xfree (data); } static struct secret_data_s * new_data (const void *data, size_t length) { struct secret_data_s *d; int total; /* we pad the data to 32 bytes so that it get more complicated finding something out by watching allocation patterns. This is usally not possible but we better assume nothing about our secure storage provider*/ total = length + 32 - (length % 32); d = gcry_malloc_secure (sizeof *d + total - 1); if (d) { d->totallen = total; d->datalen = length; memcpy (d->data, data, length); } return d; } /* check whether there are items to expire */ static void housekeeping (void) { ITEM r, rprev; time_t current = gnupg_get_time (); /* First expire the actual data */ for (r=thecache; r; r = r->next) { if (!r->lockcount && r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) { if (DBG_CACHE) log_debug (" expired `%s' (%ds after last access)\n", r->key, r->ttl); release_data (r->pw); r->pw = NULL; r->accessed = current; } } /* Second, make sure that we also remove them based on the created stamp so that the user has to enter it from time to time. */ for (r=thecache; r; r = r->next) { unsigned long maxttl; switch (r->cache_mode) { case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; default: maxttl = opt.max_cache_ttl; break; } if (!r->lockcount && r->pw && r->created + maxttl < current) { if (DBG_CACHE) log_debug (" expired `%s' (%lus after creation)\n", r->key, opt.max_cache_ttl); release_data (r->pw); r->pw = NULL; r->accessed = current; } } /* Third, make sure that we don't have too many items in the list. Expire old and unused entries after 30 minutes */ for (rprev=NULL, r=thecache; r; ) { if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current) { if (r->lockcount) { log_error ("can't remove unused cache entry `%s' due to" " lockcount=%d\n", r->key, r->lockcount); r->accessed += 60*10; /* next error message in 10 minutes */ rprev = r; r = r->next; } else { ITEM r2 = r->next; if (DBG_CACHE) log_debug (" removed `%s' (slot not used for 30m)\n", r->key); xfree (r); if (!rprev) thecache = r2; else rprev->next = r2; r = r2; } } else { rprev = r; r = r->next; } } } void agent_flush_cache (void) { ITEM r; if (DBG_CACHE) log_debug ("agent_flush_cache\n"); for (r=thecache; r; r = r->next) { if (!r->lockcount && r->pw) { if (DBG_CACHE) log_debug (" flushing `%s'\n", r->key); release_data (r->pw); r->pw = NULL; r->accessed = 0; } else if (r->lockcount && r->pw) { if (DBG_CACHE) log_debug (" marked `%s' for flushing\n", r->key); r->accessed = 0; r->ttl = 0; } } } /* Store DATA of length DATALEN in the cache under KEY and mark it with a maximum lifetime of TTL seconds. If there is already data under this key, it will be replaced. Using a DATA of NULL deletes the entry. A TTL of 0 is replaced by the default TTL and a TTL of -1 set infinite timeout. CACHE_MODE is stored with the cache entry and used t select different timeouts. */ int agent_put_cache (const char *key, cache_mode_t cache_mode, const char *data, int ttl) { ITEM r; if (DBG_CACHE) log_debug ("agent_put_cache `%s' requested ttl=%d mode=%d\n", key, ttl, cache_mode); housekeeping (); if (!ttl) { switch(cache_mode) { case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break; default: ttl = opt.def_cache_ttl; break; } } if (!ttl || cache_mode == CACHE_MODE_IGNORE) return 0; for (r=thecache; r; r = r->next) { if (!r->lockcount && !strcmp (r->key, key)) break; } if (r) { /* replace */ if (r->pw) { release_data (r->pw); r->pw = NULL; } if (data) { r->created = r->accessed = gnupg_get_time (); r->ttl = ttl; r->cache_mode = cache_mode; r->pw = new_data (data, strlen (data)+1); if (!r->pw) log_error ("out of core while allocating new cache item\n"); } } else if (data) { /* simply insert */ r = xtrycalloc (1, sizeof *r + strlen (key)); if (!r) log_error ("out of core while allocating new cache control\n"); else { strcpy (r->key, key); r->created = r->accessed = gnupg_get_time (); r->ttl = ttl; r->cache_mode = cache_mode; r->pw = new_data (data, strlen (data)+1); if (!r->pw) { log_error ("out of core while allocating new cache item\n"); xfree (r); } else { r->next = thecache; thecache = r; } } } return 0; } /* Try to find an item in the cache. Note that we currently don't make use of CACHE_MODE. */ const char * agent_get_cache (const char *key, cache_mode_t cache_mode, void **cache_id) { ITEM r; if (cache_mode == CACHE_MODE_IGNORE) return NULL; if (DBG_CACHE) log_debug ("agent_get_cache `%s'...\n", key); housekeeping (); /* first try to find one with no locks - this is an updated cache entry: We might have entries with a lockcount and without a lockcount. */ for (r=thecache; r; r = r->next) { if (!r->lockcount && r->pw && !strcmp (r->key, key)) { /* put_cache does only put strings into the cache, so we don't need the lengths */ r->accessed = gnupg_get_time (); if (DBG_CACHE) log_debug ("... hit\n"); r->lockcount++; *cache_id = r; return r->pw->data; } } /* again, but this time get even one with a lockcount set */ for (r=thecache; r; r = r->next) { if (r->pw && !strcmp (r->key, key)) { r->accessed = gnupg_get_time (); if (DBG_CACHE) log_debug ("... hit (locked)\n"); r->lockcount++; *cache_id = r; return r->pw->data; } } if (DBG_CACHE) log_debug ("... miss\n"); *cache_id = NULL; return NULL; } void agent_unlock_cache_entry (void **cache_id) { ITEM r; for (r=thecache; r; r = r->next) { if (r == *cache_id) { if (!r->lockcount) log_error ("trying to unlock non-locked cache entry `%s'\n", r->key); else r->lockcount--; return; } } } diff --git a/agent/call-scd.c b/agent/call-scd.c index ff241ce41..d0d24f9d5 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1,1091 +1,1092 @@ /* call-scd.c - fork of the scdaemon to do SC operations * Copyright (C) 2001, 2002, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #endif #include #include "agent.h" #include #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Definition of module local data of the CTRL structure. */ struct scd_local_s { /* We keep a list of all allocated context with a an achnor at SCD_LOCAL_LIST (see below). */ struct scd_local_s *next_local; /* We need to get back to the ctrl object actually referencing this structure. This is really an awkward way of enumerint the lcoal contects. A much cleaner way would be to keep a global list of ctrl objects to enumerate them. */ ctrl_t ctrl_backlink; assuan_context_t ctx; /* NULL or session context for the SCdaemon used with this connection. */ int locked; /* This flag is used to assert proper use of start_scd and unlock_scd. */ }; /* Callback parameter for learn card */ struct learn_parm_s { void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; void (*certinfo_cb)(void*, const char *); void *certinfo_cb_arg; void (*sinfo_cb)(void*, const char *, size_t, const char *); void *sinfo_cb_arg; }; struct inq_needpin_s { assuan_context_t ctx; int (*getpin_cb)(void *, const char *, char*, size_t); void *getpin_cb_arg; }; /* To keep track of all active SCD contexts, we keep a linked list anchored at this variable. */ static struct scd_local_s *scd_local_list; /* A Mutex used inside the start_scd function. */ static pth_mutex_t start_scd_lock; /* A malloced string with the name of the socket to be used for additional connections. May be NULL if not provided by SCdaemon. */ static char *socket_name; /* The context of the primary connection. This is also used as a flag to indicate whether the scdaemon has been started. */ static assuan_context_t primary_scd_ctx; /* To allow reuse of the primary connection, the following flag is set to true if the primary context has been reset and is not in use by any connection. */ static int primary_scd_ctx_reusable; /* Local prototypes. */ static assuan_error_t membuf_data_cb (void *opaque, const void *buffer, size_t length); /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_scd (void) { static int initialized; if (!initialized) { if (!pth_mutex_init (&start_scd_lock)) log_fatal ("error initializing mutex: %s\n", strerror (errno)); initialized = 1; } } static void dump_mutex_state (pth_mutex_t *m) { if (!(m->mx_state & PTH_MUTEX_INITIALIZED)) log_printf ("not_initialized"); else if (!(m->mx_state & PTH_MUTEX_LOCKED)) log_printf ("not_locked"); else log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count); } /* This function may be called to print infromation pertaining to the current state of this module to the log. */ void agent_scd_dump_state (void) { log_info ("agent_scd_dump_state: scd_lock="); dump_mutex_state (&start_scd_lock); log_printf ("\n"); log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n", primary_scd_ctx, (long)assuan_get_pid (primary_scd_ctx), primary_scd_ctx_reusable); if (socket_name) log_info ("agent_scd_dump_state: socket=`%s'\n", socket_name); } /* The unlock_scd function shall be called after having accessed the SCD. It is currently not very useful but gives an opportunity to keep track of connections currently calling SCD. Note that the "lock" operation is done by the start_scd() function which must be called and error checked before any SCD operation. CTRL is the usual connection context and RC the error code to be passed trhough the function. */ static int unlock_scd (ctrl_t ctrl, int rc) { if (ctrl->scd_local->locked != 1) { log_error ("unlock_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked = 0; return rc; } /* To make sure we leave no secrets in our image after forking of the scdaemon, we use this callback. */ static void atfork_cb (void *opaque, int where) { if (!where) gcry_control (GCRYCTL_TERM_SECMEM); } /* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. Thsi fucntion might also lock the daemon, which means that the caller must call unlock_scd after this fucntion has returned success and the actual Assuan transaction been done. */ static int start_scd (ctrl_t ctrl) { gpg_error_t err = 0; const char *pgmname; assuan_context_t ctx; const char *argv[3]; int no_close_list[3]; int i; int rc; if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* If this is the first call for this session, setup the local data structure. */ if (!ctrl->scd_local) { ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); if (!ctrl->scd_local) return gpg_error_from_errno (errno); ctrl->scd_local->ctrl_backlink = ctrl; ctrl->scd_local->next_local = scd_local_list; scd_local_list = ctrl->scd_local; } /* Assert that the lock count is as expected. */ if (ctrl->scd_local->locked) { log_error ("start_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); return gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked++; if (ctrl->scd_local->ctx) return 0; /* Okay, the context is fine. We used to test for an alive context here and do an disconnect. Now that we have a ticker function to check for it, it is easier not to check here but to let the connection run on an error instead. */ /* We need to protect the following code. */ if (!pth_mutex_acquire (&start_scd_lock, 0, NULL)) { log_error ("failed to acquire the start_scd lock: %s\n", strerror (errno)); return gpg_error (GPG_ERR_INTERNAL); } /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ if (primary_scd_ctx && primary_scd_ctx_reusable) { ctx = primary_scd_ctx; primary_scd_ctx_reusable = 0; if (opt.verbose) log_info ("new connection to SCdaemon established (reusing)\n"); goto leave; } if (socket_name) { rc = assuan_socket_connect (&ctx, socket_name, 0); if (rc) { log_error ("can't connect to socket `%s': %s\n", socket_name, assuan_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_info ("new connection to SCdaemon established\n"); goto leave; } if (primary_scd_ctx) { log_info ("SCdaemon is running but won't accept further connections\n"); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } /* Nope, it has not been started. Fire it up now. */ if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); if (fflush (NULL)) { err = gpg_error (gpg_err_code_from_errno (errno)); log_error ("error flushing pending output: %s\n", strerror (errno)); goto leave; } 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] = "--multi-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_connect2 (&ctx, opt.scdaemon_program, (char**)argv, no_close_list, atfork_cb, NULL); if (rc) { log_error ("can't connect to the SCdaemon: %s\n", assuan_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_debug ("first connection to SCdaemon established\n"); /* Get the name of the additional socket opened by scdaemon. */ { membuf_t data; unsigned char *databuf; size_t datalen; xfree (socket_name); socket_name = NULL; init_membuf (&data, 256); assuan_transact (ctx, "GETINFO socket_name", membuf_data_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (databuf && datalen) { socket_name = xtrymalloc (datalen + 1); if (!socket_name) log_error ("warning: can't store socket name: %s\n", strerror (errno)); else { memcpy (socket_name, databuf, datalen); socket_name[datalen] = 0; if (DBG_ASSUAN) log_debug ("additional connections at `%s'\n", socket_name); } } xfree (databuf); } /* Tell the scdaemon we want him to send us an event signal. */ #ifndef HAVE_W32_SYSTEM { char buf[100]; sprintf (buf, "OPTION event-signal=%d", SIGUSR2); assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); } #endif primary_scd_ctx = ctx; primary_scd_ctx_reusable = 0; leave: if (err) { unlock_scd (ctrl, err); } else { ctrl->scd_local->ctx = ctx; } if (!pth_mutex_release (&start_scd_lock)) log_error ("failed to release the start_scd lock: %s\n", strerror (errno)); return err; } /* Check whether the Scdaemon is still alive and clean it up if not. */ void agent_scd_check_aliveness (void) { pth_event_t evt; pid_t pid; int rc; if (!primary_scd_ctx) return; /* No scdaemon running. */ /* This is not a critical function so we use a short timeout while acquiring the lock. */ evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); if (!pth_mutex_acquire (&start_scd_lock, 0, evt)) { if (pth_event_occurred (evt)) { if (opt.verbose > 1) log_info ("failed to acquire the start_scd lock while" " doing an aliveness check: %s\n", "timeout"); } else log_error ("failed to acquire the start_scd lock while" " doing an aliveness check: %s\n", strerror (errno)); pth_event_free (evt, PTH_FREE_THIS); return; } pth_event_free (evt, PTH_FREE_THIS); if (primary_scd_ctx) { pid = assuan_get_pid (primary_scd_ctx); if (pid != (pid_t)(-1) && pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) { /* Okay, scdaemon died. Disconnect the primary connection now but take care that it won't do another wait. Also cleanup all other connections and release their resources. The next use will start a new daemon then. Due to the use of the START_SCD_LOCAL we are sure that none of these context are actually in use. */ struct scd_local_s *sl; assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); assuan_disconnect (primary_scd_ctx); for (sl=scd_local_list; sl; sl = sl->next_local) { if (sl->ctx) { if (sl->ctx != primary_scd_ctx) assuan_disconnect (sl->ctx); sl->ctx = NULL; } } primary_scd_ctx = NULL; primary_scd_ctx_reusable = 0; xfree (socket_name); socket_name = NULL; } } if (!pth_mutex_release (&start_scd_lock)) log_error ("failed to release the start_scd lock while" " doing the aliveness check: %s\n", strerror (errno)); } /* Reset the SCD if it has been used. Actually it is not a reset but a cleanup of resources used by the current connection. */ int agent_reset_scd (ctrl_t ctrl) { if (ctrl->scd_local) { if (ctrl->scd_local->ctx) { /* We can't disconnect the primary context because libassuan does a waitpid on it and thus the system would hang. Instead we send a reset and keep that connection for reuse. */ if (ctrl->scd_local->ctx == primary_scd_ctx) { /* Send a RESTART to the SCD. This is required for the primary connection as a kind of virtual EOF; we don't have another way to tell it that the next command should be viewed as if a new connection has been made. For the non-primary connections this is not needed as we simply close the socket. We don't check for an error here because the RESTART may fail for example if the scdaemon has already been terminated. Anyway, we need to set the reusable flag to make sure that the aliveness check can clean it up. */ assuan_transact (primary_scd_ctx, "RESTART", NULL, NULL, NULL, NULL, NULL, NULL); primary_scd_ctx_reusable = 1; } else assuan_disconnect (ctrl->scd_local->ctx); ctrl->scd_local->ctx = NULL; } /* Remove the local context from our list and release it. */ if (!scd_local_list) BUG (); else if (scd_local_list == ctrl->scd_local) scd_local_list = ctrl->scd_local->next_local; else { struct scd_local_s *sl; for (sl=scd_local_list; sl->next_local; sl = sl->next_local) if (sl->next_local == ctrl->scd_local) break; if (!sl->next_local) BUG (); sl->next_local = ctrl->scd_local->next_local; } xfree (ctrl->scd_local); ctrl->scd_local = NULL; } return 0; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary Nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. */ static char * unescape_status_string (const unsigned char *s) { char *buffer, *d; buffer = d = xtrymalloc (strlen ((const char*)s)+1); if (!buffer) return NULL; while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d = xtoi_2 (s); if (!*d) *d = '\xff'; d++; s += 2; } else if (*s == '+') { *d++ = ' '; s++; } else *d++ = *s++; } *d = 0; return buffer; } 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 (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg) { int rc; struct learn_parm_s parm; rc = start_scd (ctrl); if (rc) return rc; memset (&parm, 0, sizeof parm); parm.kpinfo_cb = kpinfo_cb; parm.kpinfo_cb_arg = kpinfo_cb_arg; parm.certinfo_cb = certinfo_cb; parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force", NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) return unlock_scd (ctrl, map_assuan_err (rc)); return unlock_scd (ctrl, 0); } 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 (ctrl_t ctrl, char **r_serialno) { int rc; char *serialno = NULL; rc = start_scd (ctrl); if (rc) return rc; rc = assuan_transact (ctrl->scd_local->ctx, "SERIALNO", NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) { xfree (serialno); return unlock_scd (ctrl, map_assuan_err (rc)); } *r_serialno = serialno; return unlock_scd (ctrl, 0); } static AssuanError membuf_data_cb (void *opaque, const void *buffer, size_t length) { membuf_t *data = opaque; if (buffer) put_membuf (data, buffer, length); return 0; } /* Handle the NEEDPIN inquiry. */ static 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])) { line += 7; while (*line == ' ') line++; 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); } else if (!strncmp (line, "KEYPADINFO", 10) && (line[10] == ' ' || !line[10])) { size_t code; char *endp; code = strtoul (line+10, &endp, 10); line = endp; while (*line == ' ') line++; rc = parm->getpin_cb (parm->getpin_cb_arg, line, NULL, code); if (rc) rc = ASSUAN_Canceled; } else { log_error ("unsupported inquiry `%s'\n", line); rc = ASSUAN_Inquire_Unknown; } return rc; } /* Create a signature using the current card */ int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, unsigned char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_s inqparm; size_t len; unsigned char *sigbuf; size_t sigbuflen; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; if (indatalen*2 + 50 > DIM(line)) return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); sprintf (line, "SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, map_assuan_err (rc)); init_membuf (&data, 1024); inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; snprintf (line, DIM(line)-1, ctrl->use_auth_call? "PKAUTH %s":"PKSIGN %s", keyid); line[DIM(line)-1] = 0; rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, 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; p = xtrymalloc (*r_buflen); *r_buf = (unsigned char*)p; if (!p) return unlock_scd (ctrl, out_of_core ()); p = stpcpy (p, "(7:sig-val(3:rsa(1:s" ); sprintf (p, "%u:", (unsigned int)sigbuflen); p += strlen (p); memcpy (p, sigbuf, sigbuflen); p += sigbuflen; strcpy (p, ")))"); xfree (sigbuf); assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); return unlock_scd (ctrl, 0); } /* Decipher INDATA using the current card. Note that the returned value is */ int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_s inqparm; size_t len; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; /* FIXME: use secure memory where appropriate */ if (indatalen*2 + 50 > DIM(line)) return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); sprintf (line, "SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, map_assuan_err (rc)); init_membuf (&data, 1024); inqparm.ctx = ctrl->scd_local->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 (ctrl->scd_local->ctx, line, membuf_data_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, map_assuan_err (rc)); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (ctrl, 0); } /* Read a certificate with ID into R_BUF and R_BUFLEN. */ int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "READCERT %s", id); line[DIM(line)-1] = 0; rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, map_assuan_err (rc)); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (ctrl, 0); } /* Read a key with ID and return it in an allocate buffer pointed to by r_BUF as a valid S-expression. */ int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len, buflen; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "READKEY %s", id); line[DIM(line)-1] = 0; rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, map_assuan_err (rc)); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { xfree (*r_buf); *r_buf = NULL; return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); } return unlock_scd (ctrl, 0); } /* Type used with the card_getattr_cb. */ struct card_getattr_parm_s { const char *keyword; /* Keyword to look for. */ size_t keywordlen; /* strlen of KEYWORD. */ char *data; /* Malloced and unescaped data. */ int error; /* ERRNO value or 0 on success. */ }; /* Callback function for agent_card_getattr. */ static assuan_error_t card_getattr_cb (void *opaque, const char *line) { struct card_getattr_parm_s *parm = opaque; const char *keyword = line; int keywordlen; if (parm->data) return 0; /* We want only the first occurrence. */ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == parm->keywordlen && !memcmp (keyword, parm->keyword, keywordlen)) { parm->data = unescape_status_string ((const unsigned char*)line); if (!parm->data) parm->error = errno; } return 0; } /* Call the agent to retrieve a single line data object. On success the object is malloced and stored at RESULT; it is guaranteed that NULL is never stored in this case. On error an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result) { int err; struct card_getattr_parm_s parm; char line[ASSUAN_LINELENGTH]; *result = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&parm, 0, sizeof parm); parm.keyword = name; parm.keywordlen = strlen (name); /* We assume that NAME does not need escaping. */ if (8 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "GETATTR "), name); err = start_scd (ctrl); if (err) return err; err = map_assuan_err (assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, card_getattr_cb, &parm)); if (!err && parm.error) err = gpg_error_from_errno (parm.error); if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *result = parm.data; else xfree (parm.data); return unlock_scd (ctrl, err); } static 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 (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context) { int rc; struct inq_needpin_s inqparm; rc = start_scd (ctrl); if (rc) return rc; inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; rc = assuan_transact (ctrl->scd_local->ctx, cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); if (rc) { return unlock_scd (ctrl, map_assuan_err (rc)); } return unlock_scd (ctrl, 0); } diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 23f083c2f..18375a9ae 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -1,2884 +1,2884 @@ /* command-ssh.c - gpg-agent's ssh-agent emulation layer * Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* Only v2 of the ssh-agent protocol is implemented. */ #include #include #include #include #include #include #include #include #include #include "agent.h" #include "estream.h" #include "i18n.h" /* Request types. */ #define SSH_REQUEST_REQUEST_IDENTITIES 11 #define SSH_REQUEST_SIGN_REQUEST 13 #define SSH_REQUEST_ADD_IDENTITY 17 #define SSH_REQUEST_REMOVE_IDENTITY 18 #define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19 #define SSH_REQUEST_LOCK 22 #define SSH_REQUEST_UNLOCK 23 #define SSH_REQUEST_ADD_ID_CONSTRAINED 25 /* Options. */ #define SSH_OPT_CONSTRAIN_LIFETIME 1 #define SSH_OPT_CONSTRAIN_CONFIRM 2 /* Response types. */ #define SSH_RESPONSE_SUCCESS 6 #define SSH_RESPONSE_FAILURE 5 #define SSH_RESPONSE_IDENTITIES_ANSWER 12 #define SSH_RESPONSE_SIGN_RESPONSE 14 /* Other constants. */ #define SSH_DSA_SIGNATURE_PADDING 20 #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) /* The blurb we put into the header of a newly created control file. */ static const char sshcontrolblurb[] = "# List of allowed ssh keys. Only keys present in this file are used\n" "# in the SSH protocol. The ssh-add tool may add new entries to this\n" "# file to enable them; you may also add them manually. Comment\n" "# lines, like this one, as well as empty lines are ignored. Lines do\n" "# have a certain length limit but this is not serious limitation as\n" "# the format of the entries is fixed and checked by gpg-agent. A\n" "# non-comment line starts with optional white spaces, followed by the\n" "# keygrip of the key given as 40 hex digits, optionally followed by a\n" "# the caching TTL in seconds and another optional field for arbitrary\n" "# flags. Prepend the keygrip with an '!' mark to disable it.\n" "\n"; /* Macros. */ /* Return a new uint32 with b0 being the most significant byte and b3 being the least significant byte. */ #define uint32_construct(b0, b1, b2, b3) \ ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) /* * Basic types. */ /* Type for a request handler. */ typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl, estream_t request, estream_t response); /* Type, which is used for associating request handlers with the appropriate request IDs. */ typedef struct ssh_request_spec { unsigned char type; ssh_request_handler_t handler; const char *identifier; unsigned int secret_input; } ssh_request_spec_t; /* Type for "key modifier functions", which are necessary since OpenSSH and GnuPG treat key material slightly different. A key modifier is called right after a new key identity has been received in order to "sanitize" the material. */ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, gcry_mpi_t *mpis); /* The encoding of a generated signature is dependent on the algorithm; therefore algorithm specific signature encoding functions are necessary. */ typedef gpg_error_t (*ssh_signature_encoder_t) (estream_t signature_blob, gcry_mpi_t *mpis); /* Type, which is used for boundling all the algorithm specific information together in a single object. */ typedef struct ssh_key_type_spec { /* Algorithm identifier as used by OpenSSH. */ const char *ssh_identifier; /* Algorithm identifier as used by GnuPG. */ const char *identifier; /* List of MPI names for secret keys; order matches the one of the agent protocol. */ const char *elems_key_secret; /* List of MPI names for public keys; order matches the one of the agent protocol. */ const char *elems_key_public; /* List of MPI names for signature data. */ const char *elems_signature; /* List of MPI names for secret keys; order matches the one, which is required by gpg-agent's key access layer. */ const char *elems_sexp_order; /* Key modifier function. Key modifier functions are necessary in order to fix any inconsistencies between the representation of keys on the SSH and on the GnuPG side. */ ssh_key_modifier_t key_modifier; /* Signature encoder function. Signature encoder functions are necessary since the encoding of signatures depends on the used algorithm. */ ssh_signature_encoder_t signature_encoder; /* Misc flags. */ unsigned int flags; } ssh_key_type_spec_t; /* Prototypes. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis); static gpg_error_t ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis); static gpg_error_t ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis); /* Global variables. */ /* Associating request types with the corresponding request handlers. */ static ssh_request_spec_t request_specs[] = { #define REQUEST_SPEC_DEFINE(id, name, secret_input) \ { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input } REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1), REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0), REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1), REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1), REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0), REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0), REQUEST_SPEC_DEFINE (LOCK, lock, 0), REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0) #undef REQUEST_SPEC_DEFINE }; /* Table holding key type specifications. */ static ssh_key_type_spec_t ssh_key_types[] = { { "ssh-rsa", "rsa", "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "dsa", "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, 0 }, }; /* General utility functions. */ /* A secure realloc, i.e. it makes sure to allocate secure memory if A is NULL. This is required because the standard gcry_realloc does not know whether to allocate secure or normal if NULL is passed as existing buffer. */ static void * realloc_secure (void *a, size_t n) { void *p; if (a) p = gcry_realloc (a, n); else p = gcry_malloc_secure (n); return p; } /* Create and return a new C-string from DATA/DATA_N (i.e.: add NUL-termination); return NULL on OOM. */ static char * make_cstring (const char *data, size_t data_n) { char *s; s = xtrymalloc (data_n + 1); if (s) { strncpy (s, data, data_n); s[data_n] = 0; } return s; } /* Primitive I/O functions. */ /* Read a byte from STREAM, store it in B. */ static gpg_error_t stream_read_byte (estream_t stream, unsigned char *b) { gpg_error_t err; int ret; ret = es_fgetc (stream); if (ret == EOF) { if (es_ferror (stream)) err = gpg_error_from_errno (errno); else err = gpg_error (GPG_ERR_EOF); *b = 0; } else { *b = ret & 0xFF; err = 0; } return err; } /* Write the byte contained in B to STREAM. */ static gpg_error_t stream_write_byte (estream_t stream, unsigned char b) { gpg_error_t err; int ret; ret = es_fputc (b, stream); if (ret == EOF) err = gpg_error_from_errno (errno); else err = 0; return err; } /* Read a uint32 from STREAM, store it in UINT32. */ static gpg_error_t stream_read_uint32 (estream_t stream, u32 *uint32) { unsigned char buffer[4]; size_t bytes_read; gpg_error_t err; int ret; ret = es_read (stream, buffer, sizeof (buffer), &bytes_read); if (ret) err = gpg_error_from_errno (errno); else { if (bytes_read != sizeof (buffer)) err = gpg_error (GPG_ERR_EOF); else { u32 n; n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]); *uint32 = n; err = 0; } } return err; } /* Write the uint32 contained in UINT32 to STREAM. */ static gpg_error_t stream_write_uint32 (estream_t stream, u32 uint32) { unsigned char buffer[4]; gpg_error_t err; int ret; buffer[0] = uint32 >> 24; buffer[1] = uint32 >> 16; buffer[2] = uint32 >> 8; buffer[3] = uint32 >> 0; ret = es_write (stream, buffer, sizeof (buffer), NULL); if (ret) err = gpg_error_from_errno (errno); else err = 0; return err; } /* Read SIZE bytes from STREAM into BUFFER. */ static gpg_error_t stream_read_data (estream_t stream, unsigned char *buffer, size_t size) { gpg_error_t err; size_t bytes_read; int ret; ret = es_read (stream, buffer, size, &bytes_read); if (ret) err = gpg_error_from_errno (errno); else { if (bytes_read != size) err = gpg_error (GPG_ERR_EOF); else err = 0; } return err; } /* Write SIZE bytes from BUFFER to STREAM. */ static gpg_error_t stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) { gpg_error_t err; int ret; ret = es_write (stream, buffer, size, NULL); if (ret) err = gpg_error_from_errno (errno); else err = 0; return err; } /* Read a binary string from STREAM into STRING, store size of string in STRING_SIZE; depending on SECURE use secure memory for string. */ static gpg_error_t stream_read_string (estream_t stream, unsigned int secure, unsigned char **string, u32 *string_size) { gpg_error_t err; unsigned char *buffer; u32 length; buffer = NULL; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto out; /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length + 1); else buffer = xtrymalloc (length + 1); if (! buffer) { err = gpg_error_from_errno (errno); goto out; } /* Read data. */ err = stream_read_data (stream, buffer, length); if (err) goto out; /* Finalize string object. */ buffer[length] = 0; *string = buffer; if (string_size) *string_size = length; out: if (err) xfree (buffer); return err; } /* Read a C-string from STREAM, store copy in STRING. */ static gpg_error_t stream_read_cstring (estream_t stream, char **string) { unsigned char *buffer; gpg_error_t err; err = stream_read_string (stream, 0, &buffer, NULL); if (err) goto out; *string = (char *) buffer; out: return err; } /* Write a binary string from STRING of size STRING_N to STREAM. */ static gpg_error_t stream_write_string (estream_t stream, const unsigned char *string, u32 string_n) { gpg_error_t err; err = stream_write_uint32 (stream, string_n); if (err) goto out; err = stream_write_data (stream, string, string_n); out: return err; } /* Write a C-string from STRING to STREAM. */ static gpg_error_t stream_write_cstring (estream_t stream, const char *string) { gpg_error_t err; err = stream_write_string (stream, (const unsigned char *) string, strlen (string)); return err; } /* Read an MPI from STREAM, store it in MPINT. Depending on SECURE use secure memory. */ static gpg_error_t stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint) { unsigned char *mpi_data; u32 mpi_data_size; gpg_error_t err; gcry_mpi_t mpi; mpi_data = NULL; err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size); if (err) goto out; err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL); if (err) goto out; *mpint = mpi; out: xfree (mpi_data); return err; } /* Write the MPI contained in MPINT to STREAM. */ static gpg_error_t stream_write_mpi (estream_t stream, gcry_mpi_t mpint) { unsigned char *mpi_buffer; size_t mpi_buffer_n; gpg_error_t err; mpi_buffer = NULL; err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint); if (err) goto out; err = stream_write_string (stream, mpi_buffer, mpi_buffer_n); out: xfree (mpi_buffer); return err; } /* Copy data from SRC to DST until EOF is reached. */ static gpg_error_t stream_copy (estream_t dst, estream_t src) { char buffer[BUFSIZ]; size_t bytes_read; gpg_error_t err; int ret; err = 0; while (1) { ret = es_read (src, buffer, sizeof (buffer), &bytes_read); if (ret || (! bytes_read)) { if (ret) err = gpg_error_from_errno (errno); break; } ret = es_write (dst, buffer, bytes_read, NULL); if (ret) { err = gpg_error_from_errno (errno); break; } } return err; } /* Read the content of the file specified by FILENAME into a newly create buffer, which is to be stored in BUFFER; store length of buffer in BUFFER_N. */ static gpg_error_t file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n) { unsigned char *buffer_new; struct stat statbuf; estream_t stream; gpg_error_t err; int ret; *buffer = NULL; *buffer_n = 0; buffer_new = NULL; err = 0; stream = es_fopen (filename, "r"); if (! stream) { err = gpg_error_from_errno (errno); goto out; } ret = fstat (es_fileno (stream), &statbuf); if (ret) { err = gpg_error_from_errno (errno); goto out; } buffer_new = xtrymalloc (statbuf.st_size); if (! buffer_new) { err = gpg_error_from_errno (errno); goto out; } err = stream_read_data (stream, buffer_new, statbuf.st_size); if (err) goto out; *buffer = buffer_new; *buffer_n = statbuf.st_size; out: if (stream) es_fclose (stream); if (err) xfree (buffer_new); return err; } /* Open the ssh control file and create it if not available. With APPEND passed as true the file will be opened in append mode, otherwise in read only mode. On success a file pointer is stored at the address of R_FP. */ static gpg_error_t open_control_file (FILE **r_fp, int append) { gpg_error_t err; char *fname; FILE *fp; /* Note: As soon as we start to use non blocking functions here (i.e. where Pth might switch threads) we need to employ a mutex. */ *r_fp = NULL; fname = make_filename (opt.homedir, "sshcontrol", NULL); /* FIXME: With "a+" we are not able to check whether this will will be created and thus the blurb needs to be written first. */ fp = fopen (fname, append? "a+":"r"); if (!fp && errno == ENOENT) { /* Fixme: "x" is a GNU extension. We might want to use the es_ functions here. */ fp = fopen (fname, "wx"); if (!fp) { err = gpg_error (gpg_err_code_from_errno (errno)); log_error (_("can't create `%s': %s\n"), fname, gpg_strerror (err)); xfree (fname); return err; } fputs (sshcontrolblurb, fp); fclose (fp); fp = fopen (fname, append? "a+":"r"); } if (!fp) { err = gpg_error (gpg_err_code_from_errno (errno)); log_error (_("can't open `%s': %s\n"), fname, gpg_strerror (err)); xfree (fname); return err; } *r_fp = fp; return 0; } /* Search the file at stream FP from the beginning until a matching HEXGRIP is found; return success in this case and store true at DISABLED if the found key has been disabled. */ static gpg_error_t search_control_file (FILE *fp, const char *hexgrip, int *disabled) { int c, i; char *p, line[256]; assert (strlen (hexgrip) == 40 ); rewind (fp); *disabled = 0; next_line: do { if (!fgets (line, DIM(line)-1, fp) ) { if (feof (fp)) return gpg_error (GPG_ERR_EOF); return gpg_error (gpg_err_code_from_errno (errno)); } if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line */ while ( (c=getc (fp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); *disabled = 0; if (*p == '!') { *disabled = 1; for (p++; spacep (p); p++) ; } for (i=0; hexdigitp (p) && i < 40; p++, i++) if (hexgrip[i] != (*p >= 'a'? (*p & 0xdf): *p)) goto next_line; if (i != 40 || !(spacep (p) || *p == '\n')) { log_error ("invalid formatted line in ssh control file\n"); return gpg_error (GPG_ERR_BAD_DATA); } /* Fixme: Get TTL and flags. */ return 0; /* Okay: found it. */ } /* Add an entry to the control file to mark the key with the keygrip HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks for it. This function is in general used to add a key received through the ssh-add function. We can assume that the user wants to allow ssh using this key. */ static gpg_error_t add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl) { gpg_error_t err; FILE *fp; int disabled; err = open_control_file (&fp, 1); if (err) return err; err = search_control_file (fp, hexgrip, &disabled); if (err && gpg_err_code(err) == GPG_ERR_EOF) { struct tm *tp; time_t atime = time (NULL); /* Not yet in the file - add it. Becuase the file has been opened in append mode, we simply need to write to it. */ tp = localtime (&atime); fprintf (fp, "# Key added on %04d-%02d-%02d %02d:%02d:%02d\n%s %d\n", 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, hexgrip, ttl); } fclose (fp); return 0; } /* MPI lists. */ /* Free the list of MPIs MPI_LIST. */ static void mpint_list_free (gcry_mpi_t *mpi_list) { if (mpi_list) { unsigned int i; for (i = 0; mpi_list[i]; i++) gcry_mpi_release (mpi_list[i]); xfree (mpi_list); } } /* Receive key material MPIs from STREAM according to KEY_SPEC; depending on SECRET expect a public key or secret key. The newly allocated list of MPIs is stored in MPI_LIST. Returns usual error code. */ static gpg_error_t ssh_receive_mpint_list (estream_t stream, int secret, ssh_key_type_spec_t key_spec, gcry_mpi_t **mpi_list) { unsigned int elems_public_n; const char *elems_public; unsigned int elems_n; const char *elems; int elem_is_secret; gcry_mpi_t *mpis; gpg_error_t err; unsigned int i; mpis = NULL; err = 0; if (secret) elems = key_spec.elems_key_secret; else elems = key_spec.elems_key_public; elems_n = strlen (elems); elems_public = key_spec.elems_key_public; elems_public_n = strlen (elems_public); mpis = xtrymalloc (sizeof (*mpis) * (elems_n + 1)); if (! mpis) { err = gpg_error_from_errno (errno); goto out; } memset (mpis, 0, sizeof (*mpis) * (elems_n + 1)); elem_is_secret = 0; for (i = 0; i < elems_n; i++) { if (secret) elem_is_secret = ! strchr (elems_public, elems[i]); err = stream_read_mpi (stream, elem_is_secret, &mpis[i]); if (err) break; } if (err) goto out; *mpi_list = mpis; out: if (err) mpint_list_free (mpis); return err; } /* Key modifier function for RSA. */ static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) { gcry_mpi_t p; gcry_mpi_t q; gcry_mpi_t u; if (strcmp (elems, "nedupq")) /* Modifying only necessary for secret keys. */ goto out; u = mpis[3]; p = mpis[4]; q = mpis[5]; if (gcry_mpi_cmp (p, q) > 0) { /* P shall be smaller then Q! Swap primes. iqmp becomes u. */ gcry_mpi_t tmp; tmp = mpis[4]; mpis[4] = mpis[5]; mpis[5] = tmp; } else /* U needs to be recomputed. */ gcry_mpi_invm (u, p, q); out: return 0; } /* Signature encoder function for RSA. */ static gpg_error_t ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis) { unsigned char *data; size_t data_n; gpg_error_t err; gcry_mpi_t s; s = mpis[0]; err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); if (err) goto out; err = stream_write_string (signature_blob, data, data_n); xfree (data); out: return err; } /* Signature encoder function for DSA. */ static gpg_error_t ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis) { unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; unsigned char *data; size_t data_n; gpg_error_t err; int i; data = NULL; for (i = 0; i < 2; i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]); if (err) break; if (data_n > SSH_DSA_SIGNATURE_PADDING) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0, SSH_DSA_SIGNATURE_PADDING - data_n); memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING) + (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n); xfree (data); data = NULL; } if (err) goto out; err = stream_write_string (signature_blob, buffer, sizeof (buffer)); out: xfree (data); return err; } /* S-Expressions. */ /* This function constructs a new S-Expression for the key identified by the KEY_SPEC, SECRET, MPIS and COMMENT, which is to be stored in *SEXP. Returns usual error code. */ static gpg_error_t sexp_key_construct (gcry_sexp_t *sexp, ssh_key_type_spec_t key_spec, int secret, gcry_mpi_t *mpis, const char *comment) { const char *key_identifier[] = { "public-key", "private-key" }; gcry_sexp_t sexp_new; char *sexp_template; size_t sexp_template_n; gpg_error_t err; const char *elems; size_t elems_n; unsigned int i; unsigned int j; void **arg_list; err = 0; sexp_new = NULL; arg_list = NULL; if (secret) elems = key_spec.elems_sexp_order; else elems = key_spec.elems_key_public; elems_n = strlen (elems); /* Calculate size for sexp_template_n: "(%s(%s)(comment%s))" -> 20 + sizeof (). mpi: (X%m) -> 5. */ sexp_template_n = 20 + (elems_n * 5); sexp_template = xtrymalloc (sexp_template_n); if (! sexp_template) { err = gpg_error_from_errno (errno); goto out; } /* Key identifier, algorithm identifier, mpis, comment. */ arg_list = xtrymalloc (sizeof (*arg_list) * (2 + elems_n + 1)); if (! arg_list) { err = gpg_error_from_errno (errno); goto out; } i = 0; arg_list[i++] = &key_identifier[secret]; arg_list[i++] = &key_spec.identifier; *sexp_template = 0; sexp_template_n = 0; sexp_template_n = sprintf (sexp_template + sexp_template_n, "(%%s(%%s"); for (i = 0; i < elems_n; i++) { sexp_template_n += sprintf (sexp_template + sexp_template_n, "(%c%%m)", elems[i]); if (secret) { for (j = 0; j < elems_n; j++) if (key_spec.elems_key_secret[j] == elems[i]) break; } else j = i; arg_list[i + 2] = &mpis[j]; } sexp_template_n += sprintf (sexp_template + sexp_template_n, ")(comment%%s))"); arg_list[i + 2] = &comment; err = gcry_sexp_build_array (&sexp_new, NULL, sexp_template, arg_list); if (err) goto out; *sexp = sexp_new; out: xfree (arg_list); xfree (sexp_template); return err; } /* This functions breaks up the key contained in the S-Expression SEXP according to KEY_SPEC. The MPIs are bundled in a newly create list, which is to be stored in MPIS; a newly allocated string holding the comment will be stored in COMMENT; SECRET will be filled with a boolean flag specifying what kind of key it is. Returns usual error code. */ static gpg_error_t sexp_key_extract (gcry_sexp_t sexp, ssh_key_type_spec_t key_spec, int *secret, gcry_mpi_t **mpis, char **comment) { gpg_error_t err; gcry_sexp_t value_list; gcry_sexp_t value_pair; gcry_sexp_t comment_list; unsigned int i; char *comment_new; const char *data; size_t data_n; int is_secret; size_t elems_n; const char *elems; gcry_mpi_t *mpis_new; gcry_mpi_t mpi; err = 0; value_list = NULL; value_pair = NULL; comment_list = NULL; comment_new = NULL; mpis_new = NULL; data = gcry_sexp_nth_data (sexp, 0, &data_n); if (! data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((data_n == 10 && !strncmp (data, "public-key", 10)) || (data_n == 21 && !strncmp (data, "protected-private-key", 21)) || (data_n == 20 && !strncmp (data, "shadowed-private-key", 20))) { is_secret = 0; elems = key_spec.elems_key_public; } else if (data_n == 11 && !strncmp (data, "private-key", 11)) { is_secret = 1; elems = key_spec.elems_key_secret; } else { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems_n = strlen (elems); mpis_new = xtrymalloc (sizeof (*mpis_new) * (elems_n + 1)); if (! mpis_new) { err = gpg_error_from_errno (errno); goto out; } memset (mpis_new, 0, sizeof (*mpis_new) * (elems_n + 1)); value_list = gcry_sexp_find_token (sexp, key_spec.identifier, 0); if (! value_list) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } for (i = 0; i < elems_n; i++) { value_pair = gcry_sexp_find_token (value_list, elems + i, 1); if (! value_pair) { err = gpg_error (GPG_ERR_INV_SEXP); break; } /* Note that we need to use STD format; i.e. prepend a 0x00 to indicate a positive number if the high bit is set. */ mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD); if (! mpi) { err = gpg_error (GPG_ERR_INV_SEXP); break; } mpis_new[i] = mpi; gcry_sexp_release (value_pair); value_pair = NULL; } if (err) goto out; /* We do not require a comment sublist to be present here. */ data = NULL; data_n = 0; comment_list = gcry_sexp_find_token (sexp, "comment", 0); if (comment_list) data = gcry_sexp_nth_data (comment_list, 1, &data_n); if (! data) { data = "(none)"; data_n = 6; } comment_new = make_cstring (data, data_n); if (! comment_new) { err = gpg_error_from_errno (errno); goto out; } if (secret) *secret = is_secret; *mpis = mpis_new; *comment = comment_new; out: gcry_sexp_release (value_list); gcry_sexp_release (value_pair); gcry_sexp_release (comment_list); if (err) { xfree (comment_new); mpint_list_free (mpis_new); } return err; } /* Extract the car from SEXP, and create a newly created C-string which is to be stored in IDENTIFIER. */ static gpg_error_t sexp_extract_identifier (gcry_sexp_t sexp, char **identifier) { char *identifier_new; gcry_sexp_t sublist; const char *data; size_t data_n; gpg_error_t err; identifier_new = NULL; err = 0; sublist = gcry_sexp_nth (sexp, 1); if (! sublist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } data = gcry_sexp_nth_data (sublist, 0, &data_n); if (! data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } identifier_new = make_cstring (data, data_n); if (! identifier_new) { err = gpg_err_code_from_errno (errno); goto out; } *identifier = identifier_new; out: gcry_sexp_release (sublist); return err; } /* Key I/O. */ /* Search for a key specification entry. If SSH_NAME is not NULL, search for an entry whose "ssh_name" is equal to SSH_NAME; otherwise, search for an entry whose "name" is equal to NAME. Store found entry in SPEC on success, return error otherwise. */ static gpg_error_t ssh_key_type_lookup (const char *ssh_name, const char *name, ssh_key_type_spec_t *spec) { gpg_error_t err; unsigned int i; for (i = 0; i < DIM (ssh_key_types); i++) if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier))) || (name && (! strcmp (name, ssh_key_types[i].identifier)))) break; if (i == DIM (ssh_key_types)) err = gpg_error (GPG_ERR_NOT_FOUND); else { *spec = ssh_key_types[i]; err = 0; } return err; } /* Receive a key from STREAM, according to the key specification given as KEY_SPEC. Depending on SECRET, receive a secret or a public key. If READ_COMMENT is true, receive a comment string as well. Constructs a new S-Expression from received data and stores it in KEY_NEW. Returns zero on success or an error code. */ static gpg_error_t ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, int read_comment, ssh_key_type_spec_t *key_spec) { gpg_error_t err; char *key_type; char *comment; gcry_sexp_t key; ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list; const char *elems; mpi_list = NULL; key_type = NULL; comment = ""; key = NULL; err = stream_read_cstring (stream, &key_type); if (err) goto out; err = ssh_key_type_lookup (key_type, NULL, &spec); if (err) goto out; err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list); if (err) goto out; if (read_comment) { err = stream_read_cstring (stream, &comment); if (err) goto out; } if (secret) elems = spec.elems_key_secret; else elems = spec.elems_key_public; if (spec.key_modifier) { err = (*spec.key_modifier) (elems, mpi_list); if (err) goto out; } err = sexp_key_construct (&key, spec, secret, mpi_list, comment); if (err) goto out; if (key_spec) *key_spec = spec; *key_new = key; out: mpint_list_free (mpi_list); xfree (key_type); if (read_comment) xfree (comment); return err; } /* Converts a key of type TYPE, whose key material is given in MPIS, into a newly created binary blob, which is to be stored in BLOB/BLOB_SIZE. Returns zero on success or an error code. */ static gpg_error_t ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, const char *type, gcry_mpi_t *mpis) { unsigned char *blob_new; long int blob_size_new; estream_t stream; gpg_error_t err; unsigned int i; *blob = NULL; *blob_size = 0; blob_new = NULL; stream = NULL; err = 0; stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+"); if (! stream) { err = gpg_error_from_errno (errno); goto out; } err = stream_write_cstring (stream, type); if (err) goto out; for (i = 0; mpis[i] && (! err); i++) err = stream_write_mpi (stream, mpis[i]); if (err) goto out; blob_size_new = es_ftell (stream); if (blob_size_new == -1) { err = gpg_error_from_errno (errno); goto out; } err = es_fseek (stream, 0, SEEK_SET); if (err) goto out; blob_new = xtrymalloc (blob_size_new); if (! blob_new) { err = gpg_error_from_errno (errno); goto out; } err = stream_read_data (stream, blob_new, blob_size_new); if (err) goto out; *blob = blob_new; *blob_size = blob_size_new; out: if (stream) es_fclose (stream); if (err) xfree (blob_new); return err; } /* Write the public key KEY_PUBLIC to STREAM in SSH key format. If OVERRIDE_COMMENT is not NULL, it will be used instead of the comment stored in the key. */ static gpg_error_t ssh_send_key_public (estream_t stream, gcry_sexp_t key_public, const char *override_comment) { ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list; char *key_type; char *comment; unsigned char *blob; size_t blob_n; gpg_error_t err; key_type = NULL; mpi_list = NULL; comment = NULL; blob = NULL; err = sexp_extract_identifier (key_public, &key_type); if (err) goto out; err = ssh_key_type_lookup (NULL, key_type, &spec); if (err) goto out; err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &comment); if (err) goto out; err = ssh_convert_key_to_blob (&blob, &blob_n, spec.ssh_identifier, mpi_list); if (err) goto out; err = stream_write_string (stream, blob, blob_n); if (err) goto out; err = stream_write_cstring (stream, override_comment? override_comment : comment); out: mpint_list_free (mpi_list); xfree (key_type); xfree (comment); xfree (blob); return err; } /* Read a public key out of BLOB/BLOB_SIZE according to the key specification given as KEY_SPEC, storing the new key in KEY_PUBLIC. Returns zero on success or an error code. */ static gpg_error_t ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size, gcry_sexp_t *key_public, ssh_key_type_spec_t *key_spec) { estream_t blob_stream; gpg_error_t err; err = 0; blob_stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+"); if (! blob_stream) { err = gpg_error_from_errno (errno); goto out; } err = stream_write_data (blob_stream, blob, blob_size); if (err) goto out; err = es_fseek (blob_stream, 0, SEEK_SET); if (err) goto out; err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec); out: if (blob_stream) es_fclose (blob_stream); return err; } /* This function calculates the key grip for the key contained in the S-Expression KEY and writes it to BUFFER, which must be large enough to hold it. Returns usual error code. */ static gpg_error_t ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { if (!gcry_pk_get_keygrip (key, buffer)) return gpg_error (GPG_ERR_INTERNAL); return 0; } /* Converts the secret key KEY_SECRET into a public key, storing it in KEY_PUBLIC. SPEC is the according key specification. Returns zero on success or an error code. */ static gpg_error_t key_secret_to_public (gcry_sexp_t *key_public, ssh_key_type_spec_t spec, gcry_sexp_t key_secret) { char *comment; gcry_mpi_t *mpis; gpg_error_t err; int is_secret; comment = NULL; mpis = NULL; err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, &comment); if (err) goto out; err = sexp_key_construct (key_public, spec, 0, mpis, comment); out: mpint_list_free (mpis); xfree (comment); return err; } /* Check whether a smartcard is available and whether it has a usable key. Store a copy of that key at R_PK and return 0. If no key is available store NULL at R_PK and return an error code. If CARDSN is not NULL, a string with the serial number of the card will be a malloced and stored there. */ static gpg_error_t card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn) { gpg_error_t err; char *authkeyid; char *serialno = NULL; unsigned char *pkbuf; size_t pkbuflen; gcry_sexp_t s_pk; unsigned char grip[20]; *r_pk = NULL; if (cardsn) *cardsn = NULL; /* First see whether a card is available and whether the application is supported. */ err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED ) { /* Ask for the serial number to reset the card. */ err = agent_card_serialno (ctrl, &serialno); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); return err; } log_info (_("detected card with S/N: %s\n"), serialno); err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); } if (err) { log_error (_("error getting default authentication keyID of card: %s\n"), gpg_strerror (err)); xfree (serialno); return err; } /* Get the S/N if we don't have it yet. Use the fast getattr method. */ if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) ) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (err)); xfree (authkeyid); return err; } /* Read the public key. */ err = agent_card_readkey (ctrl, authkeyid, &pkbuf); if (err) { if (opt.verbose) log_info (_("no suitable card key found: %s\n"), gpg_strerror (err)); xfree (serialno); xfree (authkeyid); return err; } pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen); if (err) { log_error ("failed to build S-Exp from received card key: %s\n", gpg_strerror (err)); xfree (pkbuf); xfree (serialno); xfree (authkeyid); return err; } err = ssh_key_grip (s_pk, grip); if (err) { log_debug ("error computing keygrip from received card key: %s\n", gcry_strerror (err)); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } if ( agent_key_available (grip) ) { /* (Shadow)-key is not available in our key storage. */ unsigned char *shadow_info; unsigned char *tmp; shadow_info = make_shadow_info (serialno, authkeyid); if (!shadow_info) { err = gpg_error_from_errno (errno); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } err = agent_shadow_key (pkbuf, shadow_info, &tmp); xfree (shadow_info); if (err) { log_error (_("shadowing the key failed: %s\n"), gpg_strerror (err)); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } xfree (pkbuf); pkbuf = tmp; pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); assert (pkbuflen); err = agent_write_private_key (grip, pkbuf, pkbuflen, 0); if (err) { log_error (_("error writing key: %s\n"), gpg_strerror (err)); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } if (cardsn) { char *dispsn; /* If the card handler is able to return a short serialnumber, use that one, else use the complete serialno. */ if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn)) { *cardsn = xtryasprintf ("cardno:%s", dispsn); xfree (dispsn); } else *cardsn = xtryasprintf ("cardno:%s", serialno); if (!*cardsn) { err = gpg_error_from_errno (errno); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } xfree (pkbuf); xfree (serialno); xfree (authkeyid); *r_pk = s_pk; return 0; } /* Request handler. Each handler is provided with a CTRL context, a REQUEST object and a RESPONSE object. The actual request is to be read from REQUEST, the response needs to be written to RESPONSE. */ /* Handler for the "request_identities" command. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response) { char *key_type; ssh_key_type_spec_t spec; struct dirent *dir_entry; char *key_directory; size_t key_directory_n; char *key_path; unsigned char *buffer; size_t buffer_n; u32 key_counter; estream_t key_blobs; gcry_sexp_t key_secret; gcry_sexp_t key_public; DIR *dir; gpg_error_t err; int ret; FILE *ctrl_fp = NULL; char *cardsn; gpg_error_t ret_err; /* Prepare buffer stream. */ key_directory = NULL; key_secret = NULL; key_public = NULL; key_type = NULL; key_path = NULL; key_counter = 0; buffer = NULL; dir = NULL; err = 0; key_blobs = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+"); if (! key_blobs) { err = gpg_error_from_errno (errno); goto out; } /* Open key directory. */ key_directory = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL); if (! key_directory) { err = gpg_err_code_from_errno (errno); goto out; } key_directory_n = strlen (key_directory); key_path = xtrymalloc (key_directory_n + 46); if (! key_path) { err = gpg_err_code_from_errno (errno); goto out; } sprintf (key_path, "%s/", key_directory); sprintf (key_path + key_directory_n + 41, ".key"); dir = opendir (key_directory); if (! dir) { err = gpg_err_code_from_errno (errno); goto out; } /* First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol. */ if (!card_key_available (ctrl, &key_public, &cardsn)) { err = ssh_send_key_public (key_blobs, key_public, cardsn); gcry_sexp_release (key_public); key_public = NULL; xfree (cardsn); if (err) goto out; key_counter++; } /* Then look at all the registered an allowed keys. */ /* Fixme: We should better iterate over the control file and check whether the key file is there. This is better in resepct to performance if tehre are a lot of key sin our key storage. */ /* FIXME: make sure that buffer gets deallocated properly. */ err = open_control_file (&ctrl_fp, 0); if (err) goto out; while ( (dir_entry = readdir (dir)) ) { if ((strlen (dir_entry->d_name) == 44) && (! strncmp (dir_entry->d_name + 40, ".key", 4))) { char hexgrip[41]; int disabled; /* We do only want to return keys listed in our control file. */ strncpy (hexgrip, dir_entry->d_name, 40); hexgrip[40] = 0; if ( strlen (hexgrip) != 40 ) continue; if (search_control_file (ctrl_fp, hexgrip, &disabled) || disabled) continue; strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); /* Read file content. */ err = file_to_buffer (key_path, &buffer, &buffer_n); if (err) goto out; err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n); if (err) goto out; xfree (buffer); buffer = NULL; err = sexp_extract_identifier (key_secret, &key_type); if (err) goto out; err = ssh_key_type_lookup (NULL, key_type, &spec); if (err) goto out; xfree (key_type); key_type = NULL; err = key_secret_to_public (&key_public, spec, key_secret); if (err) goto out; gcry_sexp_release (key_secret); key_secret = NULL; err = ssh_send_key_public (key_blobs, key_public, NULL); if (err) goto out; gcry_sexp_release (key_public); key_public = NULL; key_counter++; } } ret = es_fseek (key_blobs, 0, SEEK_SET); if (ret) { err = gpg_error_from_errno (errno); goto out; } out: /* Send response. */ gcry_sexp_release (key_secret); gcry_sexp_release (key_public); if (! err) { ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); if (ret_err) goto leave; ret_err = stream_write_uint32 (response, key_counter); if (ret_err) goto leave; ret_err = stream_copy (response, key_blobs); if (ret_err) goto leave; } else { ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); goto leave; }; leave: if (key_blobs) es_fclose (key_blobs); if (dir) closedir (dir); if (ctrl_fp) fclose (ctrl_fp); free (key_directory); xfree (key_path); xfree (buffer); xfree (key_type); return ret_err; } /* This function hashes the data contained in DATA of size DATA_N according to the message digest algorithm specified by MD_ALGORITHM and writes the message digest to HASH, which needs to large enough for the digest. */ static gpg_error_t data_hash (unsigned char *data, size_t data_n, int md_algorithm, unsigned char *hash) { gcry_md_hash_buffer (md_algorithm, hash, data, data_n); return 0; } /* This function signs the data contained in CTRL, stores the created signature in newly allocated memory in SIG and it's size in SIG_N; SIG_ENCODER is the signature encoder to use. */ static gpg_error_t data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, unsigned char **sig, size_t *sig_n) { gpg_error_t err; gcry_sexp_t signature_sexp = NULL; estream_t stream = NULL; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; unsigned char *sig_blob = NULL; size_t sig_blob_n = 0; char *identifier = NULL; const char *identifier_raw; size_t identifier_n; ssh_key_type_spec_t spec; int ret; unsigned int i; const char *elems; size_t elems_n; gcry_mpi_t *mpis = NULL; *sig = NULL; *sig_n = 0; ctrl->use_auth_call = 1; err = agent_pksign_do (ctrl, _("Please enter the passphrase " "for the ssh key%0A %c"), &signature_sexp, CACHE_MODE_SSH); ctrl->use_auth_call = 0; if (err) goto out; valuelist = gcry_sexp_nth (signature_sexp, 1); if (! valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+"); if (! stream) { err = gpg_error_from_errno (errno); goto out; } identifier_raw = gcry_sexp_nth_data (valuelist, 0, &identifier_n); if (! identifier_raw) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } identifier = make_cstring (identifier_raw, identifier_n); if (! identifier) { err = gpg_error_from_errno (errno); goto out; } err = ssh_key_type_lookup (NULL, identifier, &spec); if (err) goto out; err = stream_write_cstring (stream, spec.ssh_identifier); if (err) goto out; elems = spec.elems_signature; elems_n = strlen (elems); mpis = xtrymalloc (sizeof (*mpis) * (elems_n + 1)); if (! mpis) { err = gpg_error_from_errno (errno); goto out; } memset (mpis, 0, sizeof (*mpis) * (elems_n + 1)); for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec.elems_signature + i, 1); if (! sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (! sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; err = (*sig_encoder) (stream, mpis); if (err) goto out; sig_blob_n = es_ftell (stream); if (sig_blob_n == -1) { err = gpg_error_from_errno (errno); goto out; } sig_blob = xtrymalloc (sig_blob_n); if (! sig_blob) { err = gpg_error_from_errno (errno); goto out; } ret = es_fseek (stream, 0, SEEK_SET); if (ret) { err = gpg_error_from_errno (errno); goto out; } err = stream_read_data (stream, sig_blob, sig_blob_n); if (err) goto out; *sig = sig_blob; *sig_n = sig_blob_n; out: if (err) xfree (sig_blob); if (stream) es_fclose (stream); gcry_sexp_release (valuelist); gcry_sexp_release (signature_sexp); gcry_sexp_release (sublist); mpint_list_free (mpis); xfree (identifier); return err; } /* Handler for the "sign_request" command. */ static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) { gcry_sexp_t key; ssh_key_type_spec_t spec; unsigned char hash[MAX_DIGEST_LEN]; unsigned int hash_n; unsigned char key_grip[20]; unsigned char *key_blob; u32 key_blob_size; unsigned char *data; unsigned char *sig; size_t sig_n; u32 data_size; u32 flags; gpg_error_t err; gpg_error_t ret_err; key_blob = NULL; data = NULL; sig = NULL; key = NULL; /* Receive key. */ err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec); if (err) goto out; /* Receive data to sign. */ err = stream_read_string (request, 0, &data, &data_size); if (err) goto out; /* FIXME? */ err = stream_read_uint32 (request, &flags); if (err) goto out; /* Hash data. */ hash_n = gcry_md_get_algo_dlen (GCRY_MD_SHA1); if (! hash_n) { err = gpg_error (GPG_ERR_INTERNAL); goto out; } err = data_hash (data, data_size, GCRY_MD_SHA1, hash); if (err) goto out; /* Calculate key grip. */ err = ssh_key_grip (key, key_grip); if (err) goto out; /* Sign data. */ ctrl->digest.algo = GCRY_MD_SHA1; memcpy (ctrl->digest.value, hash, hash_n); ctrl->digest.valuelen = hash_n; ctrl->digest.raw_value = ! (spec.flags & SPEC_FLAG_USE_PKCS1V2); ctrl->have_keygrip = 1; memcpy (ctrl->keygrip, key_grip, 20); err = data_sign (ctrl, spec.signature_encoder, &sig, &sig_n); out: /* Done. */ if (! err) { ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE); if (ret_err) goto leave; ret_err = stream_write_string (response, sig, sig_n); if (ret_err) goto leave; } else { ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); if (ret_err) goto leave; } leave: gcry_sexp_release (key); xfree (key_blob); xfree (data); xfree (sig); return ret_err; } /* This function extracts the comment contained in the key S-Expression KEY and stores a copy in COMMENT. Returns usual error code. */ static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment) { gcry_sexp_t comment_list; char *comment_new; const char *data; size_t data_n; gpg_error_t err; comment_list = gcry_sexp_find_token (key, "comment", 0); if (! comment_list) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } data = gcry_sexp_nth_data (comment_list, 1, &data_n); if (! data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } comment_new = make_cstring (data, data_n); if (! comment_new) { err = gpg_error_from_errno (errno); goto out; } *comment = comment_new; err = 0; out: gcry_sexp_release (comment_list); return err; } /* This function converts the key contained in the S-Expression KEY into a buffer, which is protected by the passphrase PASSPHRASE. Returns usual error code. */ static gpg_error_t ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase, unsigned char **buffer, size_t *buffer_n) { unsigned char *buffer_new; unsigned int buffer_new_n; gpg_error_t err; err = 0; buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0); buffer_new = xtrymalloc_secure (buffer_new_n); if (! buffer_new) { err = gpg_error_from_errno (errno); goto out; } gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n); /* FIXME: guarantee? */ err = agent_protect (buffer_new, passphrase, buffer, buffer_n); out: xfree (buffer_new); return err; } /* Store the ssh KEY into our local key storage and protect it after asking for a passphrase. Cache that passphrase. TTL is the maximum caching time for that key. If the key already exists in our key storage, don't do anything. When entering a new key also add an entry to the sshcontrol file. */ static gpg_error_t ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl) { gpg_error_t err; unsigned char key_grip_raw[20]; char key_grip[41]; unsigned char *buffer = NULL; unsigned int buffer_n; char *description = NULL; char *comment = NULL; unsigned int i; struct pin_entry_info_s *pi = NULL; err = ssh_key_grip (key, key_grip_raw); if (err) goto out; /* Check whether the key is already in our key storage. Don't do anything then. */ if ( !agent_key_available (key_grip_raw) ) goto out; /* Yes, key is available. */ err = ssh_key_extract_comment (key, &comment); if (err) goto out; if ( asprintf (&description, _("Please enter a passphrase to protect" " the received secret key%%0A" " %s%%0A" "within gpg-agent's key storage"), comment ? comment : "?") < 0) { err = gpg_error_from_errno (errno); goto out; } pi = gcry_calloc_secure (1, sizeof (*pi) + 100 + 1); if (!pi) { err = gpg_error_from_errno (errno); goto out; } pi->max_length = 100; pi->max_tries = 1; err = agent_askpin (ctrl, description, NULL, NULL, pi); if (err) goto out; err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n); if (err) goto out; /* Store this key to our key storage. */ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0); if (err) goto out; /* Cache this passphrase. */ for (i = 0; i < 20; i++) sprintf (key_grip + 2 * i, "%02X", key_grip_raw[i]); err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl); if (err) goto out; /* And add an entry to the sshcontrol file. */ err = add_control_entry (ctrl, key_grip, ttl); out: if (pi && pi->max_length) wipememory (pi->pin, pi->max_length); xfree (pi); xfree (buffer); xfree (comment); free (description); /* (asprintf allocated, thus regular free.) */ return err; } /* This function removes the key contained in the S-Expression KEY from the local key storage, in case it exists there. Returns usual error code. FIXME: this function is a stub. */ static gpg_error_t ssh_identity_drop (gcry_sexp_t key) { unsigned char key_grip[21] = { 0 }; gpg_error_t err; err = ssh_key_grip (key, key_grip); if (err) goto out; key_grip[sizeof (key_grip) - 1] = 0; /* FIXME: What to do here - forgetting the passphrase or deleting the key from key cache? */ out: return err; } /* Handler for the "add_identity" command. */ static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; gcry_sexp_t key; unsigned char b; int confirm; int ttl; confirm = 0; key = NULL; ttl = 0; /* FIXME? */ err = ssh_receive_key (request, &key, 1, 1, NULL); if (err) goto out; while (1) { err = stream_read_byte (request, &b); if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; } switch (b) { case SSH_OPT_CONSTRAIN_LIFETIME: { u32 n = 0; err = stream_read_uint32 (request, &n); if (! err) ttl = n; break; } case SSH_OPT_CONSTRAIN_CONFIRM: { confirm = 1; break; } default: /* FIXME: log/bad? */ break; } } if (err) goto out; /* FIXME: are constraints used correctly? */ err = ssh_identity_register (ctrl, key, ttl); out: gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "remove_identity" command. */ static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response) { unsigned char *key_blob; u32 key_blob_size; gcry_sexp_t key; gpg_error_t ret_err; gpg_error_t err; /* Receive key. */ key_blob = NULL; key = NULL; err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL); if (err) goto out; err = ssh_identity_drop (key); out: xfree (key_blob); gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* FIXME: stub function. Actually useful? */ static gpg_error_t ssh_identities_remove_all (void) { gpg_error_t err; err = 0; /* FIXME: shall we remove _all_ cache entries or only those registered through the ssh emulation? */ return err; } /* Handler for the "remove_all_identities" command. */ static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; err = ssh_identities_remove_all (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Lock agent? FIXME: stub function. */ static gpg_error_t ssh_lock (void) { gpg_error_t err; /* FIXME */ log_error ("ssh-agent's lock command is not implemented\n"); err = 0; return err; } /* Unock agent? FIXME: stub function. */ static gpg_error_t ssh_unlock (void) { gpg_error_t err; log_error ("ssh-agent's unlock command is not implemented\n"); err = 0; return err; } /* Handler for the "lock" command. */ static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; err = ssh_lock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "unlock" command. */ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; err = ssh_unlock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Return the request specification for the request identified by TYPE or NULL in case the requested request specification could not be found. */ static ssh_request_spec_t * request_spec_lookup (int type) { ssh_request_spec_t *spec; unsigned int i; for (i = 0; i < DIM (request_specs); i++) if (request_specs[i].type == type) break; if (i == DIM (request_specs)) { log_info ("ssh request %u is not supported\n", type); spec = NULL; } else spec = request_specs + i; return spec; } /* Process a single request. The request is read from and the response is written to STREAM_SOCK. Uses CTRL as context. Returns zero in case of success, non zero in case of failure. */ static int ssh_request_process (ctrl_t ctrl, estream_t stream_sock) { ssh_request_spec_t *spec; estream_t response; estream_t request; unsigned char request_type; gpg_error_t err; int send_err; int ret; unsigned char *request_data; u32 request_data_size; u32 response_size; request_data = NULL; response = NULL; request = NULL; send_err = 0; /* Create memory streams for request/response data. The entire request will be stored in secure memory, since it might contain secret key material. The response does not have to be stored in secure memory, since we never give out secret keys. Note: we only have little secure memory, but there is NO possibility of DoS here; only trusted clients are allowed to connect to the agent. What could happen is that the agent returns out-of-secure-memory errors on requests in case the agent's owner floods his own agent with many large messages. -moritz */ /* Retrieve request. */ err = stream_read_string (stream_sock, 1, &request_data, &request_data_size); if (err) goto out; if (opt.verbose > 1) log_info ("received ssh request of length %u\n", (unsigned int)request_data_size); if (! request_data_size) { send_err = 1; goto out; /* Broken request; FIXME. */ } request_type = request_data[0]; spec = request_spec_lookup (request_type); if (! spec) { send_err = 1; goto out; /* Unknown request; FIXME. */ } if (spec->secret_input) request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); else request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+"); if (! request) { err = gpg_error_from_errno (errno); goto out; } ret = es_setvbuf (request, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_errno (errno); goto out; } err = stream_write_data (request, request_data + 1, request_data_size - 1); if (err) goto out; es_rewind (request); response = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+"); if (! response) { err = gpg_error_from_errno (errno); goto out; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request, response); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } if (err) { send_err = 1; goto out; } response_size = es_ftell (response); if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); err = es_fseek (response, 0, SEEK_SET); if (err) { send_err = 1; goto out; } err = stream_write_uint32 (stream_sock, response_size); if (err) { send_err = 1; goto out; } err = stream_copy (stream_sock, response); if (err) goto out; err = es_fflush (stream_sock); if (err) goto out; out: if (err && es_feof (stream_sock)) log_error ("error occured while processing request: %s\n", gpg_strerror (err)); if (send_err) { if (opt.verbose > 1) log_info ("sending ssh error response\n"); err = stream_write_uint32 (stream_sock, 1); if (err) goto leave; err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE); if (err) goto leave; } leave: if (request) es_fclose (request); if (response) es_fclose (response); xfree (request_data); /* FIXME? */ return !!err; } /* Start serving client on SOCK_CLIENT. */ void start_command_handler_ssh (int sock_client) { struct server_control_s ctrl; estream_t stream_sock; gpg_error_t err; int ret; /* Setup control structure. */ memset (&ctrl, 0, sizeof (ctrl)); agent_init_default_ctrl (&ctrl); ctrl.connection_fd = sock_client; /* Because the ssh protocol does not send us information about the the current TTY setting, we resort here to use those from startup or those explictly set. */ if (!ctrl.display && opt.startup_display) ctrl.display = strdup (opt.startup_display); if (!ctrl.ttyname && opt.startup_ttyname) ctrl.ttyname = strdup (opt.startup_ttyname); if (!ctrl.ttytype && opt.startup_ttytype) ctrl.ttytype = strdup (opt.startup_ttytype); if (!ctrl.lc_ctype && opt.startup_lc_ctype) ctrl.lc_ctype = strdup (opt.startup_lc_ctype); if (!ctrl.lc_messages && opt.startup_lc_messages) ctrl.lc_messages = strdup (opt.startup_lc_messages); /* Create stream from socket. */ stream_sock = es_fdopen (sock_client, "r+"); if (!stream_sock) { err = gpg_error_from_errno (errno); log_error (_("failed to create stream from socket: %s\n"), gpg_strerror (err)); goto out; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ ret = es_setvbuf (stream_sock, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_errno (errno); log_error (_("failed to disable buffering " "on socket stream: %s\n"), gpg_strerror (err)); goto out; } /* Main processing loop. */ while ( !ssh_request_process (&ctrl, stream_sock) ) ; /* Reset the SCD in case it has been used. */ agent_reset_scd (&ctrl); out: if (stream_sock) es_fclose (stream_sock); free (ctrl.display); free (ctrl.ttyname); free (ctrl.ttytype); free (ctrl.lc_ctype); free (ctrl.lc_messages); } diff --git a/agent/command.c b/agent/command.c index daf9b8698..12770dac8 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,1104 +1,1105 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* FIXME: we should not use the default assuan buffering but setup some buffering in secure mempory to protect session keys etc. */ #include #include #include #include #include #include #include #include #include #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; char *keydesc; /* Allocated description for the next key operation. */ }; /* Release the memory buffer MB but first wipe out the used memory. */ static void clear_outbuf (membuf_t *mb) { void *p; size_t n; p = get_membuf (mb, &n); if (p) { memset (p, 0, n); xfree (p); } } /* Write the content of memory buffer MB as assuan data to CTX and wipe the buffer out afterwards. */ static gpg_error_t write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb) { assuan_error_t ae; void *p; size_t n; p = get_membuf (mb, &n); if (!p) return gpg_error (GPG_ERR_ENOMEM); ae = assuan_send_data (ctx, p, n); memset (p, 0, n); xfree (p); return map_assuan_err (ae); } static void reset_notify (ASSUAN_CONTEXT ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); memset (ctrl->keygrip, 0, 20); ctrl->have_keygrip = 0; ctrl->digest.valuelen = 0; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; } /* 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))); } /* Replace all '+' by a blank. */ static void plus_to_blank (char *s) { for (; *s; s++) { if (*s == '+') *s = ' '; } } /* Parse a hex string. Return an Assuan error code or 0 on success and the length of the parsed string in LEN. */ static int parse_hexstring (ASSUAN_CONTEXT ctx, const char *string, size_t *len) { const char *p; size_t n; /* parse the hash value */ for (p=string, n=0; hexdigitp (p); p++, n++) ; if (*p != ' ' && *p != '\t' && *p) return set_error (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=(const unsigned char*)string, n=0; n < 20; p += 2, n++) buf[n] = xtoi_2 (p); return 0; } /* ISTRUSTED 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 Store a new key in into the trustlist*/ static int cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; int flag; /* parse the fingerprint value */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (!spacep (p) || !(n == 40 || n == 32)) return set_error (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); } /* HAVEKEY 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 SETKEY Set the key used for a sign or decrypt operation */ static int cmd_sigkey (ASSUAN_CONTEXT ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); rc = parse_keygrip (ctx, line, ctrl->keygrip); if (rc) return rc; ctrl->have_keygrip = 1; return 0; } /* SETKEYDESC plus_percent_escaped_string: Set a description to be used for the next PKSIGN or PKDECRYPT operation if this operation requires the entry of a passphrase. If this command is not used a default text will be used. Note, that this description implictly selects the label used for the entry box; if the string contains the string PIN (which in general will not be translated), "PIN" is used, otherwise the translation of 'passphrase" is used. The description string should not contain blanks unless they are percent or '+' escaped. The description is only valid for the next PKSIGN or PKDECRYPT operation. */ static int cmd_setkeydesc (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *desc, *p; for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage; we might late use it for other args. */ if (!desc || !*desc) return set_error (Parameter_Error, "no description given"); /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ plus_to_blank (desc); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = xtrystrdup (desc); if (!ctrl->server_local->keydesc) return map_to_assuan_status (gpg_error_from_errno (errno)); return 0; } /* SETHASH 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_t 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 Perform the actual sign operation. Neither input nor output are sensitive to eavesdropping. */ static int cmd_pksign (ASSUAN_CONTEXT ctx, char *line) { int rc; cache_mode_t cache_mode = CACHE_MODE_NORMAL; ctrl_t ctrl = assuan_get_pointer (ctx); membuf_t outbuf; if (opt.ignore_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; else if (!ctrl->server_local->use_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; init_membuf (&outbuf, 512); rc = agent_pksign (ctrl, ctrl->server_local->keydesc, &outbuf, cache_mode); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); if (rc) log_error ("command pksign failed: %s\n", gpg_strerror (rc)); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return map_to_assuan_status (rc); } /* PKDECRYPT Perform the actual decrypt operation. Input is not sensitive to eavesdropping */ static int cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; membuf_t outbuf; /* First inquire the data to decrypt */ rc = assuan_inquire (ctx, "CIPHERTEXT", &value, &valuelen, MAXLEN_CIPHERTEXT); if (rc) return rc; init_membuf (&outbuf, 512); rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, value, valuelen, &outbuf); xfree (value); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); if (rc) log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc)); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return 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_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *value; size_t valuelen; membuf_t outbuf; /* First inquire the parameters */ rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) return rc; init_membuf (&outbuf, 512); rc = agent_genkey (ctrl, (char*)value, valuelen, &outbuf); xfree (value); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); if (rc) log_error ("command genkey failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* READKEY Return the public key for the given keygrip. */ static int cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_pkey = NULL; rc = parse_keygrip (ctx, line, grip); if (rc) return rc; /* Return immediately as this is already an Assuan error code.*/ rc = agent_public_key_from_file (ctrl, grip, &s_pkey); if (!rc) { size_t len; unsigned char *buf; len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) rc = gpg_error_from_errno (errno); else { len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, buf, len); assert (len); rc = assuan_send_data (ctx, buf, len); rc = map_assuan_err (rc); xfree (buf); } gcry_sexp_release (s_pkey); } if (rc) log_error ("command readkey failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* GET_PASSPHRASE [ ] 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_t 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_MODE_NORMAL, &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, CACHE_MODE_USER, 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 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, CACHE_MODE_USER, NULL, 0); return 0; } /* GET_CONFIRMATION This command may be used to ask for a simple confirmation. DESCRIPTION is displayed along with a Okay and Cancel button. 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. Note, that the length of DESCRIPTION is implicitly limited by the maximum length of a command. DESCRIPTION should not contain any spaces, those must be encoded either percent escaped or simply as '+'. */ static int cmd_get_confirmation (ASSUAN_CONTEXT ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *desc = NULL; char *p; /* parse the stuff */ for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage -may be later used for other args. */ if (!desc || !*desc) return set_error (Parameter_Error, "no description given"); if (!strcmp (desc, "X")) desc = NULL; /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ if (desc) plus_to_blank (desc); rc = agent_get_confirmation (ctrl, desc, NULL, NULL); if (rc) log_error ("command get_confirmation failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* 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) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = agent_handle_learn (ctrl, has_option (line, "--send")? ctx : NULL); if (rc) log_error ("command learn failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* PASSWD Change the passphrase/PID for the key identified by keygrip in LINE. */ static int cmd_passwd (ASSUAN_CONTEXT ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *shadow_info = NULL; rc = parse_keygrip (ctx, line, grip); if (rc) return rc; /* we can't jump to leave because this is already an Assuan error code. */ rc = agent_key_from_file (ctrl, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, &s_skey); if (rc) ; else if (!s_skey) { log_error ("changing a smartcard PIN is not yet supported\n"); rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else rc = agent_protect_and_store (ctrl, s_skey); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; 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); } /* PRESET_PASSPHRASE Set the cached passphrase/PIN for the key identified by the keygrip to passwd for the given time, where -1 means infinite and 0 means the default (currently only a timeout of -1 is allowed, which means to never expire it). If passwd is not provided, ask for it via the pinentry module. */ static int cmd_preset_passphrase (ASSUAN_CONTEXT ctx, char *line) { int rc; unsigned char grip[20]; char *grip_clear = NULL; char *passphrase = NULL; int ttl; if (!opt.allow_preset_passphrase) return gpg_error (GPG_ERR_NOT_SUPPORTED); rc = parse_keygrip (ctx, line, grip); if (rc) return rc; /* FIXME: parse_keygrip should return a tail pointer. */ grip_clear = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) return map_to_assuan_status (gpg_error (GPG_ERR_MISSING_VALUE)); *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; /* Currently, only infinite timeouts are allowed. */ ttl = -1; if (line[0] != '-' || line[1] != '1') return map_to_assuan_status (gpg_error (GPG_ERR_NOT_IMPLEMENTED)); line++; line++; while (!(*line != ' ' && *line != '\t')) line++; /* If there is a passphrase, use it. Currently, a passphrase is required. */ if (*line) passphrase = line; else return map_to_assuan_status (gpg_error (GPG_ERR_NOT_IMPLEMENTED)); rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl); if (rc) log_error ("command preset_passwd failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* SCD This is a general quote command to redirect everything to the SCDAEMON. */ static int cmd_scd (ASSUAN_CONTEXT ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = divert_generic_cmd (ctrl, line, ctx); return map_to_assuan_status (rc); } /* UPDATESTARTUPTTY Set startup TTY and X DISPLAY variables to the values of this session. This command is useful to pull future pinentries to another screen. It is only required because there is no way in the ssh-agent protocol to convey this information. */ static int cmd_updatestartuptty (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); xfree (opt.startup_display); opt.startup_display = NULL; xfree (opt.startup_ttyname); opt.startup_ttyname = NULL; xfree (opt.startup_ttytype); opt.startup_ttytype = NULL; xfree (opt.startup_lc_ctype); opt.startup_lc_ctype = NULL; xfree (opt.startup_lc_messages); opt.startup_lc_messages = NULL; if (ctrl->display) opt.startup_display = xtrystrdup (ctrl->display); if (ctrl->ttyname) opt.startup_ttyname = xtrystrdup (ctrl->ttyname); if (ctrl->ttytype) opt.startup_ttytype = xtrystrdup (ctrl->ttytype); if (ctrl->lc_ctype) opt.startup_lc_ctype = xtrystrdup (ctrl->lc_ctype); if (ctrl->lc_messages) opt.startup_lc_messages = xtrystrdup (ctrl->lc_messages); return 0; } static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) { ctrl_t 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; } /* 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 }, { "SETKEYDESC", cmd_setkeydesc }, { "SETHASH", cmd_sethash }, { "PKSIGN", cmd_pksign }, { "PKDECRYPT", cmd_pkdecrypt }, { "GENKEY", cmd_genkey }, { "READKEY", cmd_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase }, { "GET_CONFIRMATION", cmd_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted }, { "MARKTRUSTED", cmd_marktrusted }, { "LEARN", cmd_learn }, { "PASSWD", cmd_passwd }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd }, { "UPDATESTARTUPTTY", cmd_updatestartuptty }, { 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); ctrl.connection_fd = 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; ctrl.digest.raw_value = 0; 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; } } /* Reset the SCD if needed. */ agent_reset_scd (&ctrl); /* Reset the pinentry (in case of popup messages). */ agent_reset_query (&ctrl); 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); xfree (ctrl.server_local); } diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 926df2622..3dc7984e6 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -1,417 +1,418 @@ /* 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #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 (ctrl, &serialno); if (!rc) { log_debug ("detected card with S/N %s\n", serialno); i = strcmp (serialno, want_sn); xfree (serialno); serialno = NULL; if (!i) { xfree (want_sn); *r_kid = want_kid; return 0; /* yes, we have the correct card */ } } else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT) { log_debug ("no card present\n"); rc = 0; no_card = 1; } else { log_error ("error 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 container and return it in R_VAL. */ static int encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo, unsigned char **r_val, size_t *r_len) { unsigned char *frame; unsigned char asn[100]; size_t asnlen; *r_val = NULL; *r_len = 0; asnlen = DIM(asn); if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) { log_error ("no object identifier for algo %d\n", algo); return gpg_error (GPG_ERR_INTERNAL); } frame = xtrymalloc (asnlen + digestlen); if (!frame) return out_of_core (); memcpy (frame, asn, asnlen); memcpy (frame+asnlen, digest, digestlen); if (DBG_CRYPTO) log_printhex ("encoded hash:", frame, asnlen+digestlen); *r_val = frame; *r_len = asnlen+digestlen; return 0; } /* Callback used to ask for the PIN which should be set into BUF. The buf has been allocated by the caller and is of size MAXBUF which includes the terminating null. The function should return an UTF-8 string with the passphrase, the buffer may optionally be padded with arbitrary characters. INFO gets displayed as part of a generic string. However if the first character of INFO is a vertical bar all up to the next verical bar are considered flags and only everything after the second vertical bar gets displayed as the full prompt. Flags: 'N' = New PIN, this requests a second prompt to repeat the the PIN. If the PIN is not correctly repeated it starts from all over. 'A' = The PIN is an Admin PIN, SO-PIN, PUK or alike. Example: "|AN|Please enter the new security officer's PIN" The text "Please ..." will get displayed and the flags 'A' and 'N' are considered. */ static int getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) { struct pin_entry_info_s *pi; int rc; ctrl_t ctrl = opaque; const char *ends, *s; int any_flags = 0; int newpin = 0; const char *again_text = NULL; const char *prompt = "PIN"; if (buf && maxbuf < 2) return gpg_error (GPG_ERR_INV_VALUE); /* Parse the flags. */ if (info && *info =='|' && (ends=strchr (info+1, '|'))) { for (s=info+1; s < ends; s++) { if (*s == 'A') prompt = _("Admin PIN"); else if (*s == 'N') newpin = 1; } info = ends+1; any_flags = 1; } else if (info && *info == '|') log_debug ("pin_cb called without proper PIN info hack\n"); /* If BUF has been passed as NULL, we are in keypad mode: The callback opens the popup and immediatley returns. */ if (!buf) { if (maxbuf == 0) /* Close the pinentry. */ { agent_popup_message_stop (ctrl); rc = 0; } else if (maxbuf == 1) /* Open the pinentry. */ { rc = agent_popup_message_start (ctrl, info, NULL, NULL); } else rc = gpg_error (GPG_ERR_INV_VALUE); return rc; } /* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole mess because we should call the card's verify function from the pinentry check pin CB. */ again: pi = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10); if (!pi) return gpg_error_from_errno (errno); pi->max_length = maxbuf-1; pi->min_digits = 0; /* we want a real passphrase */ pi->max_digits = 8; pi->max_tries = 3; if (any_flags) { rc = agent_askpin (ctrl, info, prompt, again_text, pi); again_text = NULL; if (!rc && newpin) { struct pin_entry_info_s *pi2; pi2 = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10); if (!pi2) { rc = gpg_error_from_errno (errno); xfree (pi); return rc; } pi2->max_length = maxbuf-1; pi2->min_digits = 0; pi2->max_digits = 8; pi2->max_tries = 1; rc = agent_askpin (ctrl, _("Repeat this PIN"), prompt, NULL, pi2); if (!rc && strcmp (pi->pin, pi2->pin)) { again_text = N_("PIN not correctly repeated; try again"); xfree (pi2); xfree (pi); goto again; } xfree (pi2); } } else { char *desc; if ( asprintf (&desc, _("Please enter the PIN%s%s%s to unlock the card"), info? " (`":"", info? info:"", info? "')":"") < 0) desc = NULL; rc = agent_askpin (ctrl, desc?desc:info, prompt, NULL, pi); 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; unsigned 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 (ctrl, 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 (ctrl, kid, getpin_cb, ctrl, ciphertext, ciphertextlen, &plaintext, &plaintextlen); if (!rc) { *r_buf = plaintext; *r_len = plaintextlen; } xfree (kid); return rc; } int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context) { return agent_card_scd (ctrl, cmdline, getpin_cb, ctrl, assuan_context); } diff --git a/agent/findkey.c b/agent/findkey.c index 73ffb530d..3f793e5dd 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,731 +1,732 @@ /* findkey.c - locate the secret key * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #include #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; }; /* Write an S-expression formatted key to our key storage. With FORCE pased as true an existsing key with the given GRIP will get overwritten. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force) { int i; char *fname; FILE *fp; char hexgrip[40+4+1]; int fd; 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 && !access (fname, F_OK)) { log_error ("secret key file `%s' already exists\n", fname); xfree (fname); return gpg_error (GPG_ERR_GENERAL); } /* In FORCE mode we would like to create FNAME but only if it does not already exist. We cannot make this guarantee just using POSIX (GNU provides the "x" opentype for fopen, however, this is not portable). Thus, we use the more flexible open function and then use fdopen to obtain a stream. */ fd = open (fname, force? (O_CREAT | O_TRUNC | O_WRONLY) : (O_CREAT | O_EXCL | O_WRONLY), S_IRUSR | S_IWUSR #ifndef HAVE_W32_SYSTEM | S_IRGRP #endif ); if (fd < 0) fp = NULL; else { fp = fdopen (fd, "wb"); if (!fp) { int save_e = errno; close (fd); errno = save_e; } } if (!fp) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("can't create `%s': %s\n", fname, strerror (errno)); xfree (fname); return tmperr; } if (fwrite (buffer, length, 1, fp) != 1) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("error writing `%s': %s\n", fname, strerror (errno)); fclose (fp); remove (fname); xfree (fname); return tmperr; } if ( fclose (fp) ) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("error closing `%s': %s\n", fname, strerror (errno)); remove (fname); xfree (fname); return tmperr; } 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); } /* Modify a Key description, replacing certain special format characters. List of currently supported replacements: %% - Replaced by a single % %c - Replaced by the content of COMMENT. The functions returns 0 on success or an error code. On success a newly allocated string is stored at the address of RESULT. */ static gpg_error_t modify_description (const char *in, const char *comment, char **result) { size_t comment_length; size_t in_len; size_t out_len; char *out; size_t i; int special, pass; comment_length = strlen (comment); in_len = strlen (in); /* First pass calculates the length, second pass does the actual copying. */ out = NULL; out_len = 0; for (pass=0; pass < 2; pass++) { special = 0; for (i = 0; i < in_len; i++) { if (special) { special = 0; switch (in[i]) { case '%': if (out) *out++ = '%'; else out_len++; break; case 'c': /* Comment. */ if (out) { memcpy (out, comment, comment_length); out += comment_length; } else out_len += comment_length; break; default: /* Invalid special sequences are kept as they are. */ if (out) { *out++ = '%'; *out++ = in[i]; } else out_len+=2; break; } } else if (in[i] == '%') special = 1; else { if (out) *out++ = in[i]; else out_len++; } } if (!pass) { *result = out = xtrymalloc (out_len + 1); if (!out) return gpg_error_from_errno (errno); } } *out = 0; assert (*result + out_len == out); return 0; } /* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP should be the hex encoded keygrip of that key to be used with the caching mechanism. DESC_TEXT may be set to override the default description used for the pinentry. */ static int unprotect (ctrl_t ctrl, const char *desc_text, unsigned char **keybuf, const unsigned char *grip, cache_mode_t cache_mode) { 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 (cache_mode != CACHE_MODE_IGNORE) { void *cache_marker; const char *pw; pw = agent_get_cache (hexgrip, cache_mode, &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); if (!pi) return gpg_error_from_errno (errno); 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, desc_text, NULL, NULL, pi); if (!rc) { assert (arg.unprotected_key); agent_put_cache (hexgrip, cache_mode, pi->pin, 0); xfree (*keybuf); *keybuf = arg.unprotected_key; } xfree (pi); return rc; } /* Read the key identified by GRIP from the private key directory and return it as an gcrypt S-expression object in RESULT. On failure returns an error code and stores NULL at RESULT. */ static gpg_error_t read_key_file (const unsigned char *grip, gcry_sexp_t *result) { int i, rc; char *fname; FILE *fp; struct stat st; unsigned char *buf; size_t buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; *result = NULL; 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) { rc = gpg_error_from_errno (errno); log_error ("can't open `%s': %s\n", fname, strerror (errno)); xfree (fname); return rc; } if (fstat (fileno(fp), &st)) { rc = gpg_error_from_errno (errno); log_error ("can't stat `%s': %s\n", fname, strerror (errno)); xfree (fname); fclose (fp); return rc; } buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf || fread (buf, buflen, 1, fp) != 1) { rc = gpg_error_from_errno (errno); log_error ("error reading `%s': %s\n", fname, strerror (errno)); xfree (fname); fclose (fp); xfree (buf); return rc; } /* Convert the file into a gcrypt S-expression object. */ rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); xfree (fname); fclose (fp); xfree (buf); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return rc; } *result = s_skey; return 0; } /* Return the secret key as an S-Exp in RESULT after locating it using the grip. Returns NULL in RESULT if the operation should be diverted to a token; SHADOW_INFO will point then to an allocated S-Expression with the shadow_info part from the file. CACHE_MODE defines now the cache shall be used. DESC_TEXT may be set to present a custom description for the pinentry. */ gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, gcry_sexp_t *result) { int rc; unsigned char *buf; size_t len, buflen, erroff; gcry_sexp_t s_skey; int got_shadow_info = 0; *result = NULL; if (shadow_info) *shadow_info = NULL; rc = read_key_file (grip, &s_skey); if (rc) return rc; /* For use with the protection functions we also need the key as an canonical encoded S-expression in abuffer. Create this buffer now. */ len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { rc = gpg_error_from_errno (errno); gcry_sexp_release (s_skey); return rc; } len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len); assert (len); switch (agent_private_key_type (buf)) { case PRIVATE_KEY_CLEAR: break; /* no unprotection needed */ case PRIVATE_KEY_PROTECTED: { gcry_sexp_t comment_sexp; size_t comment_length; char *desc_text_final; const char *comment = NULL; /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length); if (!comment) { comment = ""; comment_length = 0; } desc_text_final = NULL; if (desc_text) { if (comment[comment_length]) { /* Not a C-string; create one. We might here allocate more than actually displayed but well, that shouldn't be a problem. */ char *tmp = xtrymalloc (comment_length+1); if (!tmp) rc = gpg_error_from_errno (errno); else { memcpy (tmp, comment, comment_length); tmp[comment_length] = 0; rc = modify_description (desc_text, tmp, &desc_text_final); xfree (tmp); } } else rc = modify_description (desc_text, comment, &desc_text_final); } if (!rc) { rc = unprotect (ctrl, desc_text_final, &buf, grip, cache_mode); if (rc) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (rc)); } gcry_sexp_release (comment_sexp); xfree (desc_text_final); } break; case PRIVATE_KEY_SHADOWED: if (shadow_info) { const unsigned char *s; size_t n; rc = agent_get_shadow_info (buf, &s); if (!rc) { n = gcry_sexp_canon_len (s, 0, NULL,NULL); assert (n); *shadow_info = xtrymalloc (n); if (!*shadow_info) rc = out_of_core (); else { memcpy (*shadow_info, s, n); rc = 0; got_shadow_info = 1; } } if (rc) log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); } else rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY); break; default: log_error ("invalid private key format\n"); rc = gpg_error (GPG_ERR_BAD_SECKEY); break; } gcry_sexp_release (s_skey); s_skey = NULL; if (rc || got_shadow_info) { xfree (buf); return rc; } buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); wipememory (buf, buflen); xfree (buf); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return rc; } *result = s_skey; return 0; } /* Return the public key for the keygrip GRIP. The result is stored at RESULT. This function extracts the public key from the private key database. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { int i, idx, rc; gcry_sexp_t s_skey; const char *algoname; gcry_sexp_t uri_sexp, comment_sexp; const char *uri, *comment; size_t uri_length, comment_length; char *format, *p; void *args[4+2+2+1]; /* Size is max. # of elements + 2 for uri + 2 for comment + end-of-list. */ int argidx; gcry_sexp_t list, l2; const char *name; const char *s; size_t n; const char *elems; gcry_mpi_t *array; *result = NULL; rc = read_key_file (grip, &s_skey); if (rc) return rc; list = gcry_sexp_find_token (s_skey, "shadowed-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_skey, "protected-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_skey, "private-key", 0 ); if (!list) { log_error ("invalid private key format\n"); gcry_sexp_release (s_skey); return gpg_error (GPG_ERR_BAD_SECKEY); } l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; name = gcry_sexp_nth_data (list, 0, &n); if (n==3 && !memcmp (name, "rsa", 3)) { algoname = "rsa"; elems = "ne"; } else if (n==3 && !memcmp (name, "dsa", 3)) { algoname = "dsa"; elems = "pqgy"; } else if (n==3 && !memcmp (name, "elg", 3)) { algoname = "elg"; elems = "pgy"; } else { log_error ("unknown private key algorithm\n"); gcry_sexp_release (list); gcry_sexp_release (s_skey); return gpg_error (GPG_ERR_BAD_SECKEY); } /* Allocate an array for the parameters and copy them out of the secret key. FIXME: We should have a generic copy function. */ array = xtrycalloc (strlen(elems) + 1, sizeof *array); if (!array) { rc = gpg_error_from_errno (errno); gcry_sexp_release (list); gcry_sexp_release (s_skey); return rc; } for (idx=0, s=elems; *s; s++, idx++ ) { l2 = gcry_sexp_find_token (list, s, 1); if (!l2) { /* Required parameter not found. */ for (i=0; i #include #include #include #include #include #include #include "agent.h" #include "i18n.h" static int store_key (gcry_sexp_t private, const char *passphrase, int force) { int rc; unsigned char *buf; size_t len; unsigned char grip[20]; if ( !gcry_pk_get_keygrip (private, grip) ) { log_error ("can't calculate keygrip\n"); return gpg_error (GPG_ERR_GENERAL); } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = gcry_malloc_secure (len); if (!buf) return out_of_core (); len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); assert (len); if (passphrase) { unsigned char *p; rc = agent_protect (buf, passphrase, &p, &len); if (rc) { xfree (buf); return rc; } xfree (buf); buf = p; } rc = agent_write_private_key (grip, buf, len, force); xfree (buf); return rc; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static int reenter_compare_cb (struct pin_entry_info_s *pi) { const char *pin1 = pi->check_cb_arg; if (!strcmp (pin1, pi->pin)) return 0; /* okay */ return -1; } /* Generate a new keypair according to the parameters given in KEYPARAM */ int agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; struct pin_entry_info_s *pi, *pi2; int rc; size_t len; char *buf; rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen); if (rc) { log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc)); return gpg_error (GPG_ERR_INV_DATA); } /* Get the passphrase now, cause key generation may take a while. */ { const char *text1 = _("Please enter the passphrase to%0A" "to protect your new key"); const char *text2 = _("Please re-enter this passphrase"); const char *initial_errtext = NULL; pi = gcry_calloc_secure (2, sizeof (*pi) + 100); pi2 = pi + (sizeof *pi + 100); pi->max_length = 100; pi->max_tries = 3; pi2->max_length = 100; pi2->max_tries = 3; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi); initial_errtext = NULL; if (!rc) { rc = agent_askpin (ctrl, text2, NULL, NULL, pi2); if (rc == -1) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = _("does not match - try again"); goto next_try; } } if (rc) return rc; if (!*pi->pin) { xfree (pi); pi = NULL; /* User does not want a passphrase. */ } } rc = gcry_pk_genkey (&s_key, s_keyparam ); gcry_sexp_release (s_keyparam); if (rc) { log_error ("key generation failed: %s\n", gpg_strerror (rc)); xfree (pi); return rc; } /* break out the parts */ s_private = gcry_sexp_find_token (s_key, "private-key", 0); if (!s_private) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_key); xfree (pi); return gpg_error (GPG_ERR_INV_DATA); } s_public = gcry_sexp_find_token (s_key, "public-key", 0); if (!s_public) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_private); gcry_sexp_release (s_key); xfree (pi); return gpg_error (GPG_ERR_INV_DATA); } gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ if (DBG_CRYPTO) log_debug ("storing private key\n"); rc = store_key (s_private, pi? pi->pin:NULL, 0); xfree (pi); pi = NULL; gcry_sexp_release (s_private); if (rc) { gcry_sexp_release (s_public); return rc; } /* return the public key */ if (DBG_CRYPTO) log_debug ("returning public key\n"); len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { gpg_error_t tmperr = out_of_core (); gcry_sexp_release (s_private); gcry_sexp_release (s_public); return tmperr; } len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len); assert (len); put_membuf (outbuf, buf, len); gcry_sexp_release (s_public); xfree (buf); return 0; } /* Apply a new passpahrse to the key S_SKEY and store it. */ int agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey) { struct pin_entry_info_s *pi, *pi2; int rc; { const char *text1 = _("Please enter the new passphrase"); const char *text2 = _("Please re-enter this passphrase"); const char *initial_errtext = NULL; pi = gcry_calloc_secure (2, sizeof (*pi) + 100); pi2 = pi + (sizeof *pi + 100); pi->max_length = 100; pi->max_tries = 3; pi2->max_length = 100; pi2->max_tries = 3; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi); if (!rc) { rc = agent_askpin (ctrl, text2, NULL, NULL, pi2); if (rc == -1) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = _("does not match - try again"); goto next_try; } } if (rc) 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 index 22bd5589d..fc2a2001a 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,1684 +1,1685 @@ /* gpg-agent.c - The GnuPG Agent * Copyright (C) 2000, 2001, 2002, 2003, 2004, * 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*HAVE_W32_SYSTEM*/ #include #include #include #define JNLIB_NEED_LOG_LOGV #include "agent.h" #include /* Malloc hooks */ #include "i18n.h" #include "sysutils.h" #ifdef HAVE_W32_SYSTEM #include "../jnlib/w32-afunix.h" #endif #include "setenv.h" enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, oOptions, oDebug, oDebugAll, oDebugLevel, oDebugWait, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oNoGrab, oLogFile, oServer, oDaemon, oBatch, oPinentryProgram, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oScdaemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, oMaxCacheTTLSSH, oUseStandardSocket, oNoUseStandardSocket, oIgnoreCacheForSigning, oAllowMarkTrusted, oAllowPresetPassphrase, oKeepTTY, oKeepDISPLAY, oSSHSupport, oDisableScdaemon, oWriteEnvFile }; static ARGPARSE_OPTS opts[] = { { aGPGConfList, "gpgconf-list", 256, "@" }, { 301, NULL, 0, N_("@Options:\n ") }, { oServer, "server", 0, N_("run in server mode (foreground)") }, { oDaemon, "daemon", 0, N_("run in daemon mode (background)") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oSh, "sh", 0, N_("sh-style command output") }, { oCsh, "csh", 0, N_("csh-style command output") }, { oOptions, "options" , 2, N_("|FILE|read options from FILE")}, { oDebug, "debug" ,4|16, "@"}, { oDebugAll, "debug-all" ,0, "@"}, { oDebugLevel, "debug-level" ,2, "@"}, { oDebugWait,"debug-wait",1, "@"}, { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, { oNoGrab, "no-grab" ,0, N_("do not grab keyboard and mouse")}, { oLogFile, "log-file" ,2, N_("use a log file for the server")}, { oUseStandardSocket, "use-standard-socket", 0, N_("use a standard location for the socket")}, { oNoUseStandardSocket, "no-use-standard-socket", 0, "@"}, { oPinentryProgram, "pinentry-program", 2 , N_("|PGM|use PGM as the PIN-Entry program") }, { oScdaemonProgram, "scdaemon-program", 2 , N_("|PGM|use PGM as the SCdaemon program") }, { oDisableScdaemon, "disable-scdaemon", 0, N_("do not use the SCdaemon") }, { oDisplay, "display", 2, "@" }, { oTTYname, "ttyname", 2, "@" }, { oTTYtype, "ttytype", 2, "@" }, { oLCctype, "lc-ctype", 2, "@" }, { oLCmessages, "lc-messages", 2, "@" }, { oKeepTTY, "keep-tty", 0, N_("ignore requests to change the TTY")}, { oKeepDISPLAY, "keep-display", 0, N_("ignore requests to change the X display")}, { oDefCacheTTL, "default-cache-ttl", 4, N_("|N|expire cached PINs after N seconds")}, { oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" }, { oMaxCacheTTL, "max-cache-ttl", 4, "@" }, { oMaxCacheTTLSSH, "max-cache-ttl-ssh", 4, "@" }, { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0, N_("do not use the PIN cache when signing")}, { oAllowMarkTrusted, "allow-mark-trusted", 0, N_("allow clients to mark keys as \"trusted\"")}, { oAllowPresetPassphrase, "allow-preset-passphrase", 0, N_("allow presetting passphrase")}, { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh-agent emulation") }, { oWriteEnvFile, "write-env-file", 2|8, N_("|FILE|write environment settings also to FILE")}, {0} }; #define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ #define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ #define MAX_CACHE_TTL (120*60) /* 2 hours */ /* 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 used for native gpg-agent requests. */ static char *socket_name; /* Name of the communication socket used for ssh-agent-emulation. */ static char *socket_name_ssh; /* 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; /* Helper to implement --debug-level */ static const char *debug_level; /* Keep track of the current log file so that we can avoid updating the log file after a SIGHUP if it didn't changed. Malloced. */ static char *current_logfile; /* The handle_tick() function may test whether a parent is still running. We record the PID of the parent here or -1 if it should be watched. */ static pid_t parent_pid = (pid_t)(-1); /* Local prototypes. */ static char *create_socket_name (int use_standard_socket, char *standard_name, char *template); static int create_server_socket (int is_standard_name, const char *name); static void create_directories (void); static void handle_connections (int listen_fd, int listen_fd_ssh); static int check_for_running_agent (int); /* Pth wrapper function definitions. */ GCRY_THREAD_OPTION_PTH_IMPL; /* Functions. */ 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_GT ); #else #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); #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); } /* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. Note that we don't fail here, because it is important to keep gpg-agent running even after re-reading the options due to a SIGHUP. */ static void set_debug (void) { if (!debug_level) ; else if (!strcmp (debug_level, "none")) opt.debug = 0; else if (!strcmp (debug_level, "basic")) opt.debug = DBG_ASSUAN_VALUE; else if (!strcmp (debug_level, "advanced")) opt.debug = DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE; else if (!strcmp (debug_level, "expert")) opt.debug = (DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE |DBG_CACHE_VALUE); else if (!strcmp (debug_level, "guru")) opt.debug = ~0; else { log_error (_("invalid debug-level `%s' given\n"), debug_level); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } /* Helper for cleanup to remove one socket with NAME. */ static void remove_socket (char *name) { if (name && *name) { char *p; remove (name); p = strrchr (name, '/'); if (p) { *p = 0; rmdir (name); *p = '/'; } *name = 0; } } static void cleanup (void) { remove_socket (socket_name); remove_socket (socket_name_ssh); } /* Handle options which are allowed to be reset after program start. Return true when the current option in PARGS could be handled and false if not. As a special feature, passing a value of NULL for PARGS, resets the options to the default. REREAD should be set true if it is not the initial option parsing. */ static int parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) { if (!pargs) { /* reset mode */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; opt.no_grab = 0; opt.pinentry_program = NULL; opt.scdaemon_program = NULL; opt.def_cache_ttl = DEFAULT_CACHE_TTL; opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; opt.max_cache_ttl_ssh = MAX_CACHE_TTL; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 0; opt.disable_scdaemon = 0; return 1; } switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: opt.debug |= pargs->r.ret_ulong; break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs->r.ret_str; break; case oLogFile: if (!reread) return 0; /* not handeld */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { log_set_file (pargs->r.ret_str); xfree (current_logfile); current_logfile = xtrystrdup (pargs->r.ret_str); } break; case oNoGrab: opt.no_grab = 1; break; case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_scdaemon = 1; break; case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break; default: return 0; /* not handled */ } return 1; /* handled */ } 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 gpgconf_list = 0; int standard_socket = 0; gpg_error_t err; const char *env_file_name = NULL; set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to INIT_SECMEM() somewhere after the option parsing */ log_set_prefix ("gpg-agent", JNLIB_LOG_WITH_PREFIX|JNLIB_LOG_WITH_PID); /* Try to auto set the character set. */ set_native_charset (NULL); i18n_init (); /* Libgcrypt requires us to register the threading model first. Note that this will also do the pth_init. */ err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); if (err) { log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", gpg_strerror (err)); } /* Check that the libraries are suitable. Do it here because the option parsing may need services of the library. */ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) { log_fatal( _("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); assuan_set_assuan_log_stream (log_get_stream ()); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); gcry_set_log_handler (my_gcry_logger, NULL); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); /* Set default options. */ parse_rereadable_options (NULL, 0); /* Reset them to default values. */ #ifdef HAVE_W32_SYSTEM standard_socket = 1; /* Under Windows we always use a standard socket. */ #endif shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; opt.homedir = default_homedir (); /* Record some of the original environment strings. */ opt.startup_display = getenv ("DISPLAY"); if (opt.startup_display) opt.startup_display = xstrdup (opt.startup_display); opt.startup_ttyname = ttyname (0); if (opt.startup_ttyname) opt.startup_ttyname = xstrdup (opt.startup_ttyname); opt.startup_ttytype = getenv ("TERM"); if (opt.startup_ttytype) opt.startup_ttytype = xstrdup (opt.startup_ttytype); /* Fixme: Better use the locale function here. */ opt.startup_lc_ctype = getenv ("LC_CTYPE"); if (opt.startup_lc_ctype) opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype); opt.startup_lc_messages = getenv ("LC_MESSAGES"); if (opt.startup_lc_messages) opt.startup_lc_messages = xstrdup (opt.startup_lc_messages); /* Check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) opt.homedir = pargs.r.ret_str; } /* initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 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, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oOptions: /* config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: opt.homedir = pargs.r.ret_str; break; case oNoDetach: nodetach = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oServer: pipe_server = 1; break; case oDaemon: is_daemon = 1; break; case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break; case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break; case oUseStandardSocket: standard_socket = 1; break; case oNoUseStandardSocket: standard_socket = 0; break; case oKeepTTY: opt.keep_tty = 1; break; case oKeepDISPLAY: opt.keep_display = 1; break; case oSSHSupport: opt.ssh_support = 1; break; case oWriteEnvFile: if (pargs.r_type) env_file_name = pargs.r.ret_str; else env_file_name = make_filename ("~/.gpg-agent-info", NULL); break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); if (nogreeting ) greeting = 0; if (greeting) { fprintf (stderr, "%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); fprintf (stderr, "%s\n", strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION /* We don't want to print it here because gpg-agent is useful of its own and quite matured. */ /*log_info ("NOTE: this is a development version!\n");*/ #endif set_debug (); if (atexit (cleanup)) { log_error ("atexit failed\n"); cleanup (); exit (1); } initialize_module_query (); initialize_module_call_scd (); /* Try to create missing directories. */ create_directories (); if (debug_wait && pipe_server) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); sleep (debug_wait); log_debug ("... okay\n"); } if (gpgconf_list) { char *filename; /* List options and default values in the GPG Conf format. */ /* The following list is taken from gnupg/tools/gpgconf-comp.c. */ /* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ #define GC_OPT_FLAG_NONE 0UL /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* The DEFAULT flag for an option indicates that the option has a default value. */ #define GC_OPT_FLAG_DEFAULT (1UL << 4) /* The DEF_DESC flag for an option indicates that the option has a default, which is described by the value of the default field. */ #define GC_OPT_FLAG_DEF_DESC (1UL << 5) /* The NO_ARG_DESC flag for an option indicates that the argument has a default, which is described by the value of the ARGDEF field. */ #define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) filename = make_filename (opt.homedir, "gpg-agent.conf", NULL ); printf ("gpgconf-gpg-agent.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, filename); xfree (filename); printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); printf ("default-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL ); printf ("no-grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); printf ("ignore-cache-for-signing:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); printf ("allow-mark-trusted:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); printf ("disable-scdaemon:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); agent_exit (0); } /* If this has been called without any options, we merely check whether an agent is already running. We do this here so that we don't clobber a logfile but print it directly to stderr. */ if (!pipe_server && !is_daemon) { log_set_prefix (NULL, JNLIB_LOG_WITH_PREFIX); check_for_running_agent (0); agent_exit (0); } #ifdef ENABLE_NLS /* gpg-agent usually does not output any messages because it runs in the background. For log files it is acceptable to have messages always encoded in utf-8. We switch here to utf-8, so that commands like --help still give native messages. It is far easier to switch only once instead of for every message and it actually helps when more then one thread is active (avoids an extra copy step). */ bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); #endif /* Now start with logging to a file if this is desired. */ if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX |JNLIB_LOG_WITH_TIME |JNLIB_LOG_WITH_PID)); current_logfile = xstrdup (logfile); } /* 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) ; /* NOTREACHED */ else { /* Regular server mode */ int fd; int fd_ssh; pid_t pid; /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default display when gpg-agent was started using --display or a client requested this using an OPTION command. Note, that we don't do this when running in reverse daemon mode (i.e. when exec the program given as arguments). */ #ifndef HAVE_W32_SYSTEM if (!opt.keep_display && !argc) unsetenv ("DISPLAY"); #endif /* Create the sockets. */ socket_name = create_socket_name (standard_socket, "S.gpg-agent", "/tmp/gpg-XXXXXX/S.gpg-agent"); if (opt.ssh_support) socket_name_ssh = create_socket_name (standard_socket, "S.gpg-agent.ssh", "/tmp/gpg-XXXXXX/S.gpg-agent.ssh"); fd = create_server_socket (standard_socket, socket_name); if (opt.ssh_support) fd_ssh = create_server_socket (standard_socket, socket_name_ssh); else fd_ssh = -1; /* If we are going to exec a program in the parent, we record the PID, so that the child may check whether the program is still alive. */ if (argc) parent_pid = getpid (); fflush (NULL); #ifdef HAVE_W32_SYSTEM pid = getpid (); printf ("set GPG_AGENT_INFO=%s;%lu;1\n", socket_name, (ulong)pid); #else /*!HAVE_W32_SYSTEM*/ pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* We are the parent */ char *infostr, *infostr_ssh_sock, *infostr_ssh_pid; close (fd); /* Create the info string: :: */ if (asprintf (&infostr, "GPG_AGENT_INFO=%s:%lu:1", socket_name, (ulong)pid ) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } if (opt.ssh_support) { if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", socket_name_ssh) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } if (asprintf (&infostr_ssh_pid, "SSH_AGENT_PID=%u", pid) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } } *socket_name = 0; /* Don't let cleanup() remove the socket - the child should do this from now on */ if (opt.ssh_support) *socket_name_ssh = 0; if (env_file_name) { FILE *fp; fp = fopen (env_file_name, "w"); if (!fp) log_error (_("error creating `%s': %s\n"), env_file_name, strerror (errno)); else { fputs (infostr, fp); putc ('\n', fp); if (opt.ssh_support) { fputs (infostr_ssh_sock, fp); putc ('\n', fp); fputs (infostr_ssh_pid, fp); putc ('\n', fp); } fclose (fp); } } if (argc) { /* Run the program given on the commandline. */ if (putenv (infostr)) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } if (opt.ssh_support && putenv (infostr_ssh_sock)) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } if (opt.ssh_support && putenv (infostr_ssh_pid)) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); exit (1); } else { /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { *strchr (infostr, '=') = ' '; printf ("setenv %s\n", infostr); if (opt.ssh_support) { *strchr (infostr_ssh_sock, '=') = ' '; printf ("setenv %s\n", infostr_ssh_sock); *strchr (infostr_ssh_pid, '=') = ' '; printf ("setenv %s\n", infostr_ssh_pid); } } else { printf ( "%s; export GPG_AGENT_INFO;\n", infostr); if (opt.ssh_support) { printf ("%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock); printf ("%s; export SSH_AGENT_PID;\n", infostr_ssh_pid); } } free (infostr); /* (Note that a vanilla free is here correct.) */ if (opt.ssh_support) { free (infostr_ssh_sock); free (infostr_ssh_pid); } exit (0); } /*NOTREACHED*/ } /* End parent */ /* This is the child */ /* Detach from tty and put process into a new session */ if (!nodetach ) { int i; unsigned int oldflags; /* Close stdin, stdout and stderr unless it is the log stream */ for (i=0; i <= 2; i++) { if (!log_test_fd (i) && i != fd ) close (i); } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | JNLIB_LOG_RUN_DETACHED); opt.running_detached = 1; } if (chdir("/")) { log_error ("chdir to / failed: %s\n", strerror (errno)); exit (1); } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif /*!HAVE_W32_SYSTEM*/ handle_connections (fd, opt.ssh_support ? fd_ssh : -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) { ctrl->connection_fd = -1; /* 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, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = 1; /* do not remove the args */ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) { if (pargs.r_opt < -1) pargs.err = 1; /* Print a warning. */ else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } fclose (fp); set_debug (); } /* Create a name for the socket. With USE_STANDARD_SOCKET given as true using STANDARD_NAME in the home directory or if given has false from the mkdir type name TEMPLATE. In the latter case a unique name in a unique new directory will be created. In both cases check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. Retunrs: Pointer to an allcoated string with the absolute name of the socket used. */ static char * create_socket_name (int use_standard_socket, char *standard_name, char *template) { char *name, *p; if (use_standard_socket) name = make_filename (opt.homedir, standard_name, NULL); else { name = xstrdup (template); p = strrchr (name, '/'); if (!p) BUG (); *p = 0; if (!mkdtemp (name)) { log_error (_("can't create directory `%s': %s\n"), name, strerror (errno)); agent_exit (2); } *p = '/'; } if (strchr (name, PATHSEP_C)) { log_error (("`%s' are not allowed in the socket name\n"), PATHSEP_S); agent_exit (2); } if (strlen (name) + 1 >= DIMof (struct sockaddr_un, sun_path) ) { log_error (_("name of socket too long\n")); agent_exit (2); } return name; } /* Create a Unix domain socket with NAME. IS_STANDARD_NAME indicates whether a non-random socket is used. Returns the filedescriptor or terminates the process in case of an error. */ static int create_server_socket (int is_standard_name, const char *name) { struct sockaddr_un *serv_addr; socklen_t len; int fd; int rc; #ifdef HAVE_W32_SYSTEM fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); #else fd = socket (AF_UNIX, SOCK_STREAM, 0); #endif if (fd == -1) { log_error (_("can't create socket: %s\n"), strerror (errno)); agent_exit (2); } serv_addr = xmalloc (sizeof (*serv_addr)); memset (serv_addr, 0, sizeof *serv_addr); serv_addr->sun_family = AF_UNIX; assert (strlen (name) + 1 < sizeof (serv_addr->sun_path)); strcpy (serv_addr->sun_path, name); len = (offsetof (struct sockaddr_un, sun_path) + strlen (serv_addr->sun_path) + 1); #ifdef HAVE_W32_SYSTEM rc = _w32_sock_bind (fd, (struct sockaddr*) serv_addr, len); if (is_standard_name && rc == -1 ) { remove (name); rc = bind (fd, (struct sockaddr*) serv_addr, len); } #else rc = bind (fd, (struct sockaddr*) serv_addr, len); if (is_standard_name && rc == -1 && errno == EADDRINUSE) { remove (name); rc = bind (fd, (struct sockaddr*) serv_addr, len); } #endif if (rc == -1) { log_error (_("error binding socket to `%s': %s\n"), serv_addr->sun_path, strerror (errno)); close (fd); agent_exit (2); } if (listen (fd, 5 ) == -1) { log_error (_("listen() failed: %s\n"), strerror (errno)); close (fd); agent_exit (2); } if (opt.verbose) log_info (_("listening on socket `%s'\n"), serv_addr->sun_path); return fd; } /* Check that the directory for storing the private keys exists and create it if not. This function won't fail as it is only a convenience function and not strictly necessary. */ static void create_private_keys_directory (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL); if (stat (fname, &statbuf) && errno == ENOENT) { #ifdef HAVE_W32_SYSTEM /*FIXME: Setup proper permissions. */ if (!CreateDirectory (fname, NULL)) log_error (_("can't create directory `%s': %s\n"), fname, w32_strerror (-1) ); #else if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR )) log_error (_("can't create directory `%s': %s\n"), fname, strerror (errno) ); #endif else if (!opt.quiet) log_info (_("directory `%s' created\n"), fname); } xfree (fname); } /* Create the directory only if the supplied directory name is the same as the default one. This way we avoid to create arbitrary directories when a non-default home directory is used. To cope with HOME, we compare only the suffix if we see that the default homedir does start with a tilde. We don't stop here in case of problems because other functions will throw an error anyway.*/ static void create_directories (void) { struct stat statbuf; const char *defhome = 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) ) ) { #ifdef HAVE_W32_SYSTEM if (!CreateDirectory (home, NULL)) log_error (_("can't create directory `%s': %s\n"), home, w32_strerror (-1) ); #else if (mkdir (home, S_IRUSR|S_IWUSR|S_IXUSR )) log_error (_("can't create directory `%s': %s\n"), home, strerror (errno) ); #endif else { if (!opt.quiet) log_info (_("directory `%s' created\n"), home); create_private_keys_directory (home); } } } else log_error (_("stat() failed for `%s': %s\n"), home, strerror (errno)); } else if ( !S_ISDIR(statbuf.st_mode)) { log_error (_("can't use `%s' as home directory\n"), home); } else /* exists and is a directory. */ { create_private_keys_directory (home); } xfree (home); } /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { /* Check whether the scdaemon has died and cleanup in this case. */ agent_scd_check_aliveness (); /* If we are running as a child of another process, check whether the parent is still alive and shutdown if not. */ #ifndef HAVE_W32_SYSTEM if (parent_pid != (pid_t)(-1)) { if (kill (parent_pid, 0)) { shutdown_pending = 2; log_info ("parent process died - shutting down\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } } #endif /*HAVE_W32_SYSTEM*/ } static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: log_info ("SIGHUP received - " "re-reading configuration and flushing cache\n"); agent_flush_cache (); reread_configuration (); agent_reload_trustlist (); break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); agent_query_dump_state (); agent_scd_dump_state (); break; case SIGUSR2: log_info ("SIGUSR2 received - checking smartcard status\n"); break; case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); else log_info ("SIGTERM received - still %ld running threads\n", pth_ctrl( PTH_CTRL_GETTHREADS )); shutdown_pending++; if (shutdown_pending > 2) { log_info ("shutdown forced\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); agent_exit (0); break; #endif default: log_info ("signal %d received - no action defined\n", signo); } } /* This is the standard connection thread's main function. */ static void * start_connection_thread (void *arg) { int fd = (int)arg; if (opt.verbose) log_info (_("handler 0x%lx for fd %d started\n"), (long)pth_self (), fd); /* FIXME: Move this housekeeping into a ticker function. Calling it for each connection should work but won't work anymore if our clients start to keep connections. */ agent_trustlist_housekeeping (); start_command_handler (-1, fd); if (opt.verbose) log_info (_("handler 0x%lx for fd %d terminated\n"), (long)pth_self (), fd); return NULL; } /* This is the ssh connection thread's main function. */ static void * start_connection_thread_ssh (void *arg) { int fd = (int)arg; if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d started\n"), (long)pth_self (), fd); agent_trustlist_housekeeping (); start_command_handler_ssh (fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), (long)pth_self (), fd); return NULL; } /* Connection handler loop. Wait for coecntion requests and spawn a thread after accepting a connection. */ static void handle_connections (int listen_fd, int listen_fd_ssh) { pth_attr_t tattr; pth_event_t ev, time_ev; sigset_t sigs; int signo; struct sockaddr_un paddr; socklen_t plen; fd_set fdset, read_fdset; int ret; int fd; tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); #ifndef HAVE_W32_SYSTEM /* fixme */ /* Make sure that the signals we are going to handle are not blocked and create an event object for them. */ sigemptyset (&sigs ); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGUSR1); sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); pth_sigmask (SIG_UNBLOCK, &sigs, NULL); ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #else ev = NULL; #endif time_ev = NULL; FD_ZERO (&fdset); FD_SET (listen_fd, &fdset); if (listen_fd_ssh != -1) FD_SET (listen_fd_ssh, &fdset); for (;;) { sigset_t oldsigs; 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; } /* Create a timeout event if needed. */ if (!time_ev) time_ev = pth_event (PTH_EVENT_TIME, pth_timeout (2, 0)); /* POSIX says that fd_set should be implemented as a structure, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; if (time_ev) pth_event_concat (ev, time_ev, NULL); ret = pth_select_ev (FD_SETSIZE, &read_fdset, NULL, NULL, NULL, ev); if (time_ev) pth_event_isolate (time_ev); if (ret == -1) { if (pth_event_occurred (ev) || (time_ev && pth_event_occurred (time_ev))) { if (pth_event_occurred (ev)) handle_signal (signo); if (time_ev && pth_event_occurred (time_ev)) { pth_event_free (time_ev, PTH_FREE_ALL); time_ev = NULL; handle_tick (); } continue; } log_error (_("pth_select failed: %s - waiting 1s\n"), strerror (errno)); pth_sleep (1); continue; } if (pth_event_occurred (ev)) { handle_signal (signo); } if (time_ev && pth_event_occurred (time_ev)) { pth_event_free (time_ev, PTH_FREE_ALL); time_ev = NULL; handle_tick (); } /* We now might create new threads and because we don't want any signals - we are handling here - to be delivered to a new thread. Thus we need to block those signals. */ pth_sigmask (SIG_BLOCK, &sigs, &oldsigs); if (FD_ISSET (listen_fd, &read_fdset)) { plen = sizeof paddr; fd = pth_accept (listen_fd, (struct sockaddr *)&paddr, &plen); if (fd == -1) { log_error ("accept failed: %s\n", strerror (errno)); } else { char threadname[50]; snprintf (threadname, sizeof threadname-1, "conn fd=%d (gpg)", fd); threadname[sizeof threadname -1] = 0; pth_attr_set (tattr, PTH_ATTR_NAME, threadname); if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) { log_error ("error spawning connection handler: %s\n", strerror (errno) ); close (fd); } } fd = -1; } if (listen_fd_ssh != -1 && FD_ISSET (listen_fd_ssh, &read_fdset)) { plen = sizeof paddr; fd = pth_accept (listen_fd_ssh, (struct sockaddr *)&paddr, &plen); if (fd == -1) { log_error ("accept failed for ssh: %s\n", strerror (errno)); } else { char threadname[50]; snprintf (threadname, sizeof threadname-1, "conn fd=%d (ssh)", fd); threadname[sizeof threadname -1] = 0; pth_attr_set (tattr, PTH_ATTR_NAME, threadname); if (!pth_spawn (tattr, start_connection_thread_ssh, (void*)fd)) { log_error ("error spawning ssh connection handler: %s\n", strerror (errno) ); close (fd); } } fd = -1; } /* Restore the signal mask. */ pth_sigmask (SIG_SETMASK, &oldsigs, NULL); } pth_event_free (ev, PTH_FREE_ALL); if (time_ev) pth_event_free (time_ev, PTH_FREE_ALL); cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); } /* Figure out whether an agent is available and running. Prints an error if not. Usually started with MODE 0. */ static int check_for_running_agent (int mode) { int rc; char *infostr, *p; assuan_context_t ctx; int prot, pid; if (!mode) { infostr = getenv ("GPG_AGENT_INFO"); if (!infostr || !*infostr) { if (!check_for_running_agent (1)) return 0; /* Okay, its running on the standard socket. */ log_error (_("no gpg-agent running in this session\n")); return -1; } infostr = xstrdup (infostr); if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) { xfree (infostr); if (!check_for_running_agent (1)) return 0; /* Okay, its running on the standard socket. */ log_error (_("malformed GPG_AGENT_INFO environment variable\n")); return -1; } *p++ = 0; pid = atoi (p); while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { xfree (infostr); log_error (_("gpg-agent protocol version %d is not supported\n"), prot); if (!check_for_running_agent (1)) return 0; /* Okay, its running on the standard socket. */ return -1; } } else /* MODE != 0 */ { infostr = make_filename (opt.homedir, "S.gpg-agent", NULL); pid = (pid_t)(-1); } rc = assuan_socket_connect (&ctx, infostr, pid); xfree (infostr); if (rc) { if (!mode && !check_for_running_agent (1)) return 0; /* Okay, its running on the standard socket. */ if (!mode) log_error ("can't connect to the agent: %s\n", assuan_strerror (rc)); return -1; } if (!opt.quiet) log_info ("gpg-agent running and available\n"); assuan_disconnect (ctx); return 0; } diff --git a/agent/learncard.c b/agent/learncard.c index 72238507f..8ddf4ee54 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -1,435 +1,436 @@ /* learncard.c - Handle the LEARN command * Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "agent.h" #include /* Structures used by the callback mechanism to convey information pertaining to key pairs. */ struct keypair_info_s { struct keypair_info_s *next; int no_cert; char *id; /* points into grip */ char hexgrip[1]; /* The keygrip (i.e. a hash over the public key parameters) formatted as a hex string. Allocated somewhat large to also act as memeory for the above ID field. */ }; typedef struct keypair_info_s *KEYPAIR_INFO; struct kpinfo_cb_parm_s { int error; KEYPAIR_INFO info; }; /* Structures used by the callback mechanism to convey information pertaining to certificates. */ struct certinfo_s { struct certinfo_s *next; int type; int done; char id[1]; }; typedef struct certinfo_s *CERTINFO; struct certinfo_cb_parm_s { int error; CERTINFO info; }; /* Structures used by the callback mechanism to convey assuan status lines. */ struct sinfo_s { struct sinfo_s *next; char *data; /* Points into keyword. */ char keyword[1]; }; typedef struct sinfo_s *SINFO; struct sinfo_cb_parm_s { int error;; SINFO info; }; /* Destructor for key information objects. */ static void release_keypair_info (KEYPAIR_INFO info) { while (info) { KEYPAIR_INFO tmp = info->next; xfree (info); info = tmp; } } /* Destructor for certificate information objects. */ static void release_certinfo (CERTINFO info) { while (info) { CERTINFO tmp = info->next; xfree (info); info = tmp; } } /* Destructor for status information objects. */ static void release_sinfo (SINFO info) { while (info) { SINFO tmp = info->next; xfree (info); info = tmp; } } /* This callback is used by agent_card_learn and passed the content of all KEYPAIRINFO lines. It merely stores this data away */ static void kpinfo_cb (void *opaque, const char *line) { struct kpinfo_cb_parm_s *parm = opaque; KEYPAIR_INFO item; char *p; if (parm->error) return; /* no need to gather data after an error coccured */ 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; } static int send_cert_back (ctrl_t ctrl, const char *id, void *assuan_context) { int rc; char *derbuf; size_t derbuflen; rc = agent_card_readcert (ctrl, id, &derbuf, &derbuflen); if (rc) { 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 back via Assuan. */ int agent_handle_learn (ctrl_t ctrl, void *assuan_context) { int rc; struct kpinfo_cb_parm_s parm; struct certinfo_cb_parm_s cparm; struct sinfo_cb_parm_s sparm; char *serialno = NULL; KEYPAIR_INFO item; SINFO sitem; unsigned char grip[20]; char *p; int i; static int certtype_list[] = { 101, /* trusted */ 102, /* useful */ 100, /* regular */ /* We don't include 110 here because gpgsm can't handle it. */ -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 (ctrl, &serialno); if (rc) goto leave; /* Now gather all the available info. */ rc = agent_card_learn (ctrl, kpinfo_cb, &parm, certinfo_cb, &cparm, sinfo_cb, &sparm); if (!rc && (parm.error || cparm.error || sparm.error)) rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error; if (rc) { log_debug ("agent_card_learn failed: %s\n", gpg_strerror (rc)); goto leave; } log_info ("card has S/N: %s\n", serialno); /* Pass on all the collected status information. */ if (assuan_context) { for (sitem = sparm.info; sitem; sitem = sitem->next) { assuan_write_status (assuan_context, sitem->keyword, sitem->data); } } /* Write out the certificates in a standard order. */ for (i=0; certtype_list[i] != -1; i++) { CERTINFO citem; for (citem = cparm.info; citem; citem = citem->next) { if (certtype_list[i] != citem->type) continue; if (opt.verbose) log_info (" id: %s (type=%d)\n", citem->id, citem->type); if (assuan_context) { rc = send_cert_back (ctrl, citem->id, assuan_context); if (rc) goto leave; citem->done = 1; } } } for (item = parm.info; item; item = item->next) { unsigned char *pubkey, *shdkey; size_t n; if (opt.verbose) log_info (" id: %s (grip=%s)\n", item->id, item->hexgrip); if (item->no_cert) continue; /* No public key yet available. */ for (p=item->hexgrip, i=0; i < 20; p += 2, i++) grip[i] = xtoi_2 (p); if (!agent_key_available (grip)) continue; /* The key is already available. */ /* Unknown key - store it. */ rc = agent_card_readkey (ctrl, item->id, &pubkey); if (rc) { log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc)); goto leave; } { unsigned char *shadow_info = make_shadow_info (serialno, item->id); if (!shadow_info) { rc = gpg_error (GPG_ERR_ENOMEM); xfree (pubkey); goto leave; } rc = agent_shadow_key (pubkey, shadow_info, &shdkey); xfree (shadow_info); } xfree (pubkey); if (rc) { log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); goto leave; } n = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); assert (n); rc = agent_write_private_key (grip, shdkey, n, 0); xfree (shdkey); if (rc) { log_error ("error writing key: %s\n", gpg_strerror (rc)); goto leave; } if (opt.verbose) log_info ("stored\n"); if (assuan_context) { CERTINFO citem; /* only send the certificate if we have not done so before */ for (citem = cparm.info; citem; citem = citem->next) { if (!strcmp (citem->id, item->id)) break; } if (!citem) { rc = send_cert_back (ctrl, item->id, assuan_context); if (rc) goto leave; } } } leave: xfree (serialno); release_keypair_info (parm.info); release_certinfo (cparm.info); release_sinfo (sparm.info); return rc; } diff --git a/agent/minip12.c b/agent/minip12.c index 6f99bf24d..912d387d8 100644 --- a/agent/minip12.c +++ b/agent/minip12.c @@ -1,1957 +1,1958 @@ /* minip12.c - A minimal pkcs-12 implementation. * Copyright (C) 2002, 2003, 2004, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef TEST #include #include #include #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_pkcs_12_CertBag[11] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 }; static unsigned char const oid_pkcs_12_CrlBag[11] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 }; static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 }; static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 }; static unsigned char const oid_x509Certificate_for_pkcs_12[10] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 }; static unsigned char const oid_rsaEncryption[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; static unsigned char const data_3desiter2048[30] = { 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E, 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; #define DATA_3DESITER2048_SALT_OFF 18 static unsigned char const data_rc2iter2048[30] = { 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E, 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; #define DATA_RC2ITER2048_SALT_OFF 18 static unsigned char const data_mactemplate[51] = { 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x02, 0x08, 0x00 }; #define DATA_MACTEMPLATE_MAC_OFF 17 #define DATA_MACTEMPLATE_SALT_OFF 39 struct buffer_s { unsigned char *buffer; size_t length; }; struct tag_info { int class; int is_constructed; unsigned long tag; unsigned long length; /* length part of the TLV */ int nhdr; int ndef; /* It is an indefinite length */ }; /* Parse the buffer at the address BUFFER which is of SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE on success. Checks that the encoded length does not exhaust the length of the provided buffer. */ static int parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) { int c; unsigned long tag; const unsigned char *buf = *buffer; size_t length = *size; ti->length = 0; ti->ndef = 0; ti->nhdr = 0; /* Get the tag */ if (!length) return -1; /* premature eof */ c = *buf++; length--; ti->nhdr++; ti->class = (c & 0xc0) >> 6; ti->is_constructed = !!(c & 0x20); tag = c & 0x1f; if (tag == 0x1f) { tag = 0; do { tag <<= 7; if (!length) return -1; /* premature eof */ c = *buf++; length--; ti->nhdr++; tag |= c & 0x7f; } while (c & 0x80); } ti->tag = tag; /* Get the length */ if (!length) return -1; /* prematureeof */ c = *buf++; length--; ti->nhdr++; if ( !(c & 0x80) ) ti->length = c; else if (c == 0x80) ti->ndef = 1; else if (c == 0xff) return -1; /* forbidden length value */ else { unsigned long len = 0; int count = c & 0x7f; for (; count; count--) { len <<= 8; if (!length) return -1; /* premature_eof */ c = *buf++; length--; ti->nhdr++; len |= c & 0xff; } ti->length = len; } if (ti->class == UNIVERSAL && !ti->tag) ti->length = 0; if (ti->length > length) return -1; /* data larger than buffer. */ *buffer = buf; *size = length; return 0; } /* Given an ASN.1 chunk of a structure like: 24 NDEF: OCTET STRING -- This is not passed to us 04 1: OCTET STRING -- INPUT point s to here : 30 04 1: OCTET STRING : 80 [...] 04 2: OCTET STRING : 00 00 : } -- This denotes a Null tag and are the last -- two bytes in INPUT. Create a new buffer with the content of that octet string. INPUT is the orginal buffer with a length as stored at LENGTH. Returns NULL on error or a new malloced buffer with the length of this new buffer stored at LENGTH and the number of bytes parsed from input are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is allowed to be passed as NULL if the caller is not interested in this value. */ static unsigned char * cram_octet_string (const unsigned char *input, size_t *length, size_t *input_consumed) { const unsigned char *s = input; size_t n = *length; unsigned char *output, *d; struct tag_info ti; /* Allocate output buf. We know that it won't be longer than the input buffer. */ d = output = gcry_malloc (n); if (!output) goto bailout; for (;;) { if (parse_tag (&s, &n, &ti)) goto bailout; if (ti.class == UNIVERSAL && ti.tag == TAG_OCTET_STRING && !ti.ndef && !ti.is_constructed) { memcpy (d, s, ti.length); s += ti.length; d += ti.length; n -= ti.length; } else if (ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) break; /* Ready */ else goto bailout; } *length = d - output; if (input_consumed) *input_consumed += s - input; return output; bailout: if (input_consumed) *input_consumed += s - input; gcry_free (output); return NULL; } static int string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, int req_keylen, unsigned char *keybuf) { int rc, i, j; gcry_md_hd_t md; gcry_mpi_t num_b1 = NULL; int pwlen; unsigned char hash[20], buf_b[64], buf_i[128], *p; size_t cur_keylen; size_t n; cur_keylen = 0; pwlen = strlen (pw); if (pwlen > 63/2) { log_error ("password too long\n"); return -1; } if (saltlen < 8) { log_error ("salt too short\n"); return -1; } /* Store salt and password in BUF_I */ p = buf_i; for(i=0; i < 64; i++) *p++ = salt [i%saltlen]; for(i=j=0; i < 64; i += 2) { *p++ = 0; *p++ = pw[j]; if (++j > pwlen) /* Note, that we include the trailing zero */ j = 0; } for (;;) { rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (rc) { log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc)); return rc; } for(i=0; i < 64; i++) gcry_md_putc (md, id); gcry_md_write (md, buf_i, 128); memcpy (hash, gcry_md_read (md, 0), 20); gcry_md_close (md); for (i=1; i < iter; i++) gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20); for (i=0; i < 20 && cur_keylen < req_keylen; i++) keybuf[cur_keylen++] = hash[i]; if (cur_keylen == req_keylen) { gcry_mpi_release (num_b1); return 0; /* ready */ } /* need more bytes. */ for(i=0; i < 64; i++) buf_b[i] = hash[i % 20]; rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n); if (rc) { log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc)); return -1; } gcry_mpi_add_ui (num_b1, num_b1, 1); for (i=0; i < 128; i += 64) { gcry_mpi_t num_ij; rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n); if (rc) { log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc)); return -1; } gcry_mpi_add (num_ij, num_ij, num_b1); gcry_mpi_clear_highbit (num_ij, 64*8); rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij); if (rc) { log_error ( "gcry_mpi_print failed: %s\n", gpg_strerror (rc)); return -1; } gcry_mpi_release (num_ij); } } } static int set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, const char *pw, int keybytes) { unsigned char keybuf[24]; int rc; assert (keybytes == 5 || keybytes == 24); if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf)) return -1; rc = gcry_cipher_setkey (chd, keybuf, keybytes); if (rc) { log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc)); return -1; } if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf)) return -1; rc = gcry_cipher_setiv (chd, keybuf, 8); if (rc) { log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc)); return -1; } return 0; } static void crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, int iter, const char *pw, int cipher_algo, int encrypt) { gcry_cipher_hd_t chd; int rc; rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0); if (rc) { log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc)); wipememory (buffer, length); return; } if (set_key_iv (chd, salt, saltlen, iter, pw, cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) { wipememory (buffer, length); goto leave; } rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0) : gcry_cipher_decrypt (chd, buffer, length, NULL, 0); if (rc) { wipememory (buffer, length); log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc)); goto leave; } leave: gcry_cipher_close (chd); } static int parse_bag_encrypted_data (const unsigned char *buffer, size_t length, int startoffset, size_t *r_consumed, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), void *certcbarg) { struct tag_info ti; const unsigned char *p = buffer; const unsigned char *p_start = buffer; size_t n = length; const char *where; char salt[16]; size_t saltlen; unsigned int iter; unsigned char *plain = NULL; int bad_pass = 0; unsigned char *cram_buffer = NULL; size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ 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); where = "bag.encryptedData.keyinfo"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_SEQUENCE) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (!ti.class && ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC) && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC, DIM(oid_pbeWithSHAAnd40BitRC2_CBC))) { p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC); n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC); } else goto bailout; where = "rc2-params"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_SEQUENCE) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OCTET_STRING || ti.length < 8 || ti.length > 16 ) goto bailout; saltlen = ti.length; memcpy (salt, p, saltlen); p += saltlen; n -= saltlen; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) goto bailout; for (iter=0; ti.length; ti.length--) { iter <<= 8; iter |= (*p++) & 0xff; n--; } where = "rc2-ciphertext"; if (parse_tag (&p, &n, &ti)) goto bailout; consumed = p - p_start; if (ti.class == CONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of octect strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-rc2-ciphertext"; cram_buffer = cram_octet_string ( p, &n, &consumed); if (!cram_buffer) goto bailout; p = p_start = cram_buffer; if (r_consumed) *r_consumed = consumed; r_consumed = NULL; /* Ugly hack to not update that value any further. */ ti.length = n; } else if (ti.class == CONTEXT && ti.tag == 0 && ti.length ) ; else goto bailout; log_info ("%lu bytes of RC2 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, saltlen, iter, pw, GCRY_CIPHER_RFC2268_40, 0); n = ti.length; startoffset = 0; p_start = p = plain; /* { */ /* # warning debug code is enabled */ /* FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */ /* if (!fp || fwrite (p, n, 1, fp) != 1) */ /* exit (2); */ /* fclose (fp); */ /* } */ where = "outer.outer.seq"; if (parse_tag (&p, &n, &ti)) { bad_pass = 1; goto bailout; } if (ti.class || ti.tag != TAG_SEQUENCE) { bad_pass = 1; goto bailout; } if (parse_tag (&p, &n, &ti)) { bad_pass = 1; goto bailout; } /* Loop over all certificates inside the bag. */ while (n) { int isbag = 0; where = "certbag.nextcert"; if (ti.class || ti.tag != TAG_SEQUENCE) goto bailout; where = "certbag.objectidentifier"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OBJECT_ID) goto bailout; if ( ti.length == DIM(oid_pkcs_12_CertBag) && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) { p += DIM(oid_pkcs_12_CertBag); n -= DIM(oid_pkcs_12_CertBag); } else if ( ti.length == DIM(oid_pkcs_12_CrlBag) && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag))) { p += DIM(oid_pkcs_12_CrlBag); n -= DIM(oid_pkcs_12_CrlBag); isbag = 1; } else goto bailout; where = "certbag.before.certheader"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != CONTEXT || ti.tag) goto bailout; if (isbag) { log_info ("skipping unsupported crlBag\n"); p += ti.length; n -= ti.length; } else { if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_SEQUENCE) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_x509Certificate_for_pkcs_12) || memcmp (p, oid_x509Certificate_for_pkcs_12, DIM(oid_x509Certificate_for_pkcs_12))) goto bailout; p += DIM(oid_x509Certificate_for_pkcs_12); n -= DIM(oid_x509Certificate_for_pkcs_12); where = "certbag.before.octetstring"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != CONTEXT || ti.tag) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) goto bailout; /* Return the certificate. */ if (certcb) certcb (certcbarg, p, ti.length); p += ti.length; n -= ti.length; } /* Ugly hack to cope with the padding: Forget about the rest if that is less or equal to the cipher's block length. We can reasonable assume that all valid data will be longer than just one block. */ if (n <= 8) n = 0; /* Skip the optional SET with the pkcs12 cert attributes. */ if (n) { where = "bag.attributes"; if (parse_tag (&p, &n, &ti)) goto bailout; if (!ti.class && ti.tag == TAG_SEQUENCE) ; /* No attributes. */ else if (!ti.class && ti.tag == TAG_SET && !ti.ndef) { /* The optional SET. */ p += ti.length; n -= ti.length; if (n <= 8) n = 0; if (n && parse_tag (&p, &n, &ti)) goto bailout; } else goto bailout; } } if (r_consumed) *r_consumed = consumed; gcry_free (plain); gcry_free (cram_buffer); return 0; bailout: if (r_consumed) *r_consumed = consumed; gcry_free (plain); gcry_free (cram_buffer); log_error ("encryptedData error at \"%s\", offset %u\n", where, (p - p_start)+startoffset); if (bad_pass) { /* Note, that the following string might be used by other programs to check for a bad passphrase; it should therefore not be translated or changed. */ log_error ("possibly bad passphrase given\n"); } return -1; } static gcry_mpi_t * parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, size_t *r_consumed, const char *pw) { int rc; struct tag_info ti; const unsigned char *p = buffer; const unsigned char *p_start = buffer; size_t n = length; const char *where; char salt[16]; size_t saltlen; unsigned int iter; int len; unsigned char *plain = NULL; gcry_mpi_t *result = NULL; int result_count, i; unsigned char *cram_buffer = NULL; size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ where = "start"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != CONTEXT || ti.tag) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OCTET_STRING) goto bailout; consumed = p - p_start; if (ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of octect strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-data.outersegs"; cram_buffer = cram_octet_string ( p, &n, &consumed); if (!cram_buffer) goto bailout; p = p_start = cram_buffer; if (r_consumed) *r_consumed = consumed; r_consumed = NULL; /* Ugly hack to not update that value any further. */ } where = "data.outerseqs"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_SEQUENCE) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_SEQUENCE) goto bailout; where = "data.objectidentifier"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) goto bailout; p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); where = "shrouded,outerseqs"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != 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 || ti.length > 16) goto bailout; saltlen = ti.length; memcpy (salt, p, saltlen); p += saltlen; n -= saltlen; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) goto bailout; for (iter=0; ti.length; ti.length--) { iter <<= 8; iter |= (*p++) & 0xff; n--; } where = "3des-ciphertext"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) goto bailout; log_info ("%lu bytes of 3DES encrypted text\n", ti.length); plain = gcry_malloc_secure (ti.length); if (!plain) { log_error ("error allocating decryption buffer\n"); goto bailout; } memcpy (plain, p, ti.length); consumed += p - p_start + ti.length; crypt_block (plain, ti.length, salt, saltlen, iter, pw, GCRY_CIPHER_3DES, 0); n = ti.length; startoffset = 0; p_start = p = plain; /* { */ /* # warning debug code is enabled */ /* FILE *fp = fopen ("tmp-rc2-plain-key.der", "wb"); */ /* if (!fp || fwrite (p, n, 1, fp) != 1) */ /* exit (2); */ /* fclose (fp); */ /* } */ where = "decrypted-text"; if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) goto bailout; if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER || ti.length != 1 || *p) goto bailout; p++; n--; if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) goto bailout; len = ti.length; if (parse_tag (&p, &n, &ti)) goto bailout; if (len < ti.nhdr) goto bailout; len -= ti.nhdr; if (ti.class || ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_rsaEncryption) || memcmp (p, oid_rsaEncryption, DIM(oid_rsaEncryption))) goto bailout; p += DIM (oid_rsaEncryption); n -= DIM (oid_rsaEncryption); if (len < ti.length) goto bailout; len -= ti.length; if (n < len) goto bailout; p += len; n -= len; if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) goto bailout; if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) goto bailout; len = ti.length; result = gcry_calloc (10, sizeof *result); if (!result) { log_error ( "error allocating result array\n"); goto bailout; } result_count = 0; where = "reading.key-parameters"; for (result_count=0; len && result_count < 9;) { if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) goto bailout; if (len < ti.nhdr) goto bailout; len -= ti.nhdr; if (len < ti.length) goto bailout; len -= ti.length; if (!result_count && ti.length == 1 && !*p) ; /* ignore the very first one if it is a 0 */ else { rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, ti.length, NULL); if (rc) { log_error ("error parsing key parameter: %s\n", gpg_strerror (rc)); goto bailout; } result_count++; } p += ti.length; n -= ti.length; } if (len) goto bailout; gcry_free (cram_buffer); if (r_consumed) *r_consumed = consumed; return result; bailout: gcry_free (plain); if (result) { for (i=0; result[i]; i++) gcry_mpi_release (result[i]); gcry_free (result); } gcry_free (cram_buffer); log_error ( "data error at \"%s\", offset %u\n", where, (p - buffer) + startoffset); if (r_consumed) *r_consumed = consumed; return NULL; } /* Parse a PKCS12 object and return an array of MPI representing the secret key parameters. This is a very limited implementation in that it is only able to look for 3DES encoded encryptedData and tries to extract the first private key object it finds. In case of an error NULL is returned. CERTCB and CERRTCBARG are used to pass X.509 certificates back to the caller. */ gcry_mpi_t * p12_parse (const unsigned char *buffer, size_t length, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), void *certcbarg) { struct tag_info ti; const unsigned char *p = buffer; const unsigned char *p_start = buffer; size_t n = length; const char *where; int bagseqlength, len; int bagseqndef, lenndef; gcry_mpi_t *result = NULL; unsigned char *cram_buffer = NULL; where = "pfx"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.tag != TAG_SEQUENCE) goto bailout; where = "pfxVersion"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3) goto bailout; p++; n--; where = "authSave"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.tag != TAG_SEQUENCE) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) || memcmp (p, oid_data, DIM(oid_data))) goto bailout; p += DIM(oid_data); n -= DIM(oid_data); if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != CONTEXT || ti.tag) goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING) goto bailout; if (ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of octect strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-bags"; cram_buffer = cram_octet_string ( p, &n, NULL); if (!cram_buffer) goto bailout; p = p_start = cram_buffer; } where = "bags"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) goto bailout; bagseqndef = ti.ndef; bagseqlength = ti.length; while (bagseqlength || bagseqndef) { log_debug ( "at offset %u\n", (p - p_start)); where = "bag-sequence"; if (parse_tag (&p, &n, &ti)) goto bailout; if (bagseqndef && ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) break; /* Ready */ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) goto bailout; if (!bagseqndef) { if (bagseqlength < ti.nhdr) goto bailout; bagseqlength -= ti.nhdr; if (bagseqlength < ti.length) goto bailout; bagseqlength -= ti.length; } lenndef = ti.ndef; len = ti.length; if (parse_tag (&p, &n, &ti)) goto bailout; if (lenndef) len = ti.nhdr; else len -= ti.nhdr; if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) { size_t consumed = 0; p += DIM(oid_encryptedData); n -= DIM(oid_encryptedData); if (!lenndef) len -= DIM(oid_encryptedData); where = "bag.encryptedData"; if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw, certcb, certcbarg)) goto bailout; if (lenndef) len += consumed; } else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) && !memcmp (p, oid_data, DIM(oid_data))) { if (result) { log_info ("already got an data object, skipping next one\n"); p += ti.length; n -= ti.length; } else { size_t consumed = 0; p += DIM(oid_data); n -= DIM(oid_data); if (!lenndef) len -= DIM(oid_data); result = parse_bag_data (p, n, (p - p_start), &consumed, pw); if (!result) goto bailout; if (lenndef) len += consumed; } } else { log_info ("unknown bag type - skipped\n"); p += ti.length; n -= ti.length; } if (len < 0 || len > n) goto bailout; p += len; n -= len; if (lenndef) { /* Need to skip the Null Tag. */ if (parse_tag (&p, &n, &ti)) goto bailout; if (!(ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)) goto bailout; } } gcry_free (cram_buffer); return result; bailout: log_error ("error at \"%s\", offset %u\n", where, (p - p_start)); /* fixme: need to release RESULT. */ gcry_free (cram_buffer); return NULL; } static size_t compute_tag_length (size_t n) { int needed = 0; if (n < 128) needed += 2; /* tag and one length byte */ else if (n < 256) needed += 3; /* tag, number of length bytes, 1 length byte */ else if (n < 65536) needed += 4; /* tag, number of length bytes, 2 length bytes */ else { log_error ("object too larger to encode\n"); return 0; } return needed; } static unsigned char * store_tag_length (unsigned char *p, int tag, size_t n) { if (tag == TAG_SEQUENCE) tag |= 0x20; /* constructed */ *p++ = tag; if (n < 128) *p++ = n; else if (n < 256) { *p++ = 0x81; *p++ = n; } else if (n < 65536) { *p++ = 0x82; *p++ = n >> 8; *p++ = n; } return p; } /* Create the final PKCS-12 object from the sequences contained in SEQLIST. PW is the password. That array is terminated with an NULL object. */ static unsigned char * create_final (struct buffer_s *sequences, const char *pw, size_t *r_length) { int i; size_t needed = 0; size_t len[8], n; unsigned char *macstart; size_t maclen; unsigned char *result, *p; size_t resultlen; char salt[8]; unsigned char keybuf[20]; gcry_md_hd_t md; int rc; /* 9 steps to create the pkcs#12 Krampf. */ /* 8. The MAC. */ /* We add this at step 0. */ /* 7. All the buffers. */ for (i=0; sequences[i].buffer; i++) needed += sequences[i].length; /* 6. This goes into a sequences. */ len[6] = needed; n = compute_tag_length (needed); needed += n; /* 5. Encapsulate all in an octet string. */ len[5] = needed; n = compute_tag_length (needed); needed += n; /* 4. And tag it with [0]. */ len[4] = needed; n = compute_tag_length (needed); needed += n; /* 3. Prepend an data OID. */ needed += 2 + DIM (oid_data); /* 2. Put all into a sequences. */ len[2] = needed; n = compute_tag_length (needed); needed += n; /* 1. Prepend the version integer 3. */ needed += 3; /* 0. And the final outer sequence. */ needed += DIM (data_mactemplate); len[0] = needed; n = compute_tag_length (needed); needed += n; /* Allocate a buffer. */ result = gcry_malloc (needed); if (!result) { log_error ("error allocating buffer\n"); return NULL; } p = result; /* 0. Store the very outer sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[0]); /* 1. Store the version integer 3. */ *p++ = TAG_INTEGER; *p++ = 1; *p++ = 3; /* 2. Store another sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[2]); /* 3. Store the data OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); memcpy (p, oid_data, DIM (oid_data)); p += DIM (oid_data); /* 4. Next comes a context tag. */ p = store_tag_length (p, 0xa0, len[4]); /* 5. And an octet string. */ p = store_tag_length (p, TAG_OCTET_STRING, len[5]); /* 6. And the inner sequence. */ macstart = p; p = store_tag_length (p, TAG_SEQUENCE, len[6]); /* 7. Append all the buffers. */ for (i=0; sequences[i].buffer; i++) { memcpy (p, sequences[i].buffer, sequences[i].length); p += sequences[i].length; } /* Intermezzo to compute the MAC. */ maclen = p - macstart; gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf)) { gcry_free (result); return NULL; } rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); if (rc) { log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc)); gcry_free (result); return NULL; } rc = gcry_md_setkey (md, keybuf, 20); if (rc) { log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); gcry_free (result); return NULL; } gcry_md_write (md, macstart, maclen); /* 8. Append the MAC template and fix it up. */ memcpy (p, data_mactemplate, DIM (data_mactemplate)); memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8); memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20); p += DIM (data_mactemplate); gcry_md_close (md); /* Ready. */ resultlen = p - result; if (needed != resultlen) log_debug ("length mismatch: %lu, %lu\n", (unsigned long)needed, (unsigned long)resultlen); *r_length = resultlen; return result; } /* Build a DER encoded SEQUENCE with the key: SEQUENCE { INTEGER 0 SEQUENCE { OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1) NULL } OCTET STRING, encapsulates { SEQUENCE { INTEGER 0 INTEGER INTEGER INTEGER INTEGER INTEGER INTEGER INTEGER INTEGER } } } */ static unsigned char * build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) { int rc, i; size_t needed, n; unsigned char *plain, *p; size_t plainlen; size_t outseqlen, oidseqlen, octstrlen, inseqlen; needed = 3; /* The version(?) integer of value 0. */ for (i=0; kparms[i]; i++) { n = 0; rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); if (rc) { log_error ("error formatting parameter: %s\n", gpg_strerror (rc)); return NULL; } needed += n; n = compute_tag_length (n); if (!n) return NULL; needed += n; } if (i != 8) { log_error ("invalid 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 (i=0; i < n; i++, plainlen++) *p++ = n; *r_length = plainlen; return plain; } static unsigned char * build_key_bag (unsigned char *buffer, size_t buflen, char *salt, size_t *r_length) { size_t len[11], needed; unsigned char *p, *keybag; size_t keybaglen; /* Walk 11 steps down to collect the info: */ /* 10. The data goes into an octet string. */ needed = compute_tag_length (buflen); needed += buflen; /* 9. Prepend the algorithm identifier. */ needed += DIM (data_3desiter2048); /* 8. Put a sequence around. */ len[8] = needed; needed += compute_tag_length (needed); /* 7. Prepend a [0] tag. */ len[7] = needed; needed += compute_tag_length (needed); /* 6. Prepend the shroudedKeyBag OID. */ needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); /* 5+4. Put all into two sequences. */ len[5] = needed; needed += compute_tag_length ( needed); len[4] = needed; needed += compute_tag_length (needed); /* 3. This all goes into an octet string. */ len[3] = needed; needed += compute_tag_length (needed); /* 2. Prepend another [0] tag. */ len[2] = needed; needed += compute_tag_length (needed); /* 1. Prepend the data OID. */ needed += 2 + DIM (oid_data); /* 0. Prepend another sequence. */ len[0] = needed; needed += compute_tag_length (needed); /* Now that we have all length information, allocate a buffer. */ p = keybag = gcry_malloc (needed); if (!keybag) { log_error ("error allocating buffer\n"); return NULL; } /* Walk 11 steps up to store the data. */ /* 0. Store the first sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[0]); /* 1. Store the data OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); memcpy (p, oid_data, DIM (oid_data)); p += DIM (oid_data); /* 2. Store a [0] tag. */ p = store_tag_length (p, 0xa0, len[2]); /* 3. And an octet string. */ p = store_tag_length (p, TAG_OCTET_STRING, len[3]); /* 4+5. Two sequences. */ p = store_tag_length (p, TAG_SEQUENCE, len[4]); p = store_tag_length (p, TAG_SEQUENCE, len[5]); /* 6. Store the shroudedKeyBag OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); /* 7. Store a [0] tag. */ p = store_tag_length (p, 0xa0, len[7]); /* 8. Store a sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[8]); /* 9. Now for the pre-encoded algorithm identifier and the salt. */ memcpy (p, data_3desiter2048, DIM (data_3desiter2048)); memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8); p += DIM (data_3desiter2048); /* 10. And finally the octet string with the encrypted data. */ p = store_tag_length (p, TAG_OCTET_STRING, buflen); memcpy (p, buffer, buflen); p += buflen; keybaglen = p - keybag; if (needed != keybaglen) log_debug ("length mismatch: %lu, %lu\n", (unsigned long)needed, (unsigned long)keybaglen); *r_length = keybaglen; return keybag; } static unsigned char * build_cert_bag (unsigned char *buffer, size_t buflen, char *salt, size_t *r_length) { size_t len[9], needed; unsigned char *p, *certbag; size_t certbaglen; /* Walk 9 steps down to collect the info: */ /* 8. The data goes into an octet string. */ needed = compute_tag_length (buflen); needed += buflen; /* 7. The algorithm identifier. */ needed += DIM (data_rc2iter2048); /* 6. The data OID. */ needed += 2 + DIM (oid_data); /* 5. A sequence. */ len[5] = needed; needed += compute_tag_length ( needed); /* 4. An integer. */ needed += 3; /* 3. A sequence. */ len[3] = needed; needed += compute_tag_length (needed); /* 2. A [0] tag. */ len[2] = needed; needed += compute_tag_length (needed); /* 1. The encryptedData OID. */ needed += 2 + DIM (oid_encryptedData); /* 0. The first sequence. */ len[0] = needed; needed += compute_tag_length (needed); /* Now that we have all length information, allocate a buffer. */ p = certbag = gcry_malloc (needed); if (!certbag) { log_error ("error allocating buffer\n"); return NULL; } /* Walk 9 steps up to store the data. */ /* 0. Store the first sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[0]); /* 1. Store the encryptedData OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData)); memcpy (p, oid_encryptedData, DIM (oid_encryptedData)); p += DIM (oid_encryptedData); /* 2. Store a [0] tag. */ p = store_tag_length (p, 0xa0, len[2]); /* 3. Store a sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[3]); /* 4. Store the integer 0. */ *p++ = TAG_INTEGER; *p++ = 1; *p++ = 0; /* 5. Store a sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[5]); /* 6. Store the data OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); memcpy (p, oid_data, DIM (oid_data)); p += DIM (oid_data); /* 7. Now for the pre-encoded algorithm identifier and the salt. */ memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048)); memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8); p += DIM (data_rc2iter2048); /* 8. And finally the [0] tag with the encrypted data. */ p = store_tag_length (p, 0x80, buflen); memcpy (p, buffer, buflen); p += buflen; certbaglen = p - certbag; if (needed != certbaglen) log_debug ("length mismatch: %lu, %lu\n", (unsigned long)needed, (unsigned long)certbaglen); *r_length = certbaglen; return certbag; } static unsigned char * build_cert_sequence (unsigned char *buffer, size_t buflen, size_t *r_length) { size_t len[8], needed, n; unsigned char *p, *certseq; size_t certseqlen; int i; /* Walk 8 steps down to collect the info: */ /* 7. The data goes into an octet string. */ needed = compute_tag_length (buflen); needed += buflen; /* 6. A [0] tag. */ len[6] = needed; needed += compute_tag_length (needed); /* 5. An OID. */ needed += 2 + DIM (oid_x509Certificate_for_pkcs_12); /* 4. A sequence. */ len[4] = needed; needed += compute_tag_length (needed); /* 3. A [0] tag. */ len[3] = needed; needed += compute_tag_length (needed); /* 2. An OID. */ needed += 2 + DIM (oid_pkcs_12_CertBag); /* 1. A sequence. */ len[1] = needed; needed += compute_tag_length (needed); /* 0. The first sequence. */ len[0] = needed; needed += compute_tag_length (needed); /* Now that we have all length information, allocate a buffer. */ p = certseq = gcry_malloc (needed + 8 /*(for padding)*/); if (!certseq) { log_error ("error allocating buffer\n"); return NULL; } /* Walk 8 steps up to store the data. */ /* 0. Store the first sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[0]); /* 1. Store the second sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[1]); /* 2. Store the pkcs12-cert-bag OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag)); memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag)); p += DIM (oid_pkcs_12_CertBag); /* 3. Store a [0] tag. */ p = store_tag_length (p, 0xa0, len[3]); /* 4. Store a sequence. */ p = store_tag_length (p, TAG_SEQUENCE, len[4]); /* 5. Store the x509Certificate OID. */ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_x509Certificate_for_pkcs_12)); memcpy (p, oid_x509Certificate_for_pkcs_12, DIM (oid_x509Certificate_for_pkcs_12)); p += DIM (oid_x509Certificate_for_pkcs_12); /* 6. Store a [0] tag. */ p = store_tag_length (p, 0xa0, len[6]); /* 7. And finally the octet string with the actual certificate. */ p = store_tag_length (p, TAG_OCTET_STRING, buflen); memcpy (p, buffer, buflen); p += buflen; certseqlen = p - certseq; if (needed != certseqlen) log_debug ("length mismatch: %lu, %lu\n", (unsigned long)needed, (unsigned long)certseqlen); /* Append some pad characters; we already allocated extra space. */ n = 8 - certseqlen % 8; for (i=0; i < n; i++, certseqlen++) *p++ = n; *r_length = certseqlen; return certseq; } /* Expect the RSA key parameters in KPARMS and a password in PW. Create a PKCS structure from it and return it as well as the length in R_LENGTH; return NULL in case of an error. */ unsigned char * p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, const char *pw, size_t *r_length) { unsigned char *buffer; size_t n, buflen; char salt[8]; struct buffer_s seqlist[3]; int seqlistidx = 0; n = buflen = 0; /* (avoid compiler warning). */ if (cert && certlen) { /* Encode the certificate. */ buffer = build_cert_sequence (cert, certlen, &buflen); if (!buffer) goto failure; /* Encrypt it. */ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_RFC2268_40, 1); /* Encode the encrypted stuff into a bag. */ seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n); seqlist[seqlistidx].length = n; gcry_free (buffer); buffer = NULL; if (!seqlist[seqlistidx].buffer) goto failure; seqlistidx++; } if (kparms) { /* Encode the key. */ buffer = build_key_sequence (kparms, &buflen); if (!buffer) goto failure; /* Encrypt it. */ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1); /* Encode the encrypted stuff into a bag. */ seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, &n); seqlist[seqlistidx].length = n; gcry_free (buffer); buffer = NULL; if (!seqlist[seqlistidx].buffer) goto failure; seqlistidx++; } seqlist[seqlistidx].buffer = NULL; seqlist[seqlistidx].length = 0; buffer = create_final (seqlist, pw, &buflen); failure: for ( ; seqlistidx; seqlistidx--) gcry_free (seqlist[seqlistidx].buffer); *r_length = buffer? buflen : 0; return buffer; } #ifdef TEST static void cert_cb (void *opaque, const unsigned char *cert, size_t certlen) { printf ("got a certificate of %u bytes length\n", certlen); } int main (int argc, char **argv) { FILE *fp; struct stat st; unsigned char *buf; size_t buflen; gcry_mpi_t *result; if (argc != 3) { fprintf (stderr, "usage: testp12 file passphrase\n"); return 1; } gcry_control (GCRYCTL_DISABLE_SECMEM, NULL); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL); fp = fopen (argv[1], "rb"); if (!fp) { fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno)); return 1; } if (fstat (fileno(fp), &st)) { fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno)); return 1; } buflen = st.st_size; buf = gcry_malloc (buflen+1); if (!buf || fread (buf, buflen, 1, fp) != 1) { fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno)); return 1; } fclose (fp); result = p12_parse (buf, buflen, argv[2], cert_cb, NULL); if (result) { int i, rc; unsigned char *tmpbuf; for (i=0; result[i]; i++) { rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); if (rc) printf ("%d: [error printing number: %s]\n", i, gpg_strerror (rc)); else { printf ("%d: %s\n", i, tmpbuf); gcry_free (tmpbuf); } } } return 0; } /* Local Variables: compile-command: "gcc -Wall -O -g -DTEST=1 -o minip12 minip12.c ../jnlib/libjnlib.a -L /usr/local/lib -lgcrypt -lgpg-error" End: */ #endif /* TEST */ diff --git a/agent/minip12.h b/agent/minip12.h index 2fbb490d7..6275f9ccb 100644 --- a/agent/minip12.h +++ b/agent/minip12.h @@ -1,36 +1,37 @@ /* 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef MINIP12_H #define MINIP12_H #include gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), void *certcbarg); unsigned char *p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, const char *pw, size_t *r_length); #endif /*MINIP12_H*/ diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 1d64c1b15..f61f0f844 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -1,141 +1,142 @@ /* 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #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 *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf) { gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; unsigned char *shadow_info = NULL; int rc; char *buf = NULL; size_t len; if (!ctrl->have_keygrip) { log_error ("speculative decryption not yet supported\n"); rc = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } rc = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen); if (rc) { log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_DATA); goto leave; } if (DBG_CRYPTO) { log_printhex ("keygrip:", ctrl->keygrip, 20); log_printhex ("cipher: ", ciphertext, ciphertextlen); } rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip, &shadow_info, CACHE_MODE_NORMAL, &s_skey); if (rc) { log_error ("failed to read the secret key\n"); goto leave; } if (!s_skey) { /* divert operation to the smartcard */ if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL)) { rc = gpg_error (GPG_ERR_INV_SEXP); goto leave; } rc = divert_pkdecrypt (ctrl, ciphertext, shadow_info, &buf, &len ); if (rc) { log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); goto leave; } /* FIXME: Change the protocol to return a complete S-expression and not just a part. */ { char tmpbuf[50]; sprintf (tmpbuf, "%u:", (unsigned int)len); put_membuf (outbuf, tmpbuf, strlen (tmpbuf)); put_membuf (outbuf, buf, len); put_membuf (outbuf, "", 1); } } else { /* No smartcard, but a private key */ /* if (DBG_CRYPTO ) */ /* { */ /* log_debug ("skey: "); */ /* gcry_sexp_dump (s_skey); */ /* } */ rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey); if (rc) { log_error ("decryption failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) { log_debug ("plain: "); gcry_sexp_dump (s_plain); } len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xmalloc (len); len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len); assert (len); put_membuf (outbuf, buf, len); } 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 index e9df19351..9863f9de0 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -1,204 +1,205 @@ /* pksign.c - public key signing (well, actually using a secret key) * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "agent.h" static int do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, int raw_value) { gcry_sexp_t hash; int rc; if (! raw_value) { const char *s; char tmp[16+1]; int i; s = gcry_md_algo_name (algo); if (s && strlen (s) < 16) { for (i=0; i < strlen (s); i++) tmp[i] = tolower (s[i]); tmp[i] = '\0'; } rc = gcry_sexp_build (&hash, NULL, "(data (flags pkcs1) (hash %s %b))", tmp, mdlen, md); } else { gcry_mpi_t mpi; rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL); if (! rc) { rc = gcry_sexp_build (&hash, NULL, "(data (flags raw) (value %m))", mpi); gcry_mpi_release (mpi); } } *r_hash = hash; return rc; } /* SIGN whatever information we have accumulated in CTRL and return the signature S-Expression. */ int agent_pksign_do (ctrl_t ctrl, const char *desc_text, gcry_sexp_t *signature_sexp, cache_mode_t cache_mode) { gcry_sexp_t s_skey = NULL, s_sig = NULL; unsigned char *shadow_info = NULL; unsigned int rc = 0; /* FIXME: gpg-error? */ if (! ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip, &shadow_info, cache_mode, &s_skey); if (rc) { log_error ("failed to read the secret key\n"); goto leave; } if (!s_skey) { /* Divert operation to the smartcard */ unsigned char *buf = NULL; size_t len = 0; rc = divert_pksign (ctrl, ctrl->digest.value, ctrl->digest.valuelen, ctrl->digest.algo, shadow_info, &buf); if (rc) { log_error ("smartcard signing failed: %s\n", gpg_strerror (rc)); goto leave; } len = gcry_sexp_canon_len (buf, 0, NULL, NULL); assert (len); rc = gcry_sexp_sscan (&s_sig, NULL, (char*)buf, len); xfree (buf); if (rc) { log_error ("failed to convert sigbuf returned by divert_pksign " "into S-Exp: %s", gpg_strerror (rc)); goto leave; } } else { /* No smartcard, but a private key */ gcry_sexp_t s_hash = NULL; /* put the hash into a sexp */ rc = do_encode_md (ctrl->digest.value, ctrl->digest.valuelen, ctrl->digest.algo, &s_hash, ctrl->digest.raw_value); if (rc) goto leave; if (DBG_CRYPTO) { log_debug ("skey: "); gcry_sexp_dump (s_skey); } /* sign */ rc = gcry_pk_sign (&s_sig, s_hash, s_skey); gcry_sexp_release (s_hash); if (rc) { log_error ("signing failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) { log_debug ("result: "); gcry_sexp_dump (s_sig); } } leave: *signature_sexp = s_sig; gcry_sexp_release (s_skey); xfree (shadow_info); return rc; } /* SIGN whatever information we have accumulated in CTRL and write it back to OUTFP. */ int agent_pksign (ctrl_t ctrl, const char *desc_text, membuf_t *outbuf, cache_mode_t cache_mode) { gcry_sexp_t s_sig = NULL; char *buf = NULL; size_t len = 0; int rc = 0; rc = agent_pksign_do (ctrl, desc_text, &s_sig, cache_mode); if (rc) goto leave; len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xmalloc (len); len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); assert (len); put_membuf (outbuf, buf, len); leave: gcry_sexp_release (s_sig); xfree (buf); return rc; } diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c index 6a9f07a3e..013c9411d 100644 --- a/agent/preset-passphrase.c +++ b/agent/preset-passphrase.c @@ -1,293 +1,294 @@ /* preset-passphrase.c - A tool to preset a passphrase. * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #ifdef HAVE_W32_SYSTEM #include /* To initialize the sockets. fixme */ #endif #define JNLIB_NEED_LOG_LOGV #include "agent.h" #include "minip12.h" #include "simple-pwquery.h" #include "i18n.h" #include "sysutils.h" enum cmd_and_opt_values { aNull = 0, oVerbose = 'v', oPassphrase = 'P', oPreset = 'c', oForget = 'f', oNoVerbose = 500, oHomedir, aTest }; static const char *opt_homedir; static const char *opt_passphrase; static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@Options:\n ") }, { oVerbose, "verbose", 0, "verbose" }, { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" }, { oPreset, "preset", 256, "preset passphrase"}, { oForget, "forget", 256, "forget passphrase"}, { oHomedir, "homedir", 2, "@" }, {0} }; static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "gpg-preset-passphrase (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); break; case 1: case 40: p = _("Usage: gpg-preset-passphrase [options] KEYGRIP (-h for help)\n"); break; case 41: p = _("Syntax: gpg-preset-passphrase [options] KEYGRIP\n" "Password cache maintenance\n"); break; default: p = NULL; } return p; } static void i18n_init (void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file( PACKAGE_GT ); #else #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); #endif #endif } static gpg_error_t map_spwq_error (int err) { switch (err) { case 0: return 0; case SPWQ_OUT_OF_CORE: return gpg_error_from_errno (ENOMEM); case SPWQ_IO_ERROR: return gpg_error_from_errno (EIO); case SPWQ_PROTOCOL_ERROR: return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); case SPWQ_ERR_RESPONSE: return gpg_error (GPG_ERR_INV_RESPONSE); case SPWQ_NO_AGENT: return gpg_error (GPG_ERR_NO_AGENT); case SPWQ_SYS_ERROR: return gpg_error_from_errno (errno); case SPWQ_GENERAL_ERROR: default: return gpg_error (GPG_ERR_GENERAL); } } static void preset_passphrase (const char *keygrip) { int rc; char *line; /* FIXME: Use secure memory. */ char passphrase[500]; if (!opt_passphrase) { rc = read (0, passphrase, sizeof (passphrase) - 1); if (rc < 0) { log_error ("reading passphrase failed: %s\n", gpg_strerror (gpg_error_from_errno (errno))); return; } passphrase[rc] = '\0'; line = strchr (passphrase, '\n'); if (line) { line--; if (line > passphrase && line[-1] == '\r') line--; *line = '\0'; } /* FIXME: How to handle empty passwords? */ } rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip, opt_passphrase? opt_passphrase : passphrase); if (rc < 0) { log_error ("caching passphrase failed: %s\n", gpg_strerror (gpg_error_from_errno (errno))); return; } if (!opt_passphrase) wipememory (passphrase, sizeof (passphrase)); rc = map_spwq_error (simple_query (line)); if (rc) { log_error ("caching passphrase failed: %s\n", gpg_strerror (rc)); return; } wipememory (line, strlen (line)); free (line); } static void forget_passphrase (const char *keygrip) { int rc; char *line; rc = asprintf (&line, "CLEAR_PASSPHRASE %s\n", keygrip); if (rc < 0) { log_error ("clearing passphrase failed: %s\n", gpg_strerror (gpg_error_from_errno (errno))); return; } free (line); } int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int cmd = 0; const char *keygrip = NULL; set_strusage (my_strusage); log_set_prefix ("gpg-preset-passphrase", 1); /* Try to auto set the character set. */ set_native_charset (NULL); #ifdef HAVE_W32_SYSTEM /* Fixme: Need to initialize the Windows sockets: This should be moved to another place and we should make sure that it won't get doen twice, like when Pth is used too. */ { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif i18n_init (); opt_homedir = default_homedir (); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* (do not remove the args) */ while (arg_parse (&pargs, opts) ) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oHomedir: opt_homedir = pargs.r.ret_str; break; case oPreset: cmd = oPreset; break; case oForget: cmd = oForget; break; case oPassphrase: opt_passphrase = pargs.r.ret_str; break; default : pargs.err = 2; break; } } if (log_get_errorcount(0)) exit(2); if (argc == 1) keygrip = *argv; else usage (1); if (cmd == oPreset) preset_passphrase (keygrip); else if (cmd == oForget) forget_passphrase (keygrip); else log_error ("one of the options --preset or --forget must be given\n"); agent_exit (0); return 8; /*NOTREACHED*/ } void agent_exit (int rc) { rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 5f59d5e06..bb14ca1e1 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -1,1355 +1,1356 @@ /* protect-tool.c - A tool to test the secret key protection * Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #define JNLIB_NEED_LOG_LOGV #include "agent.h" #include "minip12.h" #include "simple-pwquery.h" #include "i18n.h" #include "sysutils.h" enum cmd_and_opt_values { aNull = 0, oVerbose = 'v', oArmor = 'a', oPassphrase = 'P', oProtect = 'p', oUnprotect = 'u', oNoVerbose = 500, oShadow, oShowShadowInfo, oShowKeygrip, oCanonical, oP12Import, oP12Export, oStore, oForce, oHaveCert, oNoFailOnExist, oHomedir, oPrompt, oStatusMsg, 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 const char *opt_homedir; static int opt_armor; static int opt_canonical; static int opt_store; static int opt_force; static int opt_no_fail_on_exist; static int opt_have_cert; static const char *opt_passphrase; static char *opt_prompt; static int opt_status_msg; static char *get_passphrase (int promptno); static char *get_new_passphrase (int promptno); static void release_passphrase (char *pw); static int store_private_key (const unsigned char *grip, const void *buffer, size_t length, int force); static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@Options:\n ") }, { oVerbose, "verbose", 0, "verbose" }, { oArmor, "armor", 0, "write output in advanced format" }, { oCanonical, "canonical", 0, "write output in canonical 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 public 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"}, { oHaveCert, "have-cert", 0, "certificate to export provided on STDIN"}, { oStore, "store", 0, "store the created key in the appropriate place"}, { oForce, "force", 0, "force overwriting"}, { oNoFailOnExist, "no-fail-on-exist", 0, "@" }, { oHomedir, "homedir", 2, "@" }, { oPrompt, "prompt", 2, "|ESCSTRING|use ESCSTRING as prompt in pinentry"}, { oStatusMsg, "enable-status-msg", 0, "@"}, {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_GT ); #else #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); #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); */ /* } */ /* } */ static unsigned char * make_canonical (const char *fname, const char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; unsigned char *result; rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); if (rc) { log_error ("invalid S-Expression in `%s' (off=%u): %s\n", fname, (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len); assert (len); gcry_sexp_release (sexp); return result; } static char * make_advanced (const unsigned char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; char *result; rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen); if (rc) { log_error ("invalid canonical S-Expression (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); assert (len); gcry_sexp_release (sexp); return result; } static char * read_file (const char *fname, size_t *r_length) { FILE *fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; fp = stdin; #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(fp) , O_BINARY ); #endif buf = NULL; buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); nread = fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && ferror (fp)) { log_error ("error reading `[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; fp = fopen (fname, "rb"); if (!fp) { log_error ("can't open `%s': %s\n", fname, strerror (errno)); return NULL; } if (fstat (fileno(fp), &st)) { log_error ("can't stat `%s': %s\n", fname, strerror (errno)); fclose (fp); return NULL; } buflen = st.st_size; buf = xmalloc (buflen+1); if (fread (buf, buflen, 1, fp) != 1) { log_error ("error reading `%s': %s\n", fname, strerror (errno)); fclose (fp); xfree (buf); return NULL; } fclose (fp); } *r_length = buflen; return buf; } static unsigned char * read_key (const char *fname) { char *buf; size_t buflen; unsigned char *key; buf = read_file (fname, &buflen); if (!buf) return NULL; key = make_canonical (fname, buf, buflen); xfree (buf); return key; } static void read_and_protect (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; key = read_key (fname); if (!key) return; pw = get_passphrase (1); rc = agent_protect (key, pw, &result, &resultlen); release_passphrase (pw); xfree (key); if (rc) { log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_unprotect (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; key = read_key (fname); if (!key) return; rc = agent_unprotect (key, (pw=get_passphrase (1)), &result, &resultlen); release_passphrase (pw); xfree (key); if (rc) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] bad-passphrase\n"); log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_shadow (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; unsigned char dummy_info[] = "(8:313233342:43)"; key = read_key (fname); if (!key) return; rc = agent_shadow_key (key, dummy_info, &result); xfree (key); if (rc) { log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); return; } resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL); assert (resultlen); if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void show_shadow_info (const char *fname) { int rc; unsigned char *key; const unsigned char *info; size_t infolen; key = read_key (fname); if (!key) return; rc = agent_get_shadow_info (key, &info); xfree (key); if (rc) { log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); return; } infolen = gcry_sexp_canon_len (info, 0, NULL,NULL); assert (infolen); if (opt_armor) { char *p = make_advanced (info, infolen); if (!p) return; fwrite (p, strlen (p), 1, stdout); xfree (p); } else fwrite (info, infolen, 1, stdout); } static void show_file (const char *fname) { unsigned char *key; size_t keylen; char *p; key = read_key (fname); if (!key) return; keylen = gcry_sexp_canon_len (key, 0, NULL,NULL); assert (keylen); if (opt_canonical) { fwrite (key, keylen, 1, stdout); } else { p = make_advanced (key, keylen); if (p) { fwrite (p, strlen (p), 1, stdout); xfree (p); } } xfree (key); } static void show_keygrip (const char *fname) { unsigned char *key; gcry_sexp_t private; unsigned char grip[20]; int i; key = read_key (fname); if (!key) return; if (gcry_sexp_new (&private, key, 0, 0)) { log_error ("gcry_sexp_new failed\n"); return; } xfree (key); if (!gcry_pk_get_keygrip (private, grip)) { log_error ("can't calculate keygrip\n"); return; } gcry_sexp_release (private); for (i=0; i < 20; i++) printf ("%02X", grip[i]); putchar ('\n'); } static int rsa_key_check (struct rsa_secret_key_s *skey) { int err = 0; gcry_mpi_t t = gcry_mpi_snew (0); gcry_mpi_t t1 = gcry_mpi_snew (0); gcry_mpi_t t2 = gcry_mpi_snew (0); gcry_mpi_t phi = gcry_mpi_snew (0); /* check that n == p * q */ gcry_mpi_mul (t, skey->p, skey->q); if (gcry_mpi_cmp( t, skey->n) ) { log_error ("RSA oops: n != p * q\n"); err++; } /* check that p is less than q */ if (gcry_mpi_cmp (skey->p, skey->q) > 0) { gcry_mpi_t tmp; log_info ("swapping secret primes\n"); tmp = gcry_mpi_copy (skey->p); gcry_mpi_set (skey->p, skey->q); gcry_mpi_set (skey->q, tmp); gcry_mpi_release (tmp); /* and must recompute u of course */ gcry_mpi_invm (skey->u, skey->p, skey->q); } /* check that e divides neither p-1 nor q-1 */ gcry_mpi_sub_ui (t, skey->p, 1 ); gcry_mpi_div (NULL, t, t, skey->e, 0); if (!gcry_mpi_cmp_ui( t, 0) ) { log_error ("RSA oops: e divides p-1\n"); err++; } gcry_mpi_sub_ui (t, skey->q, 1); gcry_mpi_div (NULL, t, t, skey->e, 0); if (!gcry_mpi_cmp_ui( t, 0)) { log_info ( "RSA oops: e divides q-1\n" ); err++; } /* check that d is correct. */ gcry_mpi_sub_ui (t1, skey->p, 1); gcry_mpi_sub_ui (t2, skey->q, 1); gcry_mpi_mul (phi, t1, t2); gcry_mpi_invm (t, skey->e, phi); if (gcry_mpi_cmp (t, skey->d)) { /* no: try universal exponent. */ gcry_mpi_gcd (t, t1, t2); gcry_mpi_div (t, NULL, phi, t, 0); gcry_mpi_invm (t, skey->e, t); if (gcry_mpi_cmp (t, skey->d)) { log_error ("RSA oops: bad secret exponent\n"); err++; } } /* check for correctness of u */ gcry_mpi_invm (t, skey->p, skey->q); if (gcry_mpi_cmp (t, skey->u)) { log_info ( "RSA oops: bad u parameter\n"); err++; } if (err) log_info ("RSA secret key check failed\n"); gcry_mpi_release (t); gcry_mpi_release (t1); gcry_mpi_release (t2); gcry_mpi_release (phi); return err? -1:0; } /* A callback used by p12_parse to return a certificate. */ static void import_p12_cert_cb (void *opaque, const unsigned char *cert, size_t certlen) { struct b64state state; gpg_error_t err, err2; err = b64enc_start (&state, stdout, "CERTIFICATE"); if (!err) err = b64enc_write (&state, cert, certlen); err2 = b64enc_finish (&state); if (!err) err = err2; if (err) log_error ("error writing armored certificate: %s\n", gpg_strerror (err)); } static void import_p12_file (const char *fname) { char *buf; unsigned char *result; size_t buflen, resultlen; int i; int rc; gcry_mpi_t *kparms; struct rsa_secret_key_s sk; gcry_sexp_t s_key; unsigned char *key; unsigned char grip[20]; char *pw; /* fixme: we should release some stuff on error */ buf = read_file (fname, &buflen); if (!buf) return; kparms = p12_parse ((unsigned char*)buf, buflen, (pw=get_passphrase (2)), import_p12_cert_cb, NULL); release_passphrase (pw); xfree (buf); if (!kparms) { log_error ("error parsing or decrypting the PKCS-12 file\n"); return; } for (i=0; kparms[i]; i++) ; if (i != 8) { log_error ("invalid structure of private key\n"); return; } /* print_mpi (" n", kparms[0]); */ /* print_mpi (" e", kparms[1]); */ /* print_mpi (" d", kparms[2]); */ /* print_mpi (" p", kparms[3]); */ /* print_mpi (" q", kparms[4]); */ /* print_mpi ("dmp1", kparms[5]); */ /* print_mpi ("dmq1", kparms[6]); */ /* print_mpi (" u", kparms[7]); */ sk.n = kparms[0]; sk.e = kparms[1]; sk.d = kparms[2]; sk.q = kparms[3]; sk.p = kparms[4]; sk.u = kparms[7]; if (rsa_key_check (&sk)) return; /* print_mpi (" n", sk.n); */ /* print_mpi (" e", sk.e); */ /* print_mpi (" d", sk.d); */ /* print_mpi (" p", sk.p); */ /* print_mpi (" q", sk.q); */ /* print_mpi (" u", sk.u); */ /* Create an S-expresion from the parameters. */ rc = gcry_sexp_build (&s_key, NULL, "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL); for (i=0; i < 8; i++) gcry_mpi_release (kparms[i]); gcry_free (kparms); if (rc) { log_error ("failed to created S-expression from key: %s\n", gpg_strerror (rc)); return; } /* Compute the keygrip. */ if (!gcry_pk_get_keygrip (s_key, grip)) { log_error ("can't calculate keygrip\n"); return; } log_info ("keygrip: "); for (i=0; i < 20; i++) log_printf ("%02X", grip[i]); log_printf ("\n"); /* Convert to canonical encoding. */ buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, NULL, 0); assert (buflen); key = gcry_xmalloc_secure (buflen); buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, key, buflen); assert (buflen); gcry_sexp_release (s_key); rc = agent_protect (key, (pw=get_new_passphrase (4)), &result, &resultlen); release_passphrase (pw); xfree (key); if (rc) { log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } if (opt_store) store_private_key (grip, result, resultlen, opt_force); else fwrite (result, resultlen, 1, stdout); xfree (result); } static gcry_mpi_t * sexp_to_kparms (gcry_sexp_t sexp) { gcry_sexp_t list, l2; const char *name; const char *s; size_t n; int i, idx; const char *elems; gcry_mpi_t *array; list = gcry_sexp_find_token (sexp, "private-key", 0 ); if(!list) return NULL; l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; name = gcry_sexp_nth_data (list, 0, &n); if(!name || n != 3 || memcmp (name, "rsa", 3)) { gcry_sexp_release (list); return NULL; } /* Parameter names used with RSA. */ elems = "nedpqu"; array = xcalloc (strlen(elems) + 1, sizeof *array); for (idx=0, s=elems; *s; s++, idx++ ) { l2 = gcry_sexp_find_token (list, s, 1); if (!l2) { for (i=0; i 1) usage (1); if (opt_prompt) opt_prompt = percent_plus_unescape_string (xstrdup (opt_prompt)); if (cmd == oProtect) read_and_protect (fname); else if (cmd == oUnprotect) read_and_unprotect (fname); else if (cmd == oShadow) read_and_shadow (fname); else if (cmd == oShowShadowInfo) show_shadow_info (fname); else if (cmd == oShowKeygrip) show_keygrip (fname); else if (cmd == oP12Import) import_p12_file (fname); else if (cmd == oP12Export) export_p12_file (fname); else show_file (fname); agent_exit (0); return 8; /*NOTREACHED*/ } void agent_exit (int rc) { rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* Return the passphrase string and ask the agent if it has not been set from the command line PROMPTNO select the prompt to display: 0 = default 1 = taken from the option --prompt 2 = for unprotecting a pkcs#12 object 3 = for protecting a new pkcs#12 object 4 = for protecting an imported pkcs#12 in our system 5 = reenter the passphrase When adding 100 to the values, a "does not match - try again" errro message is shown. */ static char * get_passphrase (int promptno) { char *pw; int err; const char *desc; #ifdef HAVE_LANGINFO_CODESET char *orig_codeset = NULL; #endif int error_msgno; if (opt_passphrase) return xstrdup (opt_passphrase); error_msgno = promptno / 100; promptno %= 100; #ifdef ENABLE_NLS /* The Assuan agent protocol requires us to transmit utf-8 strings */ orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); #ifdef HAVE_LANGINFO_CODESET if (!orig_codeset) orig_codeset = nl_langinfo (CODESET); #endif if (orig_codeset && !strcmp (orig_codeset, "UTF-8")) orig_codeset = NULL; if (orig_codeset) { /* We only switch when we are able to restore the codeset later. */ orig_codeset = xstrdup (orig_codeset); if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8")) orig_codeset = NULL; } #endif if (promptno == 1 && opt_prompt) desc = opt_prompt; else if (promptno == 2) desc = _("Please enter the passphrase to unprotect the " "PKCS#12 object."); else if (promptno == 3) desc = _("Please enter the passphrase to protect the " "new PKCS#12 object."); else if (promptno == 4) desc = _("Please enter the passphrase to protect the " "imported object within the GnuPG system."); else if (promptno == 5) desc = _("Please re-enter this passphrase"); else desc = _("Please enter the passphrase or the PIN\n" "needed to complete this operation."); pw = simple_pwquery (NULL, error_msgno == 1? _("does not match - try again"):NULL, _("Passphrase:"), desc, &err); #ifdef ENABLE_NLS if (orig_codeset) { bind_textdomain_codeset (PACKAGE_GT, orig_codeset); xfree (orig_codeset); } #endif if (!pw) { if (err) log_error (_("error while asking for the passphrase: %s\n"), gpg_strerror (err)); else log_info (_("cancelled\n")); agent_exit (0); } return pw; } /* Same as get_passphrase but requests it a second time and compares it to the one entered the first time. */ static char * get_new_passphrase (int promptno) { char *pw; int i, secondpromptno; pw = get_passphrase (promptno); if (!pw) return NULL; /* Canceled. */ if (!*pw) return pw; /* Empty passphrase - no need to as for repeating it. */ secondpromptno = 5; for (i=0; i < 3; i++) { char *pw2 = get_passphrase (secondpromptno); if (!pw2) { xfree (pw); return NULL; /* Canceled. */ } if (!strcmp (pw, pw2)) { xfree (pw2); return pw; /* Okay. */ } secondpromptno = 105; xfree (pw2); } xfree (pw); return NULL; /* 3 times repeated wrong - cancel. */ } static void release_passphrase (char *pw) { if (pw) { wipememory (pw, strlen (pw)); xfree (pw); } } static int store_private_key (const unsigned char *grip, const void *buffer, size_t length, int force) { 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 { if (!access (fname, F_OK)) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] secretkey-exists\n"); if (opt_no_fail_on_exist) log_info ("secret key file `%s' already exists\n", fname); else log_error ("secret key file `%s' already exists\n", fname); xfree (fname); return opt_no_fail_on_exist? 0 : -1; } 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); if (opt_status_msg) log_info ("[PROTECT-TOOL:] secretkey-stored\n"); xfree (fname); return 0; } diff --git a/agent/protect.c b/agent/protect.c index 45bdae496..19f6ccbc6 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1,1023 +1,1024 @@ /* 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "agent.h" #include "sexp-parse.h" #define PROT_CIPHER GCRY_CIPHER_AES #define PROT_CIPHER_STRING "aes" #define PROT_CIPHER_KEYLEN (128/8) /* A table containing the information needed to create a protected private key */ static struct { const char *algo; const char *parmlist; int prot_from, prot_to; } protect_info[] = { { "rsa", "nedpqu", 2, 5 }, { "dsa", "pqgyx", 4, 4 }, { "elg", "pgyx", 3, 3 }, { NULL } }; static int hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned long s2kcount, unsigned char *key, size_t keylen); /* Calculate the MIC for a private key S-Exp. SHA1HASH should point to a 20 byte buffer. This function is suitable for any algorithms. */ static int calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) { const unsigned char *hash_begin, *hash_end; const unsigned char *s; size_t n; s = plainkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); hash_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ while (*s == '(') { s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; if ( *s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; } if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); s++; hash_end = s; gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, hash_begin, hash_end - hash_begin); return 0; } /* Encrypt the parameter block starting at PROTBEGIN with length PROTLEN using the utf8 encoded key PASSPHRASE and return the entire encrypted block in RESULT or return with an error code. SHA1HASH is the 20 byte SHA-1 hash required for the integrity code. The parameter block is expected to be an incomplete S-Expression of the form (example in advanced format): (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) the returned block is the S-Expression: (protected mode (parms) encrypted_octet_string) */ static int do_encryption (const unsigned char *protbegin, size_t protlen, const char *passphrase, const unsigned char *sha1hash, unsigned char **result, size_t *resultlen) { gcry_cipher_hd_t hd; const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"; int blklen, enclen, outlen; unsigned char *iv = NULL; int rc; char *outbuf = NULL; char *p; int saltpos, ivpos, encpos; *resultlen = 0; *result = NULL; rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE); if (rc) return rc; /* We need to work on a copy of the data because this makes it easier to add the trailer and the padding and more important we have to prefix the text with 2 parenthesis, so we have to allocate enough space for: (()(4:hash4:sha120:)) + padding We always append a full block of random bytes as padding but encrypt only what is needed for a full blocksize */ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen; enclen = outlen/blklen * blklen; outbuf = gcry_malloc_secure (outlen); if (!outbuf) rc = out_of_core (); if (!rc) { /* Allocate random bytes to be used as IV, padding and s2k salt. */ iv = xtrymalloc (blklen*2+8); if (!iv) rc = gpg_error (GPG_ERR_ENOMEM); gcry_create_nonce (iv, blklen*2+8); rc = gcry_cipher_setiv (hd, iv, blklen); } if (!rc) { unsigned char *key; size_t keylen = PROT_CIPHER_KEYLEN; key = gcry_malloc_secure (keylen); if (!key) rc = out_of_core (); else { rc = hash_passphrase (passphrase, GCRY_MD_SHA1, 3, iv+2*blklen, 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 = (unsigned char*)p; memcpy (p+saltpos, iv+2*blklen, 8); memcpy (p+ivpos, iv, blklen); memcpy (p+encpos, outbuf, enclen); xfree (iv); xfree (outbuf); return 0; } /* Protect the key encoded in canonical format in PLAINKEY. We assume a valid S-Exp here. */ int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen) { int rc; const unsigned char *s; const unsigned char *hash_begin, *hash_end; const unsigned char *prot_begin, *prot_end, *real_end; size_t n; int c, infidx, i; unsigned char hashvalue[20]; 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; } /* 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 *resultlen) { size_t n, newlistlen; unsigned char *newlist, *p; const unsigned char *s; const unsigned char *startpos, *endpos; int i, rc; *result = NULL; *resultlen = 0; if (replacepos < 26) return gpg_error (GPG_ERR_BUG); /* Estimate the required size of the resulting list. We have a large safety margin of >20 bytes (MIC hash from CLEARTEXT and the removed "protected-" */ newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL); if (!newlistlen) return gpg_error (GPG_ERR_BUG); n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL); if (!n) return gpg_error (GPG_ERR_BUG); newlistlen += n; newlist = gcry_malloc_secure (newlistlen); if (!newlist) return out_of_core (); /* Copy the initial segment */ strcpy ((char*)newlist, "(11:private-key"); p = newlist + 15; memcpy (p, protectedkey+15+10, replacepos-15-10); p += replacepos-15-10; /* copy the cleartext */ s = cleartext; if (*s != '(' && s[1] != '(') return gpg_error (GPG_ERR_BUG); /*we already checked this */ s += 2; startpos = s; while ( *s == '(' ) { s++; n = snext (&s); if (!n) goto invalid_sexp; s += n; n = snext (&s); if (!n) goto invalid_sexp; s += n; if ( *s != ')' ) goto invalid_sexp; s++; } if ( *s != ')' ) goto invalid_sexp; endpos = s; s++; /* 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; *resultlen = newlistlen; return 0; failure: wipememory (newlist, newlistlen); xfree (newlist); return rc; invalid_sexp: wipememory (newlist, newlistlen); xfree (newlist); return gpg_error (GPG_ERR_INV_SEXP); } /* Unprotect the key encoded in canonical format. We assume a valid S-Exp here. */ 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; size_t finallen; 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 ) ) ) */ 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 ((const char*)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 only aes-128). */ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); iv = s; s += n; if (*s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); rc = do_decryption (s, n, passphrase, s2ksalt, s2kcount, iv, 16, &cleartext); if (rc) return rc; rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext, sha1hash, &final, &finallen); /* Albeit cleartext has been allocated in secure memory and thus xfree will wipe it out, we do an extra wipe just in case somethings goes badly wrong. */ wipememory (cleartext, n); xfree (cleartext); if (rc) return rc; rc = calculate_mic (final, sha1hash2); if (!rc && memcmp (sha1hash, sha1hash2, 20)) rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION); if (rc) { wipememory (final, finallen); xfree (final); return rc; } *result = final; *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); return 0; } /* Check the type of the private key, this is one of the constants: PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the value 0), PRIVATE_KEY_CLEAR for an unprotected private key. PRIVATE_KEY_PROTECTED for an protected private key or PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored elsewhere. */ int agent_private_key_type (const unsigned char *privatekey) { const unsigned char *s; size_t n; s = privatekey; if (*s != '(') return PRIVATE_KEY_UNKNOWN; s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; if (smatch (&s, n, "protected-private-key")) return PRIVATE_KEY_PROTECTED; if (smatch (&s, n, "shadowed-private-key")) return PRIVATE_KEY_SHADOWED; if (smatch (&s, n, "private-key")) return PRIVATE_KEY_CLEAR; return PRIVATE_KEY_UNKNOWN; } /* Transform a passphrase into a suitable key of length KEYLEN and store this key in the caller provided buffer KEY. The caller must provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on that mode an S2KSALT of 8 random bytes and an S2KCOUNT (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; } /* Create an canonical encoded S-expression with the shadow info from a card's SERIALNO and the IDSTRING. */ unsigned char * make_shadow_info (const char *serialno, const char *idstring) { const char *s; char *info, *p; char numbuf[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); if (!info) return NULL; *p++ = '('; sprintf (numbuf, "%d:", n); p = stpcpy (p, numbuf); for (s=serialno; *s && s[1]; s += 2) *(unsigned char *)p++ = xtoi_2 (s); sprintf (numbuf, "%d:", strlen (idstring)); p = stpcpy (p, numbuf); p = stpcpy (p, idstring); *p++ = ')'; *p = 0; return (unsigned char *)info; } /* Create a shadow key from a public key. We use the shadow protocol "ti-v1" and insert the S-expressionn SHADOW_INFO. The resulting S-expression is returned in an allocated buffer RESULT will point to. The input parameters are expected to be valid canonicalized S-expressions */ int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result) { const unsigned char *s; const unsigned char *point; size_t n; int depth = 0; char *p; size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL); size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL); if (!pubkey_len || !shadow_info_len) return gpg_error (GPG_ERR_INV_VALUE); s = pubkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "public-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ while (*s != ')') { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; s++; } point = s; /* insert right before the point */ depth--; s++; assert (depth == 1); /* Calculate required length by taking in account: the "shadowed-" prefix, the "shadowed", "t1-v1" as well as some parenthesis */ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; *result = xtrymalloc (n); p = (char*)*result; if (!p) return out_of_core (); p = stpcpy (p, "(20:shadowed-private-key"); /* (10:public-key ...)*/ memcpy (p, pubkey+14, point - (pubkey+14)); p += point - (pubkey+14); p = stpcpy (p, "(8:shadowed5:t1-v1"); memcpy (p, shadow_info, shadow_info_len); p += shadow_info_len; *p++ = ')'; memcpy (p, point, pubkey_len - (point - pubkey)); p += pubkey_len - (point - pubkey); return 0; } /* Parse a canonical encoded shadowed key and return a pointer to the inner list with the shadow_info */ int agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info) { const unsigned char *s; size_t n; int depth = 0; s = shadowkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "shadowed-private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ for (;;) { if (*s == ')') return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "shadowed")) break; s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; s++; } /* Found the shadowed list, S points to the protocol */ n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "t1-v1")) { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); *shadow_info = s; } else return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); return 0; } diff --git a/agent/query.c b/agent/query.c index a5a3d0153..0516bec03 100644 --- a/agent/query.c +++ b/agent/query.c @@ -1,703 +1,704 @@ /* query.c - fork of the pinentry to query stuff from the user * Copyright (C) 2001, 2002, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #endif #include #include "agent.h" #include "i18n.h" #include #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Because access to the pinentry must be serialized (it is and shall be a global mutual dialog) we should better timeout further requests after some time. 2 minutes seem to be a reasonable time. */ #define LOCK_TIMEOUT (1*60) /* The assuan context of the current pinentry. */ static assuan_context_t entry_ctx; /* The control variable of the connection owning the current pinentry. This is only valid if ENTRY_CTX is not NULL. Note, that we care only about the value of the pointer and that it should never be dereferenced. */ static ctrl_t entry_owner; /* A mutex used to serialize access to the pinentry. */ static pth_mutex_t entry_lock; /* The thread ID of the popup working thread. */ static pth_t popup_tid; /* A flag used in communication between the popup working thread and its stop function. */ static int popup_finished; /* Data to be passed to our callbacks, */ struct entry_parm_s { int lines; size_t size; unsigned char *buffer; }; /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_query (void) { static int initialized; if (!initialized) { if (pth_mutex_init (&entry_lock)) initialized = 1; } } static void dump_mutex_state (pth_mutex_t *m) { if (!(m->mx_state & PTH_MUTEX_INITIALIZED)) log_printf ("not_initialized"); else if (!(m->mx_state & PTH_MUTEX_LOCKED)) log_printf ("not_locked"); else log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count); } /* This function may be called to print infromation pertaining to the current state of this module to the log. */ void agent_query_dump_state (void) { log_info ("agent_query_dump_state: entry_lock="); dump_mutex_state (&entry_lock); log_printf ("\n"); log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", entry_ctx, (long)assuan_get_pid (entry_ctx), popup_tid); } /* Called to make sure that a popup window owned by the current connection gets closed. */ void agent_reset_query (ctrl_t ctrl) { if (entry_ctx && popup_tid && entry_owner == ctrl) { agent_popup_message_stop (ctrl); } } /* Unlock the pinentry so that another thread can start one and disconnect that pinentry - we do this after the unlock so that a stalled pinentry does not block other threads. Fixme: We should have a timeout in Assuan for the disconnect operation. */ static int unlock_pinentry (int rc) { assuan_context_t ctx = entry_ctx; entry_ctx = NULL; if (!pth_mutex_release (&entry_lock)) { log_error ("failed to release the entry lock\n"); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } assuan_disconnect (ctx); return rc; } /* To make sure we leave no secrets in our image after forking of the pinentry, we use this callback. */ static void atfork_cb (void *opaque, int where) { if (!where) gcry_control (GCRYCTL_TERM_SECMEM); } /* Fork off the pin entry if this has not already been done. Note, that this function must always be used to aquire the lock for the pinentry - we will serialize _all_ pinentry calls. */ static int start_pinentry (ctrl_t ctrl) { int rc; const char *pgmname; ASSUAN_CONTEXT ctx; const char *argv[5]; int no_close_list[3]; int i; pth_event_t evt; evt = pth_event (PTH_EVENT_TIME, pth_timeout (LOCK_TIMEOUT, 0)); if (!pth_mutex_acquire (&entry_lock, 0, evt)) { if (pth_event_occurred (evt)) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error (GPG_ERR_INTERNAL); pth_event_free (evt, PTH_FREE_THIS); log_error (_("failed to acquire the pinentry lock: %s\n"), gpg_strerror (rc)); return rc; } pth_event_free (evt, PTH_FREE_THIS); entry_owner = ctrl; if (entry_ctx) return 0; if (opt.verbose) log_info ("starting a new PIN Entry\n"); 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_pinentry (tmperr); } if (!opt.pinentry_program || !*opt.pinentry_program) opt.pinentry_program = GNUPG_DEFAULT_PINENTRY; if ( !(pgmname = strrchr (opt.pinentry_program, '/'))) pgmname = opt.pinentry_program; else pgmname++; argv[0] = pgmname; if (ctrl->display && !opt.keep_display) { argv[1] = "--display"; argv[2] = ctrl->display; argv[3] = NULL; } else argv[1] = 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_connect2 (&ctx, opt.pinentry_program, (char**)argv, no_close_list, atfork_cb, NULL); if (rc) { log_error ("can't connect to the PIN entry module: %s\n", assuan_strerror (rc)); return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY)); } entry_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to PIN entry established\n"); rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); if (ctrl->ttyname) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", ctrl->ttyname) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (rc) return unlock_pinentry (map_assuan_err (rc)); } if (ctrl->ttytype) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", ctrl->ttytype) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } return 0; } static AssuanError getpin_cb (void *opaque, const void *buffer, size_t length) { struct entry_parm_s *parm = opaque; if (!buffer) return 0; /* we expect the pin to fit on one line */ if (parm->lines || length >= parm->size) return ASSUAN_Too_Much_Data; /* fixme: we should make sure that the assuan buffer is allocated in secure memory or read the response byte by byte */ memcpy (parm->buffer, buffer, length); parm->buffer[length] = 0; parm->lines++; return 0; } static int all_digitsp( const char *s) { for (; *s && *s >= '0' && *s <= '9'; s++) ; return !*s; } /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed numbers. */ int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo) { int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ if (!pininfo || pininfo->max_length < 1) return gpg_error (GPG_ERR_INV_VALUE); if (!desc_text && pininfo->min_digits) desc_text = _("Please enter your PIN, so that the secret key " "can be unlocked for this session"); else if (!desc_text) desc_text = _("Please enter your passphrase, so that the secret key " "can be unlocked for this session"); if (prompt_text) is_pin = !!strstr (prompt_text, "PIN"); else is_pin = desc_text && strstr (desc_text, "PIN"); rc = start_pinentry (ctrl); if (rc) return rc; snprintf (line, DIM(line)-1, "SETDESC %s", desc_text); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); snprintf (line, DIM(line)-1, "SETPROMPT %s", prompt_text? prompt_text : is_pin? "PIN:" : "Passphrase:"); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); if (initial_errtext) { snprintf (line, DIM(line)-1, "SETERROR %s", initial_errtext); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); parm.size = pininfo->max_length; parm.buffer = (unsigned char*)pininfo->pin; if (errtext) { /* fixme: should we show the try count? It must be translated */ snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)", errtext, pininfo->failed_tries+1, pininfo->max_tries); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); errtext = NULL; } rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, NULL, NULL, NULL, NULL); if (rc == ASSUAN_Too_Much_Data) errtext = is_pin? _("PIN too long") : _("Passphrase too long"); else if (rc) return unlock_pinentry (map_assuan_err (rc)); if (!errtext && pininfo->min_digits) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = _("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = _("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = _("PIN too short"); } if (!errtext && pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); if (rc == -1 && pininfo->cb_errtext) errtext = pininfo->cb_errtext; else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE || gpg_err_code (rc) == GPG_ERR_BAD_PIN) errtext = (is_pin? _("Bad PIN") : _("Bad Passphrase")); else if (rc) return unlock_pinentry (map_assuan_err (rc)); } if (!errtext) return unlock_pinentry (0); /* okay, got a PIN or passphrase */ } return unlock_pinentry (gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); } /* Ask for the passphrase using the supplied arguments. The passphrase is returned in RETPASS as an hex encoded string to be freed by the caller */ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext) { int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; unsigned char *p; char *hexstring; int i; *retpass = NULL; if (opt.batch) return gpg_error (GPG_ERR_BAD_PASSPHRASE); rc = start_pinentry (ctrl); if (rc) return rc; if (!prompt) prompt = desc && strstr (desc, "PIN")? "PIN": _("Passphrase"); if (desc) snprintf (line, DIM(line)-1, "SETDESC %s", desc); else snprintf (line, DIM(line)-1, "RESET"); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); snprintf (line, DIM(line)-1, "SETPROMPT %s", prompt); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); if (errtext) { snprintf (line, DIM(line)-1, "SETERROR %s", errtext); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } memset (&parm, 0, sizeof parm); parm.size = ASSUAN_LINELENGTH/2 - 5; parm.buffer = gcry_malloc_secure (parm.size+10); if (!parm.buffer) return unlock_pinentry (out_of_core ()); assuan_begin_confidential (entry_ctx); rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, NULL, NULL, NULL, NULL); if (rc) { xfree (parm.buffer); return unlock_pinentry (map_assuan_err (rc)); } hexstring = gcry_malloc_secure (strlen ((char*)parm.buffer)*2+1); if (!hexstring) { gpg_error_t tmperr = out_of_core (); xfree (parm.buffer); return unlock_pinentry (tmperr); } for (i=0, p=parm.buffer; *p; p++, i += 2) sprintf (hexstring+i, "%02X", *p); xfree (parm.buffer); *retpass = hexstring; return unlock_pinentry (0); } /* Pop up the PIN-entry, display the text and the prompt and ask the user to confirm this. We return 0 for success, ie. the user confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an other error. */ int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *cancel) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_pinentry (ctrl); if (rc) return rc; if (desc) snprintf (line, DIM(line)-1, "SETDESC %s", desc); else snprintf (line, DIM(line)-1, "RESET"); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); if (ok) { snprintf (line, DIM(line)-1, "SETOK %s", ok); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } if (cancel) { snprintf (line, DIM(line)-1, "SETCANCEL %s", cancel); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } rc = assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL); return unlock_pinentry (map_assuan_err (rc)); } /* The thread running the popup message. */ static void * popup_message_thread (void *arg) { assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL); popup_finished = 1; return NULL; } /* Pop up a message window similar to the confirm one but keep it open until agent_popup_message_stop has been called. It is crucial for the caller to make sure that the stop function gets called as soon as the message is not anymore required becuase the message is system modal and all other attempts to use the pinentry will fail (after a timeout). */ int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn, const char *cancel_btn) { int rc; char line[ASSUAN_LINELENGTH]; pth_attr_t tattr; rc = start_pinentry (ctrl); if (rc) return rc; if (desc) snprintf (line, DIM(line)-1, "SETDESC %s", desc); else snprintf (line, DIM(line)-1, "RESET"); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); if (ok_btn) { snprintf (line, DIM(line)-1, "SETOK %s", ok_btn); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } if (cancel_btn) { snprintf (line, DIM(line)-1, "SETCANCEL %s", cancel_btn); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); } tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); pth_attr_set (tattr, PTH_ATTR_NAME, "popup-message"); popup_finished = 0; popup_tid = pth_spawn (tattr, popup_message_thread, NULL); if (!popup_tid) { rc = gpg_error_from_errno (errno); log_error ("error spawning popup message handler: %s\n", strerror (errno) ); pth_attr_destroy (tattr); return unlock_pinentry (rc); } pth_attr_destroy (tattr); return 0; } /* Close a popup window. */ void agent_popup_message_stop (ctrl_t ctrl) { int rc; pid_t pid; if (!popup_tid || !entry_ctx) { log_debug ("agent_popup_message_stop called with no active popup\n"); return; } pid = assuan_get_pid (entry_ctx); if (pid == (pid_t)(-1)) ; /* No pid available can't send a kill. */ else if (popup_finished) ; /* Already finished and ready for joining. */ else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) { /* The daemon already died. No need to send a kill. However because we already waited for the process, we need to tell assuan that it should not wait again (done by unlock_pinentry). */ if (rc == pid) assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); } else kill (pid, SIGINT); /* Now wait for the thread to terminate. */ rc = pth_join (popup_tid, NULL); if (!rc) log_debug ("agent_popup_message_stop: pth_join failed: %s\n", strerror (errno)); popup_tid = NULL; entry_owner = NULL; /* Now we can close the connection. */ unlock_pinentry (0); } diff --git a/agent/t-protect.c b/agent/t-protect.c index fee3c561d..9ddd49414 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -1,308 +1,309 @@ /* t-protect.c - Module tests for protect.c * Copyright (C) 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "agent.h" #define pass() do { ; } while(0) #define fail() do { fprintf (stderr, "%s:%d: test failed\n",\ __FILE__,__LINE__); \ exit (1); \ } while(0) static void test_agent_protect (void) { /* Protect the key encoded in canonical format in PLAINKEY. We assume a valid S-Exp here. */ unsigned int i; int ret; struct key_spec { const char *string; }; /* Valid RSA key. */ struct key_spec key_rsa_valid = { "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73" "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92" "\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04" "\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D" "\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49" "\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F" "\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77" "\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28" "\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3" "\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A" "\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9" "\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77" "\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7" "\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD" "\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34" "\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B" "\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A" "\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85" "\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28" "\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F" "\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2" "\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21" "\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33" "\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72" "\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B" "\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17" "\xD0\x68\x0B\x29\x29\x29\x00" }; /* This RSA key is missing the last closing brace. */ struct key_spec key_rsa_bogus_0 = { "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73" "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92" "\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04" "\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D" "\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49" "\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F" "\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77" "\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28" "\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3" "\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A" "\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9" "\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77" "\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7" "\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD" "\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34" "\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B" "\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A" "\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85" "\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28" "\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F" "\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2" "\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21" "\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33" "\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72" "\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B" "\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17" "\xD0\x68\x0B\x29\x29\x00" }; /* This RSA key is the `e' value. */ struct key_spec key_rsa_bogus_1 = { "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73" "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xA8\x80\xB6\x71\xF4\x95\x9F\x49\x84\xED" "\xC1\x1D\x5F\xFF\xED\x14\x7B\x9C\x6A\x62\x0B\x7B\xE2\x3E\x41\x48\x49\x85\xF5\x64" "\x50\x04\x9D\x30\xFC\x84\x1F\x01\xC3\xC3\x15\x03\x48\x6D\xFE\x59\x0B\xB0\xD0\x3E" "\x68\x8A\x05\x7A\x62\xB0\xB9\x6E\xC5\xD2\xA8\xEE\x0C\x6B\xDE\x5E\x3D\x8E\xE8\x8F" "\xB3\xAE\x86\x99\x7E\xDE\x2B\xC2\x4D\x60\x51\xDB\xB1\x2C\xD0\x38\xEC\x88\x62\x3E" "\xA9\xDD\x11\x53\x04\x17\xE4\xF2\x07\x50\xDC\x44\xED\x14\xF5\x0B\xAB\x9C\xBC\x24" "\xC6\xCB\xAD\x0F\x05\x25\x94\xE2\x73\xEB\x14\xD5\xEE\x5E\x18\xF0\x40\x31\x29\x28" "\x31\x3A\x64\x31\x32\x38\x3A\x40\xD0\x55\x9D\x2A\xA7\xBC\xBF\xE2\x3E\x33\x98\x71" "\x7B\x37\x3D\xB8\x38\x57\xA1\x43\xEA\x90\x81\x42\xCA\x23\xE1\xBF\x9C\xA8\xBC\xC5" "\x9B\xF8\x9D\x77\x71\xCD\xD3\x85\x8B\x20\x3A\x92\xE9\xBC\x79\xF3\xF7\xF5\x6D\x15" "\xA3\x58\x3F\xC2\xEB\xED\x72\xD4\xE0\xCF\xEC\xB3\xEC\xEB\x09\xEA\x1E\x72\x6A\xBA" "\x95\x82\x2C\x7E\x30\x95\x66\x3F\xA8\x2D\x40\x0F\x7A\x12\x4E\xF0\x71\x0F\x97\xDB" "\x81\xE4\x39\x6D\x24\x58\xFA\xAB\x3A\x36\x73\x63\x01\x77\x42\xC7\x9A\xEA\x87\xDA" "\x93\x8F\x6C\x64\xAD\x9E\xF0\xCA\xA2\x89\xA4\x0E\xB3\x25\x73\x29\x28\x31\x3A\x70" "\x36\x35\x3A\x00\xC3\xF7\x37\x3F\x9D\x93\xEC\xC7\x5E\x4C\xB5\x73\x29\x62\x35\x80" "\xC6\x7C\x1B\x1E\x68\x5F\x92\x56\x77\x0A\xE2\x8E\x95\x74\x87\xA5\x2F\x83\x2D\xF7" "\xA1\xC2\x78\x54\x18\x6E\xDE\x35\xF0\x9F\x7A\xCA\x80\x5C\x83\x5C\x44\xAD\x8B\xE7" "\x5B\xE2\x63\x7D\x6A\xC7\x98\x97\x29\x28\x31\x3A\x71\x36\x35\x3A\x00\xDC\x1F\xB1" "\xB3\xD8\x13\xE0\x09\x19\xFD\x1C\x58\xA1\x2B\x02\xB4\xC8\xF2\x1C\xE7\xF9\xC6\x3B" "\x68\xB9\x72\x43\x86\xEF\xA9\x94\x68\x02\xEF\x7D\x77\xE0\x0A\xD1\xD7\x48\xFD\xCD" "\x98\xDA\x13\x8A\x76\x48\xD4\x0F\x63\x28\xFA\x01\x1B\xF3\xC7\x15\xB8\x53\x22\x7E" "\x77\x29\x28\x31\x3A\x75\x36\x35\x3A\x00\xB3\xBB\x4D\xEE\x5A\xAF\xD0\xF2\x56\x8A" "\x10\x2D\x6F\x4B\x2D\x76\x49\x9B\xE9\xA8\x60\x5D\x9E\x7E\x50\x86\xF1\xA1\x0F\x28" "\x9B\x7B\xE8\xDD\x1F\x87\x4E\x79\x7B\x50\x12\xA7\xB4\x8B\x52\x38\xEC\x7C\xBB\xB9" "\x55\x87\x11\x1C\x74\xE7\x7F\xA0\xBA\xE3\x34\x5D\x61\xBF\x29\x29\x29\x00" }; struct { const char *key; const char *passphrase; int no_result_expected; int compare_results; unsigned char *result_expected; size_t resultlen_expected; int ret_expected; unsigned char *result; size_t resultlen; } specs[] = { /* Invalid S-Expressions */ /* - non-NULL */ { "", "passphrase", 1, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, /* - NULL; disabled, this segfaults */ //{ NULL, // "passphrase", 1, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, /* Valid and invalid keys. */ { key_rsa_valid.string, "passphrase", 0, 0, NULL, 0, 0, NULL, 0 }, { key_rsa_bogus_0.string, "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, { key_rsa_bogus_1.string, "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, /* FIXME: add more test data. */ }; for (i = 0; i < DIM (specs); i++) { ret = agent_protect ((const unsigned char*)specs[i].key, specs[i].passphrase, &specs[i].result, &specs[i].resultlen); if (gpg_err_code (ret) != specs[i].ret_expected) { printf ("agent_protect() returned `%i/%s'; expected `%i/%s'\n", ret, gpg_strerror (ret), specs[i].ret_expected, gpg_strerror (specs[i].ret_expected)); abort (); } if (specs[i].no_result_expected) { assert (! specs[i].result); assert (! specs[i].resultlen); } else { if (specs[i].compare_results) { assert (specs[i].resultlen == specs[i].resultlen_expected); if (specs[i].result_expected) assert (! memcmp (specs[i].result, specs[i].result_expected, specs[i].resultlen)); else assert (! specs[i].result); } xfree (specs[i].result); } } } static void test_agent_unprotect (void) { /* Unprotect the key encoded in canonical format. We assume a valid S-Exp here. */ /* int */ /* agent_unprotect (const unsigned char *protectedkey, const char *passphrase, */ /* unsigned char **result, size_t *resultlen) */ } static void test_agent_private_key_type (void) { /* Check the type of the private key, this is one of the constants: PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the value 0), PRIVATE_KEY_CLEAR for an unprotected private key. PRIVATE_KEY_PROTECTED for an protected private key or PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored elsewhere. */ /* int */ /* agent_private_key_type (const unsigned char *privatekey) */ } static void test_make_shadow_info (void) { #if 0 static struct { const char *snstr; const char *idstr; const char *expected; } data[] = { { "", "", NULL }, }; int i; unsigned char *result; for (i=0; i < DIM(data); i++) { result = make_shadow_info (data[i].snstr, data[i].idstr); if (!result && !data[i].expected) pass (); else if (!result && data[i].expected) fail (); else if (!data[i].expected) fail (); /* fixme: Need to compare the result but also need to check proper S-expression syntax. */ } #endif } static void test_agent_shadow_key (void) { /* Create a shadow key from a public key. We use the shadow protocol "ti-v1" and insert the S-expressionn SHADOW_INFO. The resulting S-expression is returned in an allocated buffer RESULT will point to. The input parameters are expected to be valid canonicalized S-expressions */ /* int */ /* agent_shadow_key (const unsigned char *pubkey, */ /* const unsigned char *shadow_info, */ /* unsigned char **result) */ } static void test_agent_get_shadow_info (void) { /* Parse a canonical encoded shadowed key and return a pointer to the inner list with the shadow_info */ /* int */ /* agent_get_shadow_info (const unsigned char *shadowkey, */ /* unsigned char const **shadow_info) */ } int main (int argc, char **argv) { gcry_control (GCRYCTL_DISABLE_SECMEM); test_agent_protect (); test_agent_unprotect (); test_agent_private_key_type (); test_make_shadow_info (); test_agent_shadow_key (); test_agent_get_shadow_info (); return 0; } diff --git a/agent/trans.c b/agent/trans.c index 7fa5e3d6b..5eb7d25c0 100644 --- a/agent/trans.c +++ b/agent/trans.c @@ -1,42 +1,43 @@ /* trans.c - translatable strings * Copyright (C) 2001 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* To avoid any problems with the gettext implementation (there used to be some vulnerabilities in the last years and the use of external files is a minor security problem in itself), we use our own simple translation stuff */ #include #include #include #include #include #include #include #include #include #include "agent.h" const char * trans (const char *text) { return text; } diff --git a/agent/trustlist.c b/agent/trustlist.c index edb00650d..d234af692 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -1,417 +1,418 @@ /* trustlist.c - Maintain the list of trusted keys * Copyright (C) 2002, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "agent.h" #include /* fixme: need a way to avoid assuan calls here */ #include "i18n.h" static const char headerblurb[] = "# This is the list of trusted keys. Comment lines, like this one, as\n" "# well as empty lines are ignored. The entire file may be integrity\n" "# protected by the use of a MAC, so changing the file does not make\n" "# sense without the knowledge of the MAC key. Lines do have a length\n" "# limit but this is not serious limitation as the format of the\n" "# entries is fixed and checked by gpg-agent: A non-comment line starts\n" "# with optional white spaces, followed by the SHA-1 fingerpint in hex,\n" "# optionally followed by a flag character which my either be 'P', 'S'\n" "# or '*'. Additional data, delimited by white space, is ignored.\n" "#\n" "# NOTE: You should give the gpg-agent a HUP after editing this file.\n" "\n"; static FILE *trustfp; static int trustfp_used; /* Counter to track usage of TRUSTFP. */ static int reload_trustlist_pending; static int open_list (int append) { char *fname; fname = make_filename (opt.homedir, "trustlist.txt", NULL); trustfp = fopen (fname, append? "a+":"r"); if (!trustfp && errno == ENOENT) { trustfp = fopen (fname, "wx"); if (!trustfp) { 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; } fputs (headerblurb, trustfp); fclose (trustfp); trustfp = fopen (fname, append? "a+":"r"); } if (!trustfp) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("can't open `%s': %s\n", fname, strerror (errno)); xfree (fname); return tmperr; } /*FIXME: check the MAC */ return 0; } /* Read the trustlist and return entry by entry. KEY must point to a buffer of at least 41 characters. KEYFLAG does return either 'P', 'S' or '*'. Reading a valid entry return 0, EOF returns -1 any other error returns the appropriate error code. */ static int read_list (char *key, int *keyflag) { int rc; int c, i, j; char *p, line[256]; if (!trustfp) { rc = open_list (0); if (rc) return rc; } do { if (!fgets (line, DIM(line)-1, trustfp) ) { if (feof (trustfp)) return -1; return gpg_error (gpg_err_code_from_errno (errno)); } if (!*line || line[strlen(line)-1] != '\n') { /* eat until end of line */ while ( (c=getc (trustfp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) if ( p[i] != ':' ) key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; key[j] = 0; if (j!=40 || !(spacep (p+i) || p[i] == '\n')) { log_error ("invalid formatted fingerprint in trustlist\n"); return gpg_error (GPG_ERR_BAD_DATA); } assert (p[i]); if (p[i] == '\n') *keyflag = '*'; else { i++; if ( p[i] == 'P' || p[i] == 'p') *keyflag = 'P'; else if ( p[i] == 'S' || p[i] == 's') *keyflag = 'S'; else if ( p[i] == '*') *keyflag = '*'; else { log_error ("invalid keyflag in trustlist\n"); return gpg_error (GPG_ERR_BAD_DATA); } i++; if ( !(spacep (p+i) || p[i] == '\n')) { log_error ("invalid keyflag in trustlist\n"); return gpg_error (GPG_ERR_BAD_DATA); } } return 0; } /* Check whether the given fpr is in our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. */ int agent_istrusted (const char *fpr) { int rc; static char key[41]; int keyflag; trustfp_used++; if (trustfp) rewind (trustfp); while (!(rc=read_list (key, &keyflag))) { if (!strcmp (key, fpr)) { trustfp_used--; return 0; } } if (rc != -1) { /* Error in the trustdb - close it to give the user a chance for correction */ if (trustfp) fclose (trustfp); trustfp = NULL; } trustfp_used--; return rc; } /* Write all trust entries to FP. */ int agent_listtrusted (void *assuan_context) { int rc; static char key[51]; int keyflag; trustfp_used++; if (trustfp) rewind (trustfp); while (!(rc=read_list (key, &keyflag))) { key[40] = ' '; key[41] = keyflag; key[42] = '\n'; assuan_send_data (assuan_context, key, 43); assuan_send_data (assuan_context, NULL, 0); /* flush */ } if (rc == -1) rc = 0; if (rc) { /* Error in the trustdb - close it to give the user a chance for correction */ if (trustfp) fclose (trustfp); trustfp = NULL; } trustfp_used--; return rc; } /* Insert the given fpr into our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'. This function does first check whether that key has alreay been put into the trustdb and returns success in this case. Before a FPR actually gets inserted, the user is asked by means of the pin-entry whether this is actual wants he want to do. */ int agent_marktrusted (CTRL ctrl, const char *name, const char *fpr, int flag) { int rc; static char key[41]; int keyflag; char *desc; char *fname; /* Check whether we are at all allowed to modify the trustlist. This is useful so that the trustlist may be a symlink to a global trustlist with only admin priviliges to modify it. Of course this is not a secure way of denying access, but it avoids the usual clicking on an Okay buttun thing most users are used to. */ fname = make_filename (opt.homedir, "trustlist.txt", NULL); rc = access (fname, W_OK); if (rc && errno != ENOENT) { xfree (fname); return gpg_error (GPG_ERR_EPERM); } xfree (fname); trustfp_used++; if (trustfp) rewind (trustfp); while (!(rc=read_list (key, &keyflag))) { if (!strcmp (key, fpr)) return 0; } if (trustfp) fclose (trustfp); trustfp = NULL; if (rc != -1) { trustfp_used--; return rc; /* Error in the trustlist. */ } /* This feature must explicitly been enabled. */ if (!opt.allow_mark_trusted) { trustfp_used--; return gpg_error (GPG_ERR_NOT_SUPPORTED); } /* Insert a new one. */ if (asprintf (&desc, /* TRANSLATORS: This prompt is shown by the Pinentry and has one special property: A "%%0A" is used by Pinentry to insert a line break. The double percent sign is actually needed because it is also a printf format string. If you need to insert a plain % sign, you need to encode it as "%%25". The second "%s" gets replaced by a hexdecimal fingerprint string whereas the first one receives the name as store in the certificate. */ _("Please verify that the certificate identified as:%%0A" " \"%s\"%%0A" "has the fingerprint:%%0A" " %s"), name, fpr) < 0 ) { trustfp_used--; return out_of_core (); } /* TRANSLATORS: "Correct" is the label of a button and intended to be hit if the fingerprint matches the one of the CA. The other button is "the default "Cancel" of the Pinentry. */ rc = agent_get_confirmation (ctrl, desc, _("Correct"), NULL); free (desc); if (rc) { trustfp_used--; return rc; } if (asprintf (&desc, /* TRANSLATORS: This prompt is shown by the Pinentry and has one special property: A "%%0A" is used by Pinentry to insert a line break. The double percent sign is actually needed because it is also a printf format string. If you need to insert a plain % sign, you need to encode it as "%%25". The "%s" gets replaced by the name as store in the certificate. */ _("Do you ultimately trust%%0A" " \"%s\"%%0A" "to correctly certify user certificates?"), name) < 0 ) { trustfp_used--; return out_of_core (); } rc = agent_get_confirmation (ctrl, desc, _("Yes"), _("No")); free (desc); if (rc) { trustfp_used--; return rc; } /* Now check again to avoid duplicates. Also open in append mode now. */ rc = open_list (1); if (rc) { trustfp_used--; return rc; } rewind (trustfp); while (!(rc=read_list (key, &keyflag))) { if (!strcmp (key, fpr)) { trustfp_used--; return 0; } } if (rc != -1) { if (trustfp) fclose (trustfp); trustfp = NULL; trustfp_used--; return rc; /* Error in the trustlist. */ } rc = 0; /* Append the key. */ fflush (trustfp); fputs ("\n# ", trustfp); print_sanitized_string (trustfp, name, 0); fprintf (trustfp, "\n%s %c\n", fpr, flag); if (ferror (trustfp)) rc = gpg_error (gpg_err_code_from_errno (errno)); /* close because we are in append mode */ if (fclose (trustfp)) rc = gpg_error (gpg_err_code_from_errno (errno)); trustfp = NULL; trustfp_used--; return rc; } void agent_trustlist_housekeeping (void) { if (reload_trustlist_pending && !trustfp_used) { if (trustfp) { fclose (trustfp); trustfp = NULL; } reload_trustlist_pending = 0; } } /* Not all editors are editing files in place, thus a changes trustlist.txt won't be recognozed if we keep the file descriptor open. This function may be used to explicitly close that file descriptor, which will force a reopen in turn. */ void agent_reload_trustlist (void) { reload_trustlist_pending = 1; agent_trustlist_housekeeping (); } diff --git a/am/cmacros.am b/am/cmacros.am index de68b6f31..7b449e2c0 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -1,46 +1,47 @@ # cmacros.am - C macro definitions # Copyright (C) 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. localedir = $(datadir)/locale AM_CPPFLAGS += -DLOCALEDIR=\"$(localedir)\" if ! HAVE_DOSISH_SYSTEM AM_CPPFLAGS += -DGNUPG_BINDIR="\"$(bindir)\"" \ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" endif if GNUPG_AGENT_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" endif if GNUPG_PINENTRY_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" endif if GNUPG_SCDAEMON_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" endif if GNUPG_DIRMNGR_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" endif if GNUPG_PROTECT_TOOL_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" endif diff --git a/common/Makefile.am b/common/Makefile.am index 34819e93f..085440bb3 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -1,59 +1,60 @@ # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in noinst_LIBRARIES = libcommon.a libsimple-pwquery.a AM_CPPFLAGS = -I$(top_srcdir)/gl AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS) \ $(PTH_CFLAGS) libcommon_a_SOURCES = \ util.h i18n.h \ errors.h \ sexp-parse.h \ sexputil.c \ maperror.c \ sysutils.c sysutils.h \ homedir.c \ gettime.c \ yesno.c \ b64enc.c \ miscellaneous.c \ xasprintf.c \ xreadline.c \ membuf.c membuf.h \ iobuf.c iobuf.h \ ttyio.c ttyio.h \ asshelp.c asshelp.h \ exechelp.c exechelp.h \ simple-gettext.c \ w32reg.c \ signal.c \ dynload.h \ estream.c estream.h \ dns-cert.c dns-cert.h \ pka.c pka.h libsimple_pwquery_a_SOURCES = \ simple-pwquery.c simple-pwquery.h asshelp.c asshelp.h diff --git a/common/asshelp.c b/common/asshelp.c index 0edaeae0e..1da899522 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -1,168 +1,169 @@ /* asshelp.c - Helper functions for Assuan * Copyright (C) 2002, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "util.h" #include "asshelp.h" static gpg_error_t send_one_option (assuan_context_t ctx, gpg_err_source_t errsource, const char *name, const char *value) { gpg_error_t err; char *optstr; if (!value || !*value) err = 0; /* Avoid sending empty strings. */ else if (asprintf (&optstr, "OPTION %s=%s", name, value ) < 0) err = gpg_error_from_errno (errno); else { assuan_error_t ae; ae = assuan_transact (ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); err = ae? map_assuan_err_with_source (errsource, ae) : 0; free (optstr); } return err; } /* Send the assuan commands pertaining to the pinenry environment. The OPT_* arguments are optional and may be used to override the defaults taken from the current locale. */ gpg_error_t send_pinentry_environment (assuan_context_t ctx, gpg_err_source_t errsource, const char *opt_display, const char *opt_ttyname, const char *opt_ttytype, const char *opt_lc_ctype, const char *opt_lc_messages) { gpg_error_t err = 0; char *dft_display = NULL; char *dft_ttyname = NULL; char *dft_ttytype = NULL; char *old_lc = NULL; char *dft_lc = NULL; /* Send the DISPLAY variable. */ dft_display = getenv ("DISPLAY"); if (opt_display || dft_display) { err = send_one_option (ctx, errsource, "display", opt_display ? opt_display : dft_display); if (err) return err; } /* Send the name of the TTY. */ if (!opt_ttyname) { dft_ttyname = getenv ("GPG_TTY"); if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); } if (opt_ttyname || dft_ttyname) { err = send_one_option (ctx, errsource, "ttyname", opt_ttyname ? opt_ttyname : dft_ttyname); if (err) return err; } /* Send the type of the TTY. */ dft_ttytype = getenv ("TERM"); if (opt_ttytype || (dft_ttyname && dft_ttytype)) { err = send_one_option (ctx, errsource, "ttytype", opt_ttyname ? opt_ttytype : dft_ttytype); if (err) return err; } /* Send the value for LC_CTYPE. */ #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) return gpg_error_from_errno (errno); } dft_lc = setlocale (LC_CTYPE, ""); #endif if (opt_lc_ctype || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-ctype", opt_lc_ctype ? opt_lc_ctype : dft_lc); } #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) if (old_lc) { setlocale (LC_CTYPE, old_lc); free (old_lc); } #endif if (err) return err; /* Send the value for LC_MESSAGES. */ #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) return gpg_error_from_errno (errno); } dft_lc = setlocale (LC_MESSAGES, ""); #endif if (opt_lc_messages || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-messages", opt_lc_messages ? opt_lc_messages : dft_lc); } #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) if (old_lc) { setlocale (LC_MESSAGES, old_lc); free (old_lc); } #endif if (err) return err; return 0; } diff --git a/common/asshelp.h b/common/asshelp.h index 2d6dc79e6..9f4b5806b 100644 --- a/common/asshelp.h +++ b/common/asshelp.h @@ -1,37 +1,38 @@ /* asshelp.h - Helper functions for Assuan * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_ASSHELP_H #define GNUPG_COMMON_ASSHELP_H #include #include gpg_error_t send_pinentry_environment (assuan_context_t ctx, gpg_err_source_t errsource, const char *opt_display, const char *opt_ttyname, const char *opt_ttytype, const char *opt_lc_ctype, const char *opt_lc_messages); #endif /*GNUPG_COMMON_ASSHELP_H*/ diff --git a/common/b64enc.c b/common/b64enc.c index 5b7a42ab3..bfc49deb6 100644 --- a/common/b64enc.c +++ b/common/b64enc.c @@ -1,213 +1,214 @@ /* b64enc.c - Simple Base64 encoder. * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "i18n.h" #include "util.h" #define B64ENC_DID_HEADER 1 #define B64ENC_DID_TRAILER 2 #define B64ENC_NO_LINEFEEDS 16 /* The base-64 character list */ static unsigned char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /* Prepare for base-64 writing to the stream FP. If TITLE is not NULL and not an empty string, this string will be used as the title for the armor lines, with TITLE being an empty string, we don't write the header lines and furthermore even don't write any linefeeds. With TITLE beeing NULL, we merely don't write header but make sure that lines are not too long. Note, that we don't write any output unless at least one byte get written using b64enc_write. */ gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title) { memset (state, 0, sizeof *state); state->fp = fp; if (title && !*title) state->flags |= B64ENC_NO_LINEFEEDS; else if (title) { state->title = xtrystrdup (title); if (!state->title) return gpg_error_from_errno (errno); } return 0; } /* Write NBYTES from BUFFER to the Base 64 stream identified by STATE. With BUFFER and NBYTES being 0, merely do a fflush on the stream. */ gpg_error_t b64enc_write (struct b64state *state, const void *buffer, size_t nbytes) { unsigned char radbuf[4]; int idx, quad_count; const unsigned char *p; FILE *fp = state->fp; if (!nbytes) { if (buffer && fflush (fp)) goto write_error; return 0; } if (!(state->flags & B64ENC_DID_HEADER)) { if (state->title) { if ( fputs ("-----BEGIN ", fp) == EOF || fputs (state->title, fp) == EOF || fputs ("-----\n", fp) == EOF) goto write_error; } state->flags |= B64ENC_DID_HEADER; } idx = state->idx; quad_count = state->quad_count; assert (idx < 4); memcpy (radbuf, state->radbuf, idx); for (p=buffer; nbytes; p++, nbytes--) { radbuf[idx++] = *p; if (idx > 2) { char tmp[4]; tmp[0] = bintoasc[(*radbuf >> 2) & 077]; tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; tmp[3] = bintoasc[radbuf[2]&077]; for (idx=0; idx < 4; idx++) putc (tmp[idx], fp); idx = 0; if (ferror (fp)) goto write_error; if (++quad_count >= (64/4)) { quad_count = 0; if (!(state->flags & B64ENC_NO_LINEFEEDS) && fputs ("\n", fp) == EOF) goto write_error; } } } memcpy (state->radbuf, radbuf, idx); state->idx = idx; state->quad_count = quad_count; return 0; write_error: return gpg_error_from_errno (errno); } gpg_error_t b64enc_finish (struct b64state *state) { gpg_error_t err = 0; unsigned char radbuf[4]; int idx, quad_count; FILE *fp; if (!(state->flags & B64ENC_DID_HEADER)) goto cleanup; /* Flush the base64 encoding */ fp = state->fp; idx = state->idx; quad_count = state->quad_count; assert (idx < 4); memcpy (radbuf, state->radbuf, idx); if (idx) { char tmp[4]; tmp[0] = bintoasc[(*radbuf>>2)&077]; if (idx == 1) { tmp[1] = bintoasc[((*radbuf << 4) & 060) & 077]; tmp[2] = '='; tmp[3] = '='; } else { tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; tmp[2] = bintoasc[((radbuf[1] << 2) & 074) & 077]; tmp[3] = '='; } for (idx=0; idx < 4; idx++) putc (tmp[idx], fp); idx = 0; if (ferror (fp)) goto write_error; if (++quad_count >= (64/4)) { quad_count = 0; if (!(state->flags & B64ENC_NO_LINEFEEDS) && fputs ("\n", fp) == EOF) goto write_error; } } /* Finish the last line and write the trailer. */ if (quad_count && !(state->flags & B64ENC_NO_LINEFEEDS) && fputs ("\n", fp) == EOF) goto write_error; if (state->title) { if ( fputs ("-----END ", fp) == EOF || fputs (state->title, fp) == EOF || fputs ("-----\n", fp) == EOF) goto write_error; } goto cleanup; write_error: err = gpg_error_from_errno (errno); cleanup: if (state->title) { xfree (state->title); state->title = NULL; } state->fp = NULL; return err; } diff --git a/common/dynload.h b/common/dynload.h index 2c074141f..9b67fa9ed 100644 --- a/common/dynload.h +++ b/common/dynload.h @@ -1,71 +1,72 @@ /* dlfcn.h - W32 functions for run-time dynamic loading * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_DYNLOAD_H #define GNUPG_DYNLOAD_H #ifndef __MINGW32__ #include #else #include #define RTLD_LAZY 0 static inline void * dlopen (const char * name, int flag) { void * hd = LoadLibrary (name); return hd; } static inline void * dlsym (void *hd, const char *sym) { if (hd && sym) { void * fnc = GetProcAddress (hd, sym); if (!fnc) return NULL; return fnc; } return NULL; } static inline const char * dlerror (void) { static char buf[32]; sprintf (buf, "ec=%lu", GetLastError ()); return buf; } static inline int dlclose (void * hd) { if (hd) { CloseHandle (hd); return 0; } return -1; } #endif /*__MINGW32__*/ #endif /*GNUPG_DYNLOAD_H*/ diff --git a/common/errors.h b/common/errors.h index f34f3ba79..131891f65 100644 --- a/common/errors.h +++ b/common/errors.h @@ -1,112 +1,113 @@ /* 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * 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_OK, 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, STATUS_NEWSIG }; /*-- 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/estream.c b/common/estream.c index 70b3d9c6e..c2030371b 100644 --- a/common/estream.c +++ b/common/estream.c @@ -1,2614 +1,2615 @@ /* estream.c - Extended stream I/O/ Library - Copyright (C) 2004 g10 Code GmbH - - This file is part of Libestream. - - Libestream 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. - - Libestream 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Libestream; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + * Copyright (C) 2004 g10 Code GmbH + * + * This file is part of Libestream. + * + * Libestream 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. + * + * Libestream 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Libestream; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #ifdef USE_ESTREAM_SUPPORT_H # include #endif #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTH # include #endif #ifndef HAVE_MKSTEMP int mkstemp (char *template); #endif #ifndef HAVE_MEMRCHR void *memrchr (const void *block, int c, size_t size); #endif #include /* Generally used types. */ typedef void *(*func_realloc_t) (void *mem, size_t size); typedef void (*func_free_t) (void *mem); /* Buffer management layer. */ #define BUFFER_BLOCK_SIZE BUFSIZ #define BUFFER_UNREAD_SIZE 16 /* Macros. */ #define BUFFER_ROUND_TO_BLOCK(size, block_size) \ (((size) + (block_size - 1)) / block_size) /* Locking. */ #ifdef HAVE_PTH typedef pth_mutex_t estream_mutex_t; # define ESTREAM_MUTEX_INITIALIZER PTH_MUTEX_INIT # define ESTREAM_MUTEX_LOCK(mutex) \ pth_mutex_acquire (&(mutex), 0, NULL) # define ESTREAM_MUTEX_UNLOCK(mutex) \ pth_mutex_release (&(mutex)) # define ESTREAM_MUTEX_TRYLOCK(mutex) \ ((pth_mutex_acquire (&(mutex), 1, NULL) == TRUE) ? 0 : -1) # define ESTREAM_MUTEX_INITIALIZE(mutex) \ pth_mutex_init (&(mutex)) # define ESTREAM_THREADING_INIT() ((pth_init () == TRUE) ? 0 : -1) #else typedef void *estream_mutex_t; # define ESTREAM_MUTEX_INITIALIZER NULL # define ESTREAM_MUTEX_LOCK(mutex) (void) 0 # define ESTREAM_MUTEX_UNLOCK(mutex) (void) 0 # define ESTREAM_MUTEX_TRYLOCK(mutex) 0 # define ESTREAM_MUTEX_INITIALIZE(mutex) (void) 0 # define ESTREAM_THREADING_INIT() 0 #endif /* Memory allocator functions. */ #define MEM_ALLOC malloc #define MEM_REALLOC realloc #define MEM_FREE free /* Primitive system I/O. */ #ifdef HAVE_PTH # define ESTREAM_SYS_READ pth_read # define ESTREAM_SYS_WRITE pth_write #else # define ESTREAM_SYS_READ read # define ESTREAM_SYS_WRITE write #endif /* Misc definitions. */ #define ES_DEFAULT_OPEN_MODE (S_IRUSR | S_IWUSR) #define ES_FLAG_WRITING ES__FLAG_WRITING /* An internal stream object. */ struct estream_internal { unsigned char buffer[BUFFER_BLOCK_SIZE]; unsigned char unread_buffer[BUFFER_UNREAD_SIZE]; estream_mutex_t lock; /* Lock. */ void *cookie; /* Cookie. */ void *opaque; /* Opaque data. */ unsigned int flags; /* Flags. */ off_t offset; es_cookie_read_function_t func_read; es_cookie_write_function_t func_write; es_cookie_seek_function_t func_seek; es_cookie_close_function_t func_close; int strategy; int fd; struct { unsigned int err: 1; unsigned int eof: 1; } indicators; unsigned int deallocate_buffer: 1; }; typedef struct estream_internal *estream_internal_t; #define ESTREAM_LOCK(stream) ESTREAM_MUTEX_LOCK (stream->intern->lock) #define ESTREAM_UNLOCK(stream) ESTREAM_MUTEX_UNLOCK (stream->intern->lock) #define ESTREAM_TRYLOCK(stream) ESTREAM_MUTEX_TRYLOCK (stream->intern->lock) /* Stream list. */ typedef struct estream_list *estream_list_t; struct estream_list { estream_t car; estream_list_t cdr; estream_list_t *prev_cdr; }; static estream_list_t estream_list; #ifdef HAVE_PTH static estream_mutex_t estream_list_lock = ESTREAM_MUTEX_INITIALIZER; #endif #define ESTREAM_LIST_LOCK ESTREAM_MUTEX_LOCK (estream_list_lock) #define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock) #ifndef EOPNOTSUPP # define EOPNOTSUPP ENOSYS #endif /* Macros. */ /* Calculate array dimension. */ #define DIM(array) (sizeof (array) / sizeof (*array)) /* Evaluate EXPRESSION, setting VARIABLE to the return code, if VARIABLE is zero. */ #define SET_UNLESS_NONZERO(variable, tmp_variable, expression) \ do \ { \ tmp_variable = expression; \ if ((! variable) && tmp_variable) \ variable = tmp_variable; \ } \ while (0) /* * List manipulation. */ /* Add STREAM to the list of registered stream objects. */ static int es_list_add (estream_t stream) { estream_list_t list_obj; int ret; list_obj = MEM_ALLOC (sizeof (*list_obj)); if (! list_obj) ret = -1; else { ESTREAM_LIST_LOCK; list_obj->car = stream; list_obj->cdr = estream_list; list_obj->prev_cdr = &estream_list; if (estream_list) estream_list->prev_cdr = &list_obj->cdr; estream_list = list_obj; ESTREAM_LIST_UNLOCK; ret = 0; } return ret; } /* Remove STREAM from the list of registered stream objects. */ static void es_list_remove (estream_t stream) { estream_list_t list_obj; ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) if (list_obj->car == stream) { *list_obj->prev_cdr = list_obj->cdr; if (list_obj->cdr) list_obj->cdr->prev_cdr = list_obj->prev_cdr; MEM_FREE (list_obj); break; } ESTREAM_LIST_UNLOCK; } /* Type of an stream-iterator-function. */ typedef int (*estream_iterator_t) (estream_t stream); /* Iterate over list of registered streams, calling ITERATOR for each of them. */ static int es_list_iterate (estream_iterator_t iterator) { estream_list_t list_obj; int ret = 0; ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) ret |= (*iterator) (list_obj->car); ESTREAM_LIST_UNLOCK; return ret; } /* * Initialization. */ static int es_init_do (void) { int err; err = ESTREAM_THREADING_INIT (); return err; } /* * I/O methods. */ /* Implementation of Memory I/O. */ /* Cookie for memory objects. */ typedef struct estream_cookie_mem { unsigned int flags; /* Open flags. */ unsigned char *memory; /* Data. */ size_t memory_size; /* Size of MEMORY. */ size_t offset; /* Current offset in MEMORY. */ size_t data_len; /* Length of data in MEMORY. */ size_t block_size; /* Block size. */ unsigned int grow: 1; /* MEMORY is allowed to grow. */ unsigned int append_zero: 1; /* Append zero after data. */ unsigned int dont_free: 1; /* Append zero after data. */ char **ptr; size_t *size; func_realloc_t func_realloc; func_free_t func_free; } *estream_cookie_mem_t; /* Create function for memory objects. */ static int es_func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie, unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len, size_t block_size, unsigned int grow, unsigned int append_zero, unsigned int dont_free, char **ptr, size_t *size, func_realloc_t func_realloc, func_free_t func_free, unsigned int flags) { estream_cookie_mem_t mem_cookie; int err; mem_cookie = MEM_ALLOC (sizeof (*mem_cookie)); if (! mem_cookie) err = -1; else { mem_cookie->flags = flags; mem_cookie->memory = data; mem_cookie->memory_size = data_n; mem_cookie->offset = 0; mem_cookie->data_len = data_len; mem_cookie->block_size = block_size; mem_cookie->grow = grow ? 1 : 0; mem_cookie->append_zero = append_zero ? 1 : 0; mem_cookie->dont_free = dont_free ? 1 : 0; mem_cookie->ptr = ptr; mem_cookie->size = size; mem_cookie->func_realloc = func_realloc ? func_realloc : MEM_REALLOC; mem_cookie->func_free = func_free ? func_free : MEM_FREE; mem_cookie->offset = 0; *cookie = mem_cookie; err = 0; } return err; } /* Read function for memory objects. */ static ssize_t es_func_mem_read (void *cookie, void *buffer, size_t size) { estream_cookie_mem_t mem_cookie = cookie; ssize_t ret; if (size > mem_cookie->data_len - mem_cookie->offset) size = mem_cookie->data_len - mem_cookie->offset; if (size) { memcpy (buffer, mem_cookie->memory + mem_cookie->offset, size); mem_cookie->offset += size; } ret = size; return ret; } /* Write function for memory objects. */ static ssize_t es_func_mem_write (void *cookie, const void *buffer, size_t size) { estream_cookie_mem_t mem_cookie = cookie; func_realloc_t func_realloc = mem_cookie->func_realloc; unsigned char *memory_new; size_t newsize; ssize_t ret; int err; if (size) { /* Regular write. */ if (mem_cookie->flags & O_APPEND) /* Append to data. */ mem_cookie->offset = mem_cookie->data_len; if (! mem_cookie->grow) if (size > mem_cookie->memory_size - mem_cookie->offset) size = mem_cookie->memory_size - mem_cookie->offset; err = 0; while (size > (mem_cookie->memory_size - mem_cookie->offset)) { memory_new = (*func_realloc) (mem_cookie->memory, mem_cookie->memory_size + mem_cookie->block_size); if (! memory_new) { err = -1; break; } else { if (mem_cookie->memory != memory_new) mem_cookie->memory = memory_new; mem_cookie->memory_size += mem_cookie->block_size; } } if (err) goto out; if (size) { memcpy (mem_cookie->memory + mem_cookie->offset, buffer, size); if (mem_cookie->offset + size > mem_cookie->data_len) mem_cookie->data_len = mem_cookie->offset + size; mem_cookie->offset += size; } } else { /* Flush. */ err = 0; if (mem_cookie->append_zero) { if (mem_cookie->data_len >= mem_cookie->memory_size) { newsize = BUFFER_ROUND_TO_BLOCK (mem_cookie->data_len + 1, mem_cookie->block_size) * mem_cookie->block_size; memory_new = (*func_realloc) (mem_cookie->memory, newsize); if (! memory_new) { err = -1; goto out; } if (mem_cookie->memory != memory_new) mem_cookie->memory = memory_new; mem_cookie->memory_size = newsize; } mem_cookie->memory[mem_cookie->data_len + 1] = 0; } /* Return information to user if necessary. */ if (mem_cookie->ptr) *mem_cookie->ptr = (char *) mem_cookie->memory; if (mem_cookie->size) *mem_cookie->size = mem_cookie->data_len; } out: if (err) ret = -1; else ret = size; return ret; } /* Seek function for memory objects. */ static int es_func_mem_seek (void *cookie, off_t *offset, int whence) { estream_cookie_mem_t mem_cookie = cookie; off_t pos_new; int err = 0; switch (whence) { case SEEK_SET: pos_new = *offset; break; case SEEK_CUR: pos_new = mem_cookie->offset += *offset; break; case SEEK_END: pos_new = mem_cookie->data_len += *offset; break; default: /* Never reached. */ pos_new = 0; } if (pos_new > mem_cookie->memory_size) { /* Grow buffer if possible. */ if (mem_cookie->grow) { func_realloc_t func_realloc = mem_cookie->func_realloc; size_t newsize; void *p; newsize = BUFFER_ROUND_TO_BLOCK (pos_new, mem_cookie->block_size); p = (*func_realloc) (mem_cookie->memory, newsize); if (! p) { err = -1; goto out; } else { if (mem_cookie->memory != p) mem_cookie->memory = p; mem_cookie->memory_size = newsize; } } else { errno = EINVAL; err = -1; goto out; } } if (pos_new > mem_cookie->data_len) /* Fill spare space with zeroes. */ memset (mem_cookie->memory + mem_cookie->data_len, 0, pos_new - mem_cookie->data_len); mem_cookie->offset = pos_new; *offset = pos_new; out: return err; } /* Destroy function for memory objects. */ static int es_func_mem_destroy (void *cookie) { estream_cookie_mem_t mem_cookie = cookie; func_free_t func_free = mem_cookie->func_free; if (! mem_cookie->dont_free) (*func_free) (mem_cookie->memory); MEM_FREE (mem_cookie); return 0; } static es_cookie_io_functions_t estream_functions_mem = { es_func_mem_read, es_func_mem_write, es_func_mem_seek, es_func_mem_destroy, }; /* Implementation of fd I/O. */ /* Cookie for fd objects. */ typedef struct estream_cookie_fd { int fd; } *estream_cookie_fd_t; /* Create function for fd objects. */ static int es_func_fd_create (void **cookie, int fd, unsigned int flags) { estream_cookie_fd_t fd_cookie; int err; fd_cookie = MEM_ALLOC (sizeof (*fd_cookie)); if (! fd_cookie) err = -1; else { fd_cookie->fd = fd; *cookie = fd_cookie; err = 0; } return err; } /* Read function for fd objects. */ static ssize_t es_func_fd_read (void *cookie, void *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_read; do bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size); while (bytes_read == -1 && errno == EINTR); return bytes_read; } /* Write function for fd objects. */ static ssize_t es_func_fd_write (void *cookie, const void *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_written; do bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size); while (bytes_written == -1 && errno == EINTR); return bytes_written; } /* Seek function for fd objects. */ static int es_func_fd_seek (void *cookie, off_t *offset, int whence) { estream_cookie_fd_t file_cookie = cookie; off_t offset_new; int err; offset_new = lseek (file_cookie->fd, *offset, whence); if (offset_new == -1) err = -1; else { *offset = offset_new; err = 0; } return err; } /* Destroy function for fd objects. */ static int es_func_fd_destroy (void *cookie) { estream_cookie_fd_t fd_cookie = cookie; int err; if (fd_cookie) { err = close (fd_cookie->fd); MEM_FREE (fd_cookie); } else err = 0; return err; } static es_cookie_io_functions_t estream_functions_fd = { es_func_fd_read, es_func_fd_write, es_func_fd_seek, es_func_fd_destroy }; /* Implementation of file I/O. */ /* Create function for file objects. */ static int es_func_file_create (void **cookie, int *filedes, const char *path, unsigned int flags) { estream_cookie_fd_t file_cookie; int err; int fd; err = 0; fd = -1; file_cookie = MEM_ALLOC (sizeof (*file_cookie)); if (! file_cookie) { err = -1; goto out; } fd = open (path, flags, ES_DEFAULT_OPEN_MODE); if (fd == -1) { err = -1; goto out; } file_cookie->fd = fd; *cookie = file_cookie; *filedes = fd; out: if (err) MEM_FREE (file_cookie); return err; } static es_cookie_io_functions_t estream_functions_file = { es_func_fd_read, es_func_fd_write, es_func_fd_seek, es_func_fd_destroy }; /* Stream primitives. */ static int es_convert_mode (const char *mode, unsigned int *flags) { struct { const char *mode; unsigned int flags; } mode_flags[] = { { "r", O_RDONLY }, { "rb", O_RDONLY }, { "w", O_WRONLY | O_TRUNC | O_CREAT }, { "wb", O_WRONLY | O_TRUNC | O_CREAT }, { "a", O_WRONLY | O_APPEND | O_CREAT }, { "ab", O_WRONLY | O_APPEND | O_CREAT }, { "r+", O_RDWR }, { "rb+", O_RDWR }, { "r+b", O_RDONLY | O_WRONLY }, { "w+", O_RDWR | O_TRUNC | O_CREAT }, { "wb+", O_RDWR | O_TRUNC | O_CREAT }, { "w+b", O_RDWR | O_TRUNC | O_CREAT }, { "a+", O_RDWR | O_CREAT | O_APPEND }, { "ab+", O_RDWR | O_CREAT | O_APPEND }, { "a+b", O_RDWR | O_CREAT | O_APPEND } }; unsigned int i; int err; for (i = 0; i < DIM (mode_flags); i++) if (! strcmp (mode_flags[i].mode, mode)) break; if (i == DIM (mode_flags)) { errno = EINVAL; err = -1; } else { err = 0; *flags = mode_flags[i].flags; } return err; } /* * Low level stream functionality. */ static int es_fill (estream_t stream) { size_t bytes_read = 0; int err; if (!stream->intern->func_read) { errno = EOPNOTSUPP; err = -1; } else { es_cookie_read_function_t func_read = stream->intern->func_read; ssize_t ret; ret = (*func_read) (stream->intern->cookie, stream->buffer, stream->buffer_size); if (ret == -1) { bytes_read = 0; err = -1; } else { bytes_read = ret; err = 0; } } if (err) stream->intern->indicators.err = 1; else if (!bytes_read) stream->intern->indicators.eof = 1; stream->intern->offset += stream->data_len; stream->data_len = bytes_read; stream->data_offset = 0; return err; } static int es_flush (estream_t stream) { es_cookie_write_function_t func_write = stream->intern->func_write; int err; assert (stream->flags & ES_FLAG_WRITING); if (stream->data_offset) { size_t bytes_written; size_t data_flushed; ssize_t ret; if (! func_write) { err = EOPNOTSUPP; goto out; } /* Note: to prevent an endless loop caused by user-provided write-functions that pretend to have written more bytes than they were asked to write, we have to check for "(stream->data_offset - data_flushed) > 0" instead of "stream->data_offset - data_flushed". */ data_flushed = 0; err = 0; while ((((ssize_t) (stream->data_offset - data_flushed)) > 0) && (! err)) { ret = (*func_write) (stream->intern->cookie, stream->buffer + data_flushed, stream->data_offset - data_flushed); if (ret == -1) { bytes_written = 0; err = -1; } else bytes_written = ret; data_flushed += bytes_written; if (err) break; } stream->data_flushed += data_flushed; if (stream->data_offset == data_flushed) { stream->intern->offset += stream->data_offset; stream->data_offset = 0; stream->data_flushed = 0; /* Propagate flush event. */ (*func_write) (stream->intern->cookie, NULL, 0); } } else err = 0; out: if (err) stream->intern->indicators.err = 1; return err; } /* Discard buffered data for STREAM. */ static void es_empty (estream_t stream) { assert (! (stream->flags & ES_FLAG_WRITING)); stream->data_len = 0; stream->data_offset = 0; stream->unread_data_len = 0; } /* Initialize STREAM. */ static void es_initialize (estream_t stream, void *cookie, int fd, es_cookie_io_functions_t functions) { stream->intern->cookie = cookie; stream->intern->opaque = NULL; stream->intern->offset = 0; stream->intern->func_read = functions.func_read; stream->intern->func_write = functions.func_write; stream->intern->func_seek = functions.func_seek; stream->intern->func_close = functions.func_close; stream->intern->strategy = _IOFBF; stream->intern->fd = fd; stream->intern->indicators.err = 0; stream->intern->indicators.eof = 0; stream->intern->deallocate_buffer = 0; stream->data_len = 0; stream->data_offset = 0; stream->data_flushed = 0; stream->unread_data_len = 0; stream->flags = 0; } /* Deinitialize STREAM. */ static int es_deinitialize (estream_t stream) { es_cookie_close_function_t func_close; int err, tmp_err; func_close = stream->intern->func_close; err = 0; if (stream->flags & ES_FLAG_WRITING) SET_UNLESS_NONZERO (err, tmp_err, es_flush (stream)); if (func_close) SET_UNLESS_NONZERO (err, tmp_err, (*func_close) (stream->intern->cookie)); return err; } /* Create a new stream object, initialize it. */ static int es_create (estream_t *stream, void *cookie, int fd, es_cookie_io_functions_t functions) { estream_internal_t stream_internal_new; estream_t stream_new; int err; stream_new = NULL; stream_internal_new = NULL; stream_new = MEM_ALLOC (sizeof (*stream_new)); if (! stream_new) { err = -1; goto out; } stream_internal_new = MEM_ALLOC (sizeof (*stream_internal_new)); if (! stream_internal_new) { err = -1; goto out; } stream_new->buffer = stream_internal_new->buffer; stream_new->buffer_size = sizeof (stream_internal_new->buffer); stream_new->unread_buffer = stream_internal_new->unread_buffer; stream_new->unread_buffer_size = sizeof (stream_internal_new->unread_buffer); stream_new->intern = stream_internal_new; ESTREAM_MUTEX_INITIALIZE (stream_new->intern->lock); es_initialize (stream_new, cookie, fd, functions); err = es_list_add (stream_new); if (err) goto out; *stream = stream_new; out: if (err) { if (stream_new) { es_deinitialize (stream_new); MEM_FREE (stream_new); } } return err; } /* Deinitialize a stream object and destroy it. */ static int es_destroy (estream_t stream) { int err = 0; if (stream) { es_list_remove (stream); err = es_deinitialize (stream); MEM_FREE (stream->intern); MEM_FREE (stream); } return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER in unbuffered-mode, storing the amount of bytes read in *BYTES_READ. */ static int es_read_nbf (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { es_cookie_read_function_t func_read = stream->intern->func_read; size_t data_read; ssize_t ret; int err; data_read = 0; err = 0; while (bytes_to_read - data_read) { ret = (*func_read) (stream->intern->cookie, buffer + data_read, bytes_to_read - data_read); if (ret == -1) { err = -1; break; } else if (ret) data_read += ret; else break; } stream->intern->offset += data_read; *bytes_read = data_read; return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER in fully-buffered-mode, storing the amount of bytes read in *BYTES_READ. */ static int es_read_fbf (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { size_t data_available; size_t data_to_read; size_t data_read; int err; data_read = 0; err = 0; while ((bytes_to_read - data_read) && (! err)) { if (stream->data_offset == stream->data_len) { /* Nothing more to read in current container, try to fill container with new data. */ err = es_fill (stream); if (! err) if (! stream->data_len) /* Filling did not result in any data read. */ break; } if (! err) { /* Filling resulted in some new data. */ data_to_read = bytes_to_read - data_read; data_available = stream->data_len - stream->data_offset; if (data_to_read > data_available) data_to_read = data_available; memcpy (buffer + data_read, stream->buffer + stream->data_offset, data_to_read); stream->data_offset += data_to_read; data_read += data_to_read; } } *bytes_read = data_read; return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER in line-buffered-mode, storing the amount of bytes read in *BYTES_READ. */ static int es_read_lbf (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { int err; err = es_read_fbf (stream, buffer, bytes_to_read, bytes_read); return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER, storing *the amount of bytes read in BYTES_READ. */ static int es_readn (estream_t ES__RESTRICT stream, void *ES__RESTRICT buffer_arg, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { unsigned char *buffer = (unsigned char *)buffer_arg; size_t data_read_unread, data_read; int err; data_read_unread = 0; data_read = 0; err = 0; if (stream->flags & ES_FLAG_WRITING) { /* Switching to reading mode -> flush output. */ err = es_flush (stream); if (err) goto out; stream->flags &= ~ES_FLAG_WRITING; } /* Read unread data first. */ while ((bytes_to_read - data_read_unread) && stream->unread_data_len) { buffer[data_read_unread] = stream->unread_buffer[stream->unread_data_len - 1]; stream->unread_data_len--; data_read_unread++; } switch (stream->intern->strategy) { case _IONBF: err = es_read_nbf (stream, buffer + data_read_unread, bytes_to_read - data_read_unread, &data_read); break; case _IOLBF: err = es_read_lbf (stream, buffer + data_read_unread, bytes_to_read - data_read_unread, &data_read); break; case _IOFBF: err = es_read_fbf (stream, buffer + data_read_unread, bytes_to_read - data_read_unread, &data_read); break; } out: if (bytes_read) *bytes_read = data_read_unread + data_read; return err; } /* Try to unread DATA_N bytes from DATA into STREAM, storing the amount of bytes succesfully unread in *BYTES_UNREAD. */ static void es_unreadn (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT data, size_t data_n, size_t *ES__RESTRICT bytes_unread) { size_t space_left; space_left = stream->unread_buffer_size - stream->unread_data_len; if (data_n > space_left) data_n = space_left; if (! data_n) goto out; memcpy (stream->unread_buffer + stream->unread_data_len, data, data_n); stream->unread_data_len += data_n; stream->intern->indicators.eof = 0; out: if (bytes_unread) *bytes_unread = data_n; } /* Seek in STREAM. */ static int es_seek (estream_t ES__RESTRICT stream, off_t offset, int whence, off_t *ES__RESTRICT offset_new) { es_cookie_seek_function_t func_seek = stream->intern->func_seek; int err, ret; off_t off; if (! func_seek) { errno = EOPNOTSUPP; err = -1; goto out; } if (stream->flags & ES_FLAG_WRITING) { /* Flush data first in order to prevent flushing it to the wrong offset. */ err = es_flush (stream); if (err) goto out; stream->flags &= ~ES_FLAG_WRITING; } off = offset; if (whence == SEEK_CUR) { off = off - stream->data_len + stream->data_offset; off -= stream->unread_data_len; } ret = (*func_seek) (stream->intern->cookie, &off, whence); if (ret == -1) { err = -1; goto out; } err = 0; es_empty (stream); if (offset_new) *offset_new = off; stream->intern->indicators.eof = 0; stream->intern->offset = off; out: if (err) stream->intern->indicators.err = 1; return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in unbuffered-mode, storing the amount of bytes written in *BYTES_WRITTEN. */ static int es_write_nbf (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { es_cookie_write_function_t func_write = stream->intern->func_write; size_t data_written; ssize_t ret; int err; if (bytes_to_write && (! func_write)) { err = EOPNOTSUPP; goto out; } data_written = 0; err = 0; while (bytes_to_write - data_written) { ret = (*func_write) (stream->intern->cookie, buffer + data_written, bytes_to_write - data_written); if (ret == -1) { err = -1; break; } else data_written += ret; } stream->intern->offset += data_written; *bytes_written = data_written; out: return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in fully-buffered-mode, storing the amount of bytes written in *BYTES_WRITTEN. */ static int es_write_fbf (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { size_t space_available; size_t data_to_write; size_t data_written; int err; data_written = 0; err = 0; while ((bytes_to_write - data_written) && (! err)) { if (stream->data_offset == stream->buffer_size) /* Container full, flush buffer. */ err = es_flush (stream); if (! err) { /* Flushing resulted in empty container. */ data_to_write = bytes_to_write - data_written; space_available = stream->buffer_size - stream->data_offset; if (data_to_write > space_available) data_to_write = space_available; memcpy (stream->buffer + stream->data_offset, buffer + data_written, data_to_write); stream->data_offset += data_to_write; data_written += data_to_write; } } *bytes_written = data_written; return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in line-buffered-mode, storing the amount of bytes written in *BYTES_WRITTEN. */ static int es_write_lbf (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { size_t data_flushed = 0; size_t data_buffered = 0; unsigned char *nlp; int err = 0; nlp = memrchr (buffer, '\n', bytes_to_write); if (nlp) { /* Found a newline, directly write up to (including) this character. */ err = es_flush (stream); if (!err) err = es_write_nbf (stream, buffer, nlp - buffer + 1, &data_flushed); } if (!err) { /* Write remaining data fully buffered. */ err = es_write_fbf (stream, buffer + data_flushed, bytes_to_write - data_flushed, &data_buffered); } *bytes_written = data_flushed + data_buffered; return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in, storing the amount of bytes written in BYTES_WRITTEN. */ static int es_writen (estream_t ES__RESTRICT stream, const void *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { size_t data_written; int err; data_written = 0; err = 0; if (! (stream->flags & ES_FLAG_WRITING)) { /* Switching to writing mode -> discard input data and seek to position at which reading has stopped. */ err = es_seek (stream, 0, SEEK_CUR, NULL); if (err) { if (errno == ESPIPE) err = 0; else goto out; } } switch (stream->intern->strategy) { case _IONBF: err = es_write_nbf (stream, buffer, bytes_to_write, &data_written); break; case _IOLBF: err = es_write_lbf (stream, buffer, bytes_to_write, &data_written); break; case _IOFBF: err = es_write_fbf (stream, buffer, bytes_to_write, &data_written); break; } out: if (bytes_written) *bytes_written = data_written; if (data_written) if (! (stream->flags & ES_FLAG_WRITING)) stream->flags |= ES_FLAG_WRITING; return err; } static int es_peek (estream_t ES__RESTRICT stream, unsigned char **ES__RESTRICT data, size_t *ES__RESTRICT data_len) { int err; if (stream->flags & ES_FLAG_WRITING) { /* Switching to reading mode -> flush output. */ err = es_flush (stream); if (err) goto out; stream->flags &= ~ES_FLAG_WRITING; } if (stream->data_offset == stream->data_len) { /* Refill container. */ err = es_fill (stream); if (err) goto out; } if (data) *data = stream->buffer + stream->data_offset; if (data_len) *data_len = stream->data_len - stream->data_offset; err = 0; out: return err; } /* Skip SIZE bytes of input data contained in buffer. */ static int es_skip (estream_t stream, size_t size) { int err; if (stream->data_offset + size > stream->data_len) { errno = EINVAL; err = -1; } else { stream->data_offset += size; err = 0; } return err; } static int es_read_line (estream_t ES__RESTRICT stream, size_t max_length, char *ES__RESTRICT *ES__RESTRICT line, size_t *ES__RESTRICT line_length) { size_t space_left; size_t line_size; estream_t line_stream; char *line_new; void *line_stream_cookie; char *newline; unsigned char *data; size_t data_len; int err; line_new = NULL; line_stream = NULL; line_stream_cookie = NULL; err = es_func_mem_create (&line_stream_cookie, NULL, 0, 0, BUFFER_BLOCK_SIZE, 1, 0, 0, NULL, 0, MEM_REALLOC, MEM_FREE, O_RDWR); if (err) goto out; err = es_create (&line_stream, line_stream_cookie, -1, estream_functions_mem); if (err) goto out; space_left = max_length; line_size = 0; while (1) { if (max_length && (space_left == 1)) break; err = es_peek (stream, &data, &data_len); if (err || (! data_len)) break; if (data_len > (space_left - 1)) data_len = space_left - 1; newline = memchr (data, '\n', data_len); if (newline) { data_len = (newline - (char *) data) + 1; err = es_write (line_stream, data, data_len, NULL); if (! err) { space_left -= data_len; line_size += data_len; es_skip (stream, data_len); break; } } else { err = es_write (line_stream, data, data_len, NULL); if (! err) { space_left -= data_len; line_size += data_len; es_skip (stream, data_len); } } if (err) break; } if (err) goto out; /* Complete line has been written to line_stream. */ if ((max_length > 1) && (! line_size)) { stream->intern->indicators.eof = 1; goto out; } err = es_seek (line_stream, 0, SEEK_SET, NULL); if (err) goto out; if (! *line) { line_new = MEM_ALLOC (line_size + 1); if (! line_new) { err = -1; goto out; } } else line_new = *line; err = es_read (line_stream, line_new, line_size, NULL); if (err) goto out; line_new[line_size] = '\0'; if (! *line) *line = line_new; if (line_length) *line_length = line_size; out: if (line_stream) es_destroy (line_stream); else if (line_stream_cookie) es_func_mem_destroy (line_stream_cookie); if (err) { if (! *line) MEM_FREE (line_new); stream->intern->indicators.err = 1; } return err; } static int es_print (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap) { char data[BUFFER_BLOCK_SIZE]; size_t bytes_written; size_t bytes_read; FILE *tmp_stream; int err; bytes_written = 0; tmp_stream = NULL; err = 0; tmp_stream = tmpfile (); if (! tmp_stream) { err = errno; goto out; } err = vfprintf (tmp_stream, format, ap); if (err < 0) goto out; err = fseek (tmp_stream, 0, SEEK_SET); if (err) goto out; while (1) { bytes_read = fread (data, 1, sizeof (data), tmp_stream); if (ferror (tmp_stream)) { err = -1; break; } err = es_writen (stream, data, bytes_read, NULL); if (err) break; else bytes_written += bytes_read; if (feof (tmp_stream)) break; } if (err) goto out; out: if (tmp_stream) fclose (tmp_stream); return err ? -1 : bytes_written; } static void es_set_indicators (estream_t stream, int ind_err, int ind_eof) { if (ind_err != -1) stream->intern->indicators.err = ind_err ? 1 : 0; if (ind_eof != -1) stream->intern->indicators.eof = ind_eof ? 1 : 0; } static int es_get_indicator (estream_t stream, int ind_err, int ind_eof) { int ret = 0; if (ind_err) ret = stream->intern->indicators.err; else if (ind_eof) ret = stream->intern->indicators.eof; return ret; } static int es_set_buffering (estream_t ES__RESTRICT stream, char *ES__RESTRICT buffer, int mode, size_t size) { int err; /* Flush or empty buffer depending on mode. */ if (stream->flags & ES_FLAG_WRITING) { err = es_flush (stream); if (err) goto out; } else es_empty (stream); es_set_indicators (stream, -1, 0); /* Free old buffer in case that was allocated by this function. */ if (stream->intern->deallocate_buffer) { stream->intern->deallocate_buffer = 0; MEM_FREE (stream->buffer); stream->buffer = NULL; } if (mode == _IONBF) stream->buffer_size = 0; else { void *buffer_new; if (buffer) buffer_new = buffer; else { buffer_new = MEM_ALLOC (size); if (! buffer_new) { err = -1; goto out; } } stream->buffer = buffer_new; stream->buffer_size = size; if (! buffer) stream->intern->deallocate_buffer = 1; } stream->intern->strategy = mode; err = 0; out: return err; } static off_t es_offset_calculate (estream_t stream) { off_t offset; offset = stream->intern->offset + stream->data_offset; if (offset < stream->unread_data_len) /* Offset undefined. */ offset = 0; else offset -= stream->unread_data_len; return offset; } static void es_opaque_ctrl (estream_t ES__RESTRICT stream, void *ES__RESTRICT opaque_new, void **ES__RESTRICT opaque_old) { if (opaque_old) *opaque_old = stream->intern->opaque; if (opaque_new) stream->intern->opaque = opaque_new; } static int es_get_fd (estream_t stream) { return stream->intern->fd; } /* API. */ int es_init (void) { int err; err = es_init_do (); return err; } estream_t es_fopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; int fd; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_file_create (&cookie, &fd, path, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, fd, estream_functions_file); if (err) goto out; out: if (err && create_called) (*estream_functions_file.func_close) (cookie); return stream; } estream_t es_mopen (unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len, unsigned int grow, func_realloc_t func_realloc, func_free_t func_free, const char *ES__RESTRICT mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; cookie = 0; stream = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_mem_create (&cookie, data, data_n, data_len, BUFFER_BLOCK_SIZE, grow, 0, 0, NULL, 0, func_realloc, func_free, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, -1, estream_functions_mem); out: if (err && create_called) (*estream_functions_mem.func_close) (cookie); return stream; } estream_t es_open_memstream (char **ptr, size_t *size) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; flags = O_RDWR; create_called = 0; stream = NULL; cookie = 0; err = es_func_mem_create (&cookie, NULL, 0, 0, BUFFER_BLOCK_SIZE, 1, 1, 1, ptr, size, MEM_REALLOC, MEM_FREE, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, -1, estream_functions_mem); out: if (err && create_called) (*estream_functions_mem.func_close) (cookie); return stream; } estream_t es_fopencookie (void *ES__RESTRICT cookie, const char *ES__RESTRICT mode, es_cookie_io_functions_t functions) { unsigned int flags; estream_t stream; int err; stream = NULL; flags = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_create (&stream, cookie, -1, functions); if (err) goto out; out: return stream; } estream_t es_fdopen (int filedes, const char *mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_fd_create (&cookie, filedes, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, filedes, estream_functions_fd); out: if (err && create_called) (*estream_functions_fd.func_close) (cookie); return stream; } estream_t es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode, estream_t ES__RESTRICT stream) { int err; if (path) { unsigned int flags; int create_called; void *cookie; int fd; cookie = NULL; create_called = 0; ESTREAM_LOCK (stream); es_deinitialize (stream); err = es_convert_mode (mode, &flags); if (err) goto leave; err = es_func_file_create (&cookie, &fd, path, flags); if (err) goto leave; create_called = 1; es_initialize (stream, cookie, fd, estream_functions_file); leave: if (err) { if (create_called) es_func_fd_destroy (cookie); es_destroy (stream); stream = NULL; } else ESTREAM_UNLOCK (stream); } else { /* FIXME? We don't support re-opening at the moment. */ errno = EINVAL; es_deinitialize (stream); es_destroy (stream); stream = NULL; } return stream; } int es_fclose (estream_t stream) { int err; err = es_destroy (stream); return err; } int es_fileno_unlocked (estream_t stream) { return es_get_fd (stream); } void es_flockfile (estream_t stream) { ESTREAM_LOCK (stream); } int es_ftrylockfile (estream_t stream) { return ESTREAM_TRYLOCK (stream); } void es_funlockfile (estream_t stream) { ESTREAM_UNLOCK (stream); } int es_fileno (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_fileno_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } int es_feof_unlocked (estream_t stream) { return es_get_indicator (stream, 0, 1); } int es_feof (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_feof_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } int es_ferror_unlocked (estream_t stream) { return es_get_indicator (stream, 1, 0); } int es_ferror (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_ferror_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } void es_clearerr_unlocked (estream_t stream) { es_set_indicators (stream, 0, 0); } void es_clearerr (estream_t stream) { ESTREAM_LOCK (stream); es_clearerr_unlocked (stream); ESTREAM_UNLOCK (stream); } int es_fflush (estream_t stream) { int err; if (stream) { ESTREAM_LOCK (stream); if (stream->flags & ES_FLAG_WRITING) err = es_flush (stream); else { es_empty (stream); err = 0; } ESTREAM_UNLOCK (stream); } else err = es_list_iterate (es_fflush); return err ? EOF : 0; } int es_fseek (estream_t stream, long int offset, int whence) { int err; ESTREAM_LOCK (stream); err = es_seek (stream, offset, whence, NULL); ESTREAM_UNLOCK (stream); return err; } int es_fseeko (estream_t stream, off_t offset, int whence) { int err; ESTREAM_LOCK (stream); err = es_seek (stream, offset, whence, NULL); ESTREAM_UNLOCK (stream); return err; } long int es_ftell (estream_t stream) { long int ret; ESTREAM_LOCK (stream); ret = es_offset_calculate (stream); ESTREAM_UNLOCK (stream); return ret; } off_t es_ftello (estream_t stream) { off_t ret = -1; ESTREAM_LOCK (stream); ret = es_offset_calculate (stream); ESTREAM_UNLOCK (stream); return ret; } void es_rewind (estream_t stream) { ESTREAM_LOCK (stream); es_seek (stream, 0L, SEEK_SET, NULL); es_set_indicators (stream, 0, -1); ESTREAM_UNLOCK (stream); } int _es_getc_underflow (estream_t stream) { int err; unsigned char c; size_t bytes_read; err = es_readn (stream, &c, 1, &bytes_read); return (err || (! bytes_read)) ? EOF : c; } int _es_putc_overflow (int c, estream_t stream) { unsigned char d = c; int err; err = es_writen (stream, &d, 1, NULL); return err ? EOF : c; } int es_fgetc (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_getc_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } int es_fputc (int c, estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_putc_unlocked (c, stream); ESTREAM_UNLOCK (stream); return ret; } int es_ungetc (int c, estream_t stream) { unsigned char data = (unsigned char) c; size_t data_unread; ESTREAM_LOCK (stream); es_unreadn (stream, &data, 1, &data_unread); ESTREAM_UNLOCK (stream); return data_unread ? c : EOF; } int es_read (estream_t ES__RESTRICT stream, void *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { int err; if (bytes_to_read) { ESTREAM_LOCK (stream); err = es_readn (stream, buffer, bytes_to_read, bytes_read); ESTREAM_UNLOCK (stream); } else err = 0; return err; } int es_write (estream_t ES__RESTRICT stream, const void *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { int err; if (bytes_to_write) { ESTREAM_LOCK (stream); err = es_writen (stream, buffer, bytes_to_write, bytes_written); ESTREAM_UNLOCK (stream); } else err = 0; return err; } size_t es_fread (void *ES__RESTRICT ptr, size_t size, size_t nitems, estream_t ES__RESTRICT stream) { size_t ret, bytes; int err; if (size * nitems) { ESTREAM_LOCK (stream); err = es_readn (stream, ptr, size * nitems, &bytes); ESTREAM_UNLOCK (stream); ret = bytes / size; } else ret = 0; return ret; } size_t es_fwrite (const void *ES__RESTRICT ptr, size_t size, size_t nitems, estream_t ES__RESTRICT stream) { size_t ret, bytes; int err; if (size * nitems) { ESTREAM_LOCK (stream); err = es_writen (stream, ptr, size * nitems, &bytes); ESTREAM_UNLOCK (stream); ret = bytes / size; } else ret = 0; return ret; } char * es_fgets (char *ES__RESTRICT s, int n, estream_t ES__RESTRICT stream) { char *ret = NULL; if (n) { int err; ESTREAM_LOCK (stream); err = es_read_line (stream, n, &s, NULL); ESTREAM_UNLOCK (stream); if (! err) ret = s; } return ret; } int es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream) { size_t length; int err; length = strlen (s); ESTREAM_LOCK (stream); err = es_writen (stream, s, length, NULL); ESTREAM_UNLOCK (stream); return err ? EOF : 0; } ssize_t es_getline (char *ES__RESTRICT *ES__RESTRICT lineptr, size_t *ES__RESTRICT n, estream_t ES__RESTRICT stream) { char *line = NULL; size_t line_n = 0; int err; ESTREAM_LOCK (stream); err = es_read_line (stream, 0, &line, &line_n); ESTREAM_UNLOCK (stream); if (err) goto out; if (*n) { /* Caller wants us to use his buffer. */ if (*n < (line_n + 1)) { /* Provided buffer is too small -> resize. */ void *p; p = MEM_REALLOC (*lineptr, line_n + 1); if (! p) err = -1; else { if (*lineptr != p) *lineptr = p; } } if (! err) { memcpy (*lineptr, line, line_n + 1); if (*n != line_n) *n = line_n; } MEM_FREE (line); } else { /* Caller wants new buffers. */ *lineptr = line; *n = line_n; } out: return err ? err : line_n; } int es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap) { int ret; ESTREAM_LOCK (stream); ret = es_print (stream, format, ap); ESTREAM_UNLOCK (stream); return ret; } int es_fprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, ...) { int ret; va_list ap; va_start (ap, format); ESTREAM_LOCK (stream); ret = es_print (stream, format, ap); ESTREAM_UNLOCK (stream); va_end (ap); return ret; } static int tmpfd (void) { FILE *fp; int fp_fd; int fd; fp = NULL; fd = -1; fp = tmpfile (); if (! fp) goto out; fp_fd = fileno (fp); fd = dup (fp_fd); out: if (fp) fclose (fp); return fd; } estream_t es_tmpfile (void) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; int fd; create_called = 0; stream = NULL; flags = O_RDWR | O_TRUNC | O_CREAT; cookie = NULL; fd = tmpfd (); if (fd == -1) { err = -1; goto out; } err = es_func_fd_create (&cookie, fd, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, fd, estream_functions_fd); out: if (err) { if (create_called) es_func_fd_destroy (cookie); else if (fd != -1) close (fd); stream = NULL; } return stream; } int es_setvbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf, int type, size_t size) { int err; if (((type == _IOFBF) || (type == _IOLBF) || (type == _IONBF)) && (! ((! size) && (type != _IONBF)))) { ESTREAM_LOCK (stream); err = es_set_buffering (stream, buf, type, size); ESTREAM_UNLOCK (stream); } else { errno = EINVAL; err = -1; } return err; } void es_setbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf) { ESTREAM_LOCK (stream); es_set_buffering (stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ); ESTREAM_UNLOCK (stream); } void es_opaque_set (estream_t stream, void *opaque) { ESTREAM_LOCK (stream); es_opaque_ctrl (stream, opaque, NULL); ESTREAM_UNLOCK (stream); } void * es_opaque_get (estream_t stream) { void *opaque; ESTREAM_LOCK (stream); es_opaque_ctrl (stream, NULL, &opaque); ESTREAM_UNLOCK (stream); return opaque; } diff --git a/common/estream.h b/common/estream.h index ebe575926..a9b4847c8 100644 --- a/common/estream.h +++ b/common/estream.h @@ -1,202 +1,203 @@ /* estream.h - Extended stream I/O/ Library - Copyright (C) 2004 g10 Code GmbH - - This file is part of Libestream. - - Libestream 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. - - Libestream 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Libestream; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + * Copyright (C) 2004 g10 Code GmbH + * + * This file is part of Libestream. + * + * Libestream 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. + * + * Libestream 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Libestream; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #ifndef ESTREAM_H #define ESTREAM_H #include #include #include /* Forward declaration for the (opaque) internal type. */ struct estream_internal; /* The definition of this struct is entirely private. You must not use it for anything. It is only here so some functions can be implemented as macros. */ struct es__stream { /* The layout of this struct must never change. It may be grown, but only if all functions which access the new members are versioned. */ /* A pointer to the stream buffer. */ unsigned char *buffer; /* The size of the buffer in bytes. */ size_t buffer_size; /* The length of the usable data in the buffer, only valid when in read mode (see flags). */ size_t data_len; /* The current position of the offset pointer, valid in read and write mode. */ size_t data_offset; size_t data_flushed; unsigned char *unread_buffer; size_t unread_buffer_size; /* The number of unread bytes. */ size_t unread_data_len; /* Various flags. */ #define ES__FLAG_WRITING (1 << 0) unsigned int flags; /* A pointer to our internal data for this stream. */ struct estream_internal *intern; }; /* The opaque type for an estream. */ typedef struct es__stream *estream_t; typedef ssize_t (*es_cookie_read_function_t) (void *cookie, void *buffer, size_t size); typedef ssize_t (*es_cookie_write_function_t) (void *cookie, const void *buffer, size_t size); typedef int (*es_cookie_seek_function_t) (void *cookie, off_t *pos, int whence); typedef int (*es_cookie_close_function_t) (void *cookie); typedef struct es_cookie_io_functions { es_cookie_read_function_t func_read; es_cookie_write_function_t func_write; es_cookie_seek_function_t func_seek; es_cookie_close_function_t func_close; } es_cookie_io_functions_t; #ifndef ES__RESTRICT # if defined __GNUC__ && defined __GNUC_MINOR__ # if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 92)) # define ES__RESTRICT __restrict__ # endif # endif #endif #ifndef ES__RESTRICT # define ES__RESTRICT #endif int es_init (void); estream_t es_fopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode); estream_t es_mopen (unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len, unsigned int grow, void *(*func_realloc) (void *mem, size_t size), void (*func_free) (void *mem), const char *ES__RESTRICT mode); estream_t es_open_memstream (char **ptr, size_t *size); estream_t es_fdopen (int filedes, const char *mode); estream_t es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode, estream_t ES__RESTRICT stream); estream_t es_fopencookie (void *ES__RESTRICT cookie, const char *ES__RESTRICT mode, es_cookie_io_functions_t functions); int es_fclose (estream_t stream); int es_fileno (estream_t stream); int es_fileno_unlocked (estream_t stream); void es_flockfile (estream_t stream); int es_ftrylockfile (estream_t stream); void es_funlockfile (estream_t stream); int es_feof (estream_t stream); int es_feof_unlocked (estream_t stream); int es_ferror (estream_t stream); int es_ferror_unlocked (estream_t stream); void es_clearerr (estream_t stream); void es_clearerr_unlocked (estream_t stream); int es_fflush (estream_t stream); int es_fseek (estream_t stream, long int offset, int whence); int es_fseeko (estream_t stream, off_t offset, int whence); long int es_ftell (estream_t stream); off_t es_ftello (estream_t stream); void es_rewind (estream_t stream); int es_fgetc (estream_t stream); int es_fputc (int c, estream_t stream); int _es_getc_underflow (estream_t stream); int _es_putc_overflow (int c, estream_t stream); #define es_getc_unlocked(stream) \ (((! ((stream)->flags & 1)) \ && ((stream)->data_offset < (stream)->data_len) \ && (! (stream)->unread_data_len)) \ ? ((int) (stream)->buffer[((stream)->data_offset)++]) \ : _es_getc_underflow ((stream))) #define es_putc_unlocked(c, stream) \ ((((stream)->flags & 1) \ && ((stream)->data_offset < (stream)->buffer_size) \ && (c != '\n')) \ ? ((int) ((stream)->buffer[((stream)->data_offset)++] = (c))) \ : _es_putc_overflow ((c), (stream))) #define es_getc(stream) es_fgetc (stream) #define es_putc(c, stream) es_fputc (c, stream) int es_ungetc (int c, estream_t stream); int es_read (estream_t ES__RESTRICT stream, void *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read); int es_write (estream_t ES__RESTRICT stream, const void *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written); size_t es_fread (void *ES__RESTRICT ptr, size_t size, size_t nitems, estream_t ES__RESTRICT stream); size_t es_fwrite (const void *ES__RESTRICT ptr, size_t size, size_t memb, estream_t ES__RESTRICT stream); char *es_fgets (char *ES__RESTRICT s, int n, estream_t ES__RESTRICT stream); int es_fputs (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream); ssize_t es_getline (char *ES__RESTRICT *ES__RESTRICT lineptr, size_t *ES__RESTRICT n, estream_t stream); int es_fprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, ...); int es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap); int es_setvbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf, int mode, size_t size); void es_setbuf (estream_t ES__RESTRICT stream, char *ES__RESTRICT buf); estream_t es_tmpfile (void); void es_opaque_set (estream_t ES__RESTRICT stream, void *ES__RESTRICT opaque); void *es_opaque_get (estream_t stream); #endif /*ESTREAM_H*/ diff --git a/common/exechelp.c b/common/exechelp.c index dc0a6b0e1..e64b69022 100644 --- a/common/exechelp.c +++ b/common/exechelp.c @@ -1,482 +1,483 @@ /* exechelp.c - fork and exec helpers * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #ifdef USE_GNU_PTH #include #endif #ifndef HAVE_W32_SYSTEM #include #endif #include "util.h" #include "i18n.h" #include "exechelp.h" /* Define to 1 do enable debugging. */ #define DEBUG_W32_SPAWN 1 #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* We have the usual problem here: Some modules are linked against pth and some are not. However we want to use pth_fork and pth_waitpid here. Using a weak symbol works but is not portable - we should provide a an explicit dummy pth module instead of using the pragma. */ #ifndef _WIN32 #pragma weak pth_fork #pragma weak pth_waitpid #endif #ifdef HAVE_W32_SYSTEM /* We assume that a HANDLE can be represented by an int which should be true for all i386 systems (HANDLE is defined as void *) and these are the only systems for which Windows is available. Further we assume that -1 denotes an invalid handle. */ # define fd_to_handle(a) ((HANDLE)(a)) # define handle_to_fd(a) ((int)(a)) # define pid_to_handle(a) ((HANDLE)(a)) # define handle_to_pid(a) ((int)(a)) #endif #ifdef HAVE_W32_SYSTEM /* Build a command line for use with W32's CreateProcess. On success CMDLINE gets the address of a newly allocated string. */ static gpg_error_t build_w32_commandline (const char *pgmname, const char **argv, char **cmdline) { int i, n; const char *s; char *buf, *p; *cmdline = NULL; n = strlen (pgmname); for (i=0; (s=argv[i]); i++) { n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ for (; *s; s++) if (*s == '\"') n++; /* Need to double inner quotes. */ } n++; buf = p = xtrymalloc (n); if (!buf) return gpg_error_from_errno (errno); /* fixme: PGMNAME may not contain spaces etc. */ p = stpcpy (p, pgmname); for (i=0; argv[i]; i++) { if (!*argv[i]) /* Empty string. */ p = stpcpy (p, " \"\""); else if (strpbrk (argv[i], " \t\n\v\f\"")) { p = stpcpy (p, " \""); for (s=argv[i]; *s; s++) { *p++ = *s; if (*s == '\"') *p++ = *s; } *p++ = '\"'; *p = 0; } else p = stpcpy (stpcpy (p, " "), argv[i]); } *cmdline= buf; return 0; } #endif /*HAVE_W32_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* Create pipe where the write end is inheritable. */ static int create_inheritable_pipe (int filedes[2]) { HANDLE r, w, h; SECURITY_ATTRIBUTES sec_attr; memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; if (!CreatePipe (&r, &w, &sec_attr, 0)) return -1; if (!DuplicateHandle (GetCurrentProcess(), w, GetCurrentProcess(), &h, 0, TRUE, DUPLICATE_SAME_ACCESS )) { log_error ("DuplicateHandle failed: %s\n", w32_strerror (-1)); CloseHandle (r); CloseHandle (w); return -1; } CloseHandle (w); w = h; filedes[0] = handle_to_fd (r); filedes[1] = handle_to_fd (w); return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Fork and exec the PGMNAME, connect the file descriptor of INFILE to stdin, write the output to OUTFILE, return a new stream in STATUSFILE for stderr and the pid of the process in PID. The arguments for the process are expected in the NULL terminated array ARGV. The program name itself should not be included there. if PREEXEC is not NULL, that function will be called right before the exec. Returns 0 on success or an error code. */ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], FILE *infile, FILE *outfile, void (*preexec)(void), FILE **statusfile, pid_t *pid) { #ifdef HAVE_W32_SYSTEM gpg_error_t err; SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, /* Returns process handle. */ 0, /* Returns primary thread handle. */ 0, /* Returns pid. */ 0 /* Returns tid. */ }; STARTUPINFO si; int cr_flags; char *cmdline; int fd, fdout, rp[2]; /* Setup return values. */ *statusfile = NULL; *pid = (pid_t)(-1); fflush (infile); rewind (infile); fd = _get_osfhandle (fileno (infile)); fdout = _get_osfhandle (fileno (outfile)); if (fd == -1 || fdout == -1) log_fatal ("no file descriptor for file passed to gnupg_spawn_process\n"); /* Prepare security attributes. */ memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; /* Build the command line. */ err = build_w32_commandline (pgmname, argv, &cmdline); if (err) return err; /* Create a pipe. */ if (create_inheritable_pipe (rp)) { err = gpg_error (GPG_ERR_GENERAL); log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); xfree (cmdline); return err; } /* Start the process. Note that we can't run the PREEXEC function because this would change our own environment. */ memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; si.hStdInput = fd_to_handle (fd); si.hStdOutput = fd_to_handle (fdout); si.hStdError = fd_to_handle (rp[1]); cr_flags = (CREATE_DEFAULT_ERROR_MODE | GetPriorityClass (GetCurrentProcess ()) | CREATE_SUSPENDED); log_debug ("CreateProcess, path=`%s' cmdline=`%s'\n", pgmname, cmdline); if (!CreateProcess (pgmname, /* Program to start. */ cmdline, /* Command line arguments. */ &sec_attr, /* Process security attributes. */ &sec_attr, /* Thread security attributes. */ TRUE, /* Inherit handles. */ cr_flags, /* Creation flags. */ NULL, /* Environment. */ NULL, /* Use current drive/directory. */ &si, /* Startup information. */ &pi /* Returns process information. */ )) { log_error ("CreateProcess failed: %s\n", w32_strerror (-1)); xfree (cmdline); CloseHandle (fd_to_handle (rp[0])); CloseHandle (fd_to_handle (rp[1])); return gpg_error (GPG_ERR_GENERAL); } xfree (cmdline); cmdline = NULL; /* Close the other end of the pipe. */ CloseHandle (fd_to_handle (rp[1])); log_debug ("CreateProcess ready: hProcess=%p hThread=%p" " dwProcessID=%d dwThreadId=%d\n", pi.hProcess, pi.hThread, (int) pi.dwProcessId, (int) pi.dwThreadId); /* Process ha been created suspended; resume it now. */ ResumeThread (pi.hThread); CloseHandle (pi.hThread); { int x; x = _open_osfhandle (rp[0], 0); if (x == -1) log_error ("failed to translate osfhandle %p\n", (void*)rp[0] ); else { log_debug ("_open_osfhandle %p yields %d\n", (void*)fd, x ); *statusfile = fdopen (x, "r"); } } if (!*statusfile) { err = gpg_error_from_errno (errno); log_error (_("can't fdopen pipe for reading: %s\n"), gpg_strerror (err)); CloseHandle (pi.hProcess); return err; } *pid = handle_to_pid (pi.hProcess); return 0; #else /* !HAVE_W32_SYSTEM */ gpg_error_t err; int fd, fdout, rp[2]; *statusfile = NULL; *pid = (pid_t)(-1); fflush (infile); rewind (infile); fd = fileno (infile); fdout = fileno (outfile); if (fd == -1 || fdout == -1) log_fatal ("no file descriptor for file passed to gnupg_spawn_process\n"); if (pipe (rp) == -1) { err = gpg_error_from_errno (errno); log_error (_("error creating a pipe: %s\n"), strerror (errno)); return err; } #ifdef USE_GNU_PTH *pid = pth_fork? pth_fork () : fork (); #else *pid = fork (); #endif if (*pid == (pid_t)(-1)) { err = gpg_error_from_errno (errno); log_error (_("error forking process: %s\n"), strerror (errno)); close (rp[0]); close (rp[1]); return err; } if (!*pid) { /* Child. */ char **arg_list; int n, i, j; /* Create the command line argument array. */ for (i=0; argv[i]; i++) ; arg_list = xcalloc (i+2, sizeof *arg_list); arg_list[0] = strrchr (pgmname, '/'); if (arg_list[0]) arg_list[0]++; else arg_list[0] = xstrdup (pgmname); for (i=0,j=1; argv[i]; i++, j++) arg_list[j] = (char*)argv[i]; /* Connect the infile to stdin. */ if (fd != 0 && dup2 (fd, 0) == -1) log_fatal ("dup2 stdin failed: %s\n", strerror (errno)); /* Connect the outfile to stdout. */ if (fdout != 1 && dup2 (fdout, 1) == -1) log_fatal ("dup2 stdout failed: %s\n", strerror (errno)); /* Connect stderr to our pipe. */ if (rp[1] != 2 && dup2 (rp[1], 2) == -1) log_fatal ("dup2 stderr failed: %s\n", strerror (errno)); /* Close all other files. */ n = sysconf (_SC_OPEN_MAX); if (n < 0) n = MAX_OPEN_FDS; for (i=3; i < n; i++) close(i); errno = 0; if (preexec) preexec (); execv (pgmname, arg_list); /* No way to print anything, as we have closed all streams. */ _exit (127); } /* Parent. */ close (rp[1]); *statusfile = fdopen (rp[0], "r"); if (!*statusfile) { err = gpg_error_from_errno (errno); log_error (_("can't fdopen pipe for reading: %s\n"), strerror (errno)); kill (*pid, SIGTERM); *pid = (pid_t)(-1); return err; } return 0; #endif /* !HAVE_W32_SYSTEM */ } /* Wait for the process identified by PID to terminate. PGMNAME should be the same as suplieed to the spawn fucntion and is only used for diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL for any failures of the spawned program or other error codes.*/ gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid) { gpg_err_code_t ec; #ifdef HAVE_W32_SYSTEM HANDLE proc = fd_to_handle (pid); int code; DWORD exc; if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: We should do a pth_waitpid here. However this has not yet been implemented. A special W32 pth system call would even be better. */ code = WaitForSingleObject (proc, INFINITE); switch (code) { case WAIT_FAILED: log_error (_("waiting for process %d to terminate failed: %s\n"), (int)pid, w32_strerror (-1)); ec = GPG_ERR_GENERAL; break; case WAIT_OBJECT_0: if (!GetExitCodeProcess (proc, &exc)) { log_error (_("error getting exit code of process %d: %s\n"), (int)pid, w32_strerror (-1) ); ec = GPG_ERR_GENERAL; } else if (exc) { log_error (_("error running `%s': exit status %d\n"), pgmname, (int)exc ); ec = GPG_ERR_GENERAL; } else ec = 0; break; default: log_error ("WaitForSingleObject returned unexpected " "code %d for pid %d\n", code, (int)pid ); ec = GPG_ERR_GENERAL; break; } #else /* !HAVE_W32_SYSTEM */ int i, status; if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); #ifdef USE_GNU_PTH i = pth_waitpid ? pth_waitpid (pid, &status, 0) : waitpid (pid, &status, 0); #else while ( (i=waitpid (pid, &status, 0)) == -1 && errno == EINTR) ; #endif if (i == (pid_t)(-1)) { log_error (_("waiting for process %d to terminate failed: %s\n"), (int)pid, strerror (errno)); ec = gpg_err_code_from_errno (errno); } else if (WIFEXITED (status) && WEXITSTATUS (status) == 127) { log_error (_("error running `%s': probably not installed\n"), pgmname); ec = GPG_ERR_CONFIGURATION; } else if (WIFEXITED (status) && WEXITSTATUS (status)) { log_error (_("error running `%s': exit status %d\n"), pgmname, WEXITSTATUS (status)); ec = GPG_ERR_GENERAL; } else if (!WIFEXITED (status)) { log_error (_("error running `%s': terminated\n"), pgmname); ec = GPG_ERR_GENERAL; } else ec = 0; #endif /* !HAVE_W32_SYSTEM */ return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); } diff --git a/common/exechelp.h b/common/exechelp.h index f00d18dd8..1df029b7e 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -1,45 +1,46 @@ /* exechelp.h - Definitions for the fork and exec helpers * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_EXECHELP_H #define GNUPG_COMMON_EXECHELP_H /* Fork and exec the PGMNAME, connect the file descriptor of INFILE to stdin, write the output to OUTFILE, return a new stream in STATUSFILE for stderr and the pid of the process in PID. The arguments for the process are expected in the NULL terminated array ARGV. The program name itself should not be included there. if PREEXEC is not NULL, that function will be called right before the exec. Returns 0 on success or an error code. */ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], FILE *infile, FILE *outfile, void (*preexec)(void), FILE **statusfile, pid_t *pid); /* Wait for the process identified by PID to terminate. PGMNAME should be the same as suplieed to the spawn fucntion and is only used for diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL for any failures of the spawned program or other error codes.*/ gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid); #endif /*GNUPG_COMMON_EXECHELP_H*/ diff --git a/common/gettime.c b/common/gettime.c index ecdc7df95..c4ea3283a 100644 --- a/common/gettime.c +++ b/common/gettime.c @@ -1,304 +1,305 @@ /* 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #ifdef HAVE_LANGINFO_H #include #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; } /* Return the current time (possibly faked) in ISO format. */ void gnupg_get_isotime (gnupg_isotime_t timebuf) { time_t atime = gnupg_get_time (); if (atime < 0) *timebuf = 0; else { struct tm *tp; #ifdef HAVE_GMTIME_R struct tm tmbuf; tp = gmtime_r (&atime, &tmbuf); #else tp = gmtime (&atime); #endif sprintf (timebuf,"%04d%02d%02dT%02d%02d%02d", 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); } } /* 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 GMT */ const char * isotimestamp (u32 stamp) { static char buffer[25+5]; struct tm *tp; time_t atime = stamp; if (atime < 0) { strcpy (buffer, "????" "-??" "-??" " " "??" ":" "??" ":" "??"); } else { tp = gmtime ( &atime ); sprintf (buffer,"%04d-%02d-%02d %02d:%02d:%02d", 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); } 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"); /* NOTE: gcc -Wformat-noliteral will complain here. I have found no way to suppress this warning .*/ 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/homedir.c b/common/homedir.c index a118cbac1..39d6dce20 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -1,125 +1,126 @@ /* homedir.c - Setup the home directory. * Copyright (C) 2004, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #ifdef HAVE_W32_SYSTEM #include #ifndef CSIDL_APPDATA #define CSIDL_APPDATA 0x001a #endif #ifndef CSIDL_LOCAL_APPDATA #define CSIDL_LOCAL_APPDATA 0x001c #endif #ifndef CSIDL_FLAG_CREATE #define CSIDL_FLAG_CREATE 0x8000 #endif #endif /*HAVE_W32_SYSTEM*/ #include "util.h" #include "sysutils.h" /* This is a helper function to load a Windows function from either of one DLLs. */ #ifdef HAVE_W32_SYSTEM static HRESULT w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e) { static int initialized; static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR); if (!initialized) { static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL }; void *handle; int i; initialized = 1; for (i=0, handle = NULL; !handle && dllnames[i]; i++) { handle = dlopen (dllnames[i], RTLD_LAZY); if (handle) { func = dlsym (handle, "SHGetFolderPathA"); if (!func) { dlclose (handle); handle = NULL; } } } } if (func) return func (a,b,c,d,e); else return -1; } #endif /*HAVE_W32_SYSTEM*/ /* Set up the default home directory. The usual --homedir option should be parsed later. */ const char * default_homedir (void) { const char *dir; dir = getenv("GNUPGHOME"); #ifdef HAVE_W32_SYSTEM if (!dir || !*dir) dir = read_w32_registry_string (NULL, "Software\\GNU\\GnuPG", "HomeDir"); if (!dir || !*dir) { char path[MAX_PATH]; /* It might be better to use LOCAL_APPDATA because this is defined as "non roaming" and thus more likely to be kept locally. For private keys this is desired. However, given that many users copy private keys anyway forth and back, using a system roaming services might be better than to let them do it manually. A security conscious user will anyway use the registry entry to have better control. */ if (w32_shgetfolderpath (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, path) >= 0) { char *tmp = xmalloc (strlen (path) + 6 +1); strcpy (stpcpy (tmp, path), "\\gnupg"); dir = tmp; /* Try to create the directory if it does not yet exists. */ if (access (dir, F_OK)) CreateDirectory (dir, NULL); } } #endif /*HAVE_W32_SYSTEM*/ if (!dir || !*dir) dir = GNUPG_DEFAULT_HOMEDIR; return dir; } diff --git a/common/i18n.h b/common/i18n.h index 0e13dca4d..0187ba265 100644 --- a/common/i18n.h +++ b/common/i18n.h @@ -1,47 +1,48 @@ /* i18n.h * Copyright (C) 1998, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_I18N_H #define GNUPG_COMMON_I18N_H #ifdef USE_SIMPLE_GETTEXT int set_gettext_file( const char *filename ); const char *gettext( const char *msgid ); # define _(a) gettext (a) # define N_(a) (a) #else # ifdef HAVE_LOCALE_H # include # endif # ifdef ENABLE_NLS # include # define _(a) gettext (a) # ifdef gettext_noop # define N_(a) gettext_noop (a) # else # define N_(a) (a) # endif # else # define _(a) (a) # define N_(a) (a) # endif #endif /*!USE_SIMPLE_GETTEXT*/ #endif /*GNUPG_COMMON_I18N_H*/ diff --git a/common/iobuf.c b/common/iobuf.c index bbb666f67..8f7374f8c 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -1,2466 +1,2467 @@ /* iobuf.c - file handling * Copyright (C) 1998, 1999, 2000, 2001, 2003, * 2004, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DOSISH_SYSTEM #include #endif #ifdef __riscos__ #include #include #endif /* __riscos__ */ #include "memory.h" #include "util.h" #include "iobuf.h" /* The size of the internal buffers. NOTE: If you change this value you MUST also adjust the regression test "armored_key_8192" in armor.test! */ #define IOBUF_BUFFER_SIZE 8192 #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 _WIN32 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 /*_WIN32*/ /* 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 _WIN32 /* 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 /*_WIN32*/ /**************** * 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 * buffer, size_t * ret_len) { block_filter_ctx_t *a = opaque; char *buf = (char *)buffer; 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; a->partial = 2; if (!a->size) { a->eof = 1; if (!n) rc = -1; break; } } 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 BUG (); } 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 BUG (); } 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 BUG (); } 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 = xstrdup (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 form "-&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; digitp (fname+i); i++) ; if (!fname[i]) return atoi (fname); } return -1; } /* This fucntion returns true if FNAME indicates a PIPE (stdout or stderr) or a special file name if those are enabled. */ int iobuf_is_pipe_filename (const char *fname) { if (!fname || (*fname=='-' && !fname[1]) ) return 1; return check_special_filename (fname) != -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 _WIN32 sock_filter_ctx_t *scx; size_t len; a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192); scx = xmalloc (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 _WIN32 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 _WIN32 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; a->opaque = b->opaque; /* setup the function on the new stream */ a->filter = f; a->filter_ov = ov; a->filter_ov_owner = rel_ov; a->subno = b->subno + 1; f (ov, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &dummy_len); if (DBG_IOBUF) { log_debug ("iobuf-%d.%d: push `%s'\n", a->no, a->subno, a->desc); print_chain (a); } /* now we can initialize the new function if we have one */ if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_INIT, a->chain, NULL, &dummy_len))) log_error ("IOBUFCTRL_INIT failed: %s\n", gpg_strerror (rc)); return rc; } /**************** * Remove an i/o filter. */ static int pop_filter (iobuf_t a, int (*f) (void *opaque, int control, iobuf_t chain, byte * buf, size_t * len), void *ov) { iobuf_t b; size_t dummy_len = 0; int rc = 0; if (a->directfp) BUG (); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: pop `%s'\n", a->no, a->subno, a->desc); if (!a->filter) { /* this is simple */ b = a->chain; assert (b); xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); return 0; } for (b = a; b; b = b->chain) if (b->filter == f && (!ov || b->filter_ov == ov)) break; if (!b) log_bug ("pop_filter(): filter function not found\n"); /* flush this stream if it is an output stream */ if (a->use == 2 && (rc = iobuf_flush (b))) { log_error ("iobuf_flush failed in pop_filter: %s\n", gpg_strerror (rc)); return rc; } /* and tell the filter to free it self */ if (b->filter && (rc = b->filter (b->filter_ov, IOBUFCTRL_FREE, b->chain, NULL, &dummy_len))) { log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc)); return rc; } if (b->filter_ov && b->filter_ov_owner) { xfree (b->filter_ov); b->filter_ov = NULL; } /* and see how to remove it */ if (a == b && !b->chain) log_bug ("can't remove the last filter from the chain\n"); else if (a == b) { /* remove the first iobuf from the chain */ /* everything from b is copied to a. This is save because * a flush has been done on the to be removed entry */ b = a->chain; xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: popped filter\n", a->no, a->subno); } else if (!b->chain) { /* remove the last iobuf from the chain */ log_bug ("Ohh jeee, trying to remove a head filter\n"); } else { /* remove an intermediate iobuf from the chain */ log_bug ("Ohh jeee, trying to remove an intermediate filter\n"); } return rc; } /**************** * read underflow: read more bytes into the buffer and return * the first byte or -1 on EOF. */ static int underflow (iobuf_t a) { size_t len; int rc; assert (a->d.start == a->d.len); if (a->use == 3) return -1; /* EOF because a temp buffer can't do an underflow */ if (a->filter_eof) { if (a->chain) { iobuf_t b = a->chain; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: pop `%s' in underflow\n", a->no, a->subno, a->desc); xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); print_chain (a); } else a->filter_eof = 0; /* for the top level filter */ if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: eof (due to filter eof)\n", a->no, a->subno); return -1; /* return one(!) EOF */ } if (a->error) { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: error\n", a->no, a->subno); return -1; } if (a->directfp) { FILE *fp = a->directfp; len = fread (a->d.buf, 1, a->d.size, fp); if (len < a->d.size) { if (ferror (fp)) a->error = gpg_error_from_errno (errno); } a->d.len = len; a->d.start = 0; return len ? a->d.buf[a->d.start++] : -1; } if (a->filter) { len = a->d.size; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: req=%lu\n", a->no, a->subno, (ulong) len); rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain, a->d.buf, &len); if (DBG_IOBUF) { log_debug ("iobuf-%d.%d: underflow: got=%lu rc=%d\n", a->no, a->subno, (ulong) len, rc); /* if( a->no == 1 ) */ /* log_hexdump (" data:", a->d.buf, len); */ } if (a->use == 1 && rc == -1) { /* EOF: we can remove the filter */ size_t dummy_len = 0; /* and tell the filter to free itself */ if ((rc = a->filter (a->filter_ov, IOBUFCTRL_FREE, a->chain, NULL, &dummy_len))) log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc)); if (a->filter_ov && a->filter_ov_owner) { xfree (a->filter_ov); a->filter_ov = NULL; } a->filter = NULL; a->desc = NULL; a->filter_ov = NULL; a->filter_eof = 1; if (!len && a->chain) { iobuf_t b = a->chain; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: pop `%s' in underflow (!len)\n", a->no, a->subno, a->desc); xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); print_chain (a); } } else if (rc) a->error = rc; if (!len) { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: eof\n", a->no, a->subno); return -1; } a->d.len = len; a->d.start = 0; return a->d.buf[a->d.start++]; } else { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: eof (no filter)\n", a->no, a->subno); return -1; /* no filter; return EOF */ } } int iobuf_flush (iobuf_t a) { size_t len; int rc; if (a->directfp) return 0; if (a->use == 3) { /* increase the temp buffer */ unsigned char *newbuf; size_t newsize = a->d.size + 8192; if (DBG_IOBUF) log_debug ("increasing temp iobuf from %lu to %lu\n", (ulong) a->d.size, (ulong) newsize); newbuf = xmalloc (newsize); memcpy (newbuf, a->d.buf, a->d.len); xfree (a->d.buf); a->d.buf = newbuf; a->d.size = newsize; return 0; } else if (a->use != 2) log_bug ("flush on non-output iobuf\n"); else if (!a->filter) log_bug ("iobuf_flush: no filter\n"); len = a->d.len; rc = a->filter (a->filter_ov, IOBUFCTRL_FLUSH, a->chain, a->d.buf, &len); if (!rc && len != a->d.len) { log_info ("iobuf_flush did not write all!\n"); rc = GPG_ERR_INTERNAL; } else if (rc) a->error = rc; a->d.len = 0; return rc; } /**************** * Read a byte from the iobuf; returns -1 on EOF */ int iobuf_readbyte (iobuf_t a) { int c; /* nlimit does not work together with unget */ /* nbytes is also not valid! */ if (a->unget.buf) { if (a->unget.start < a->unget.len) return a->unget.buf[a->unget.start++]; xfree (a->unget.buf); a->unget.buf = NULL; a->nofast &= ~2; } if (a->nlimit && a->nbytes >= a->nlimit) return -1; /* forced EOF */ if (a->d.start < a->d.len) { c = a->d.buf[a->d.start++]; } else if ((c = underflow (a)) == -1) return -1; /* EOF */ a->nbytes++; return c; } int iobuf_read (iobuf_t a, void *buffer, unsigned int buflen) { unsigned char *buf = (unsigned char *)buffer; int c, n; if (a->unget.buf || a->nlimit) { /* handle special cases */ for (n = 0; n < buflen; n++) { if ((c = iobuf_readbyte (a)) == -1) { if (!n) return -1; /* eof */ break; } else if (buf) *buf = c; if (buf) buf++; } return n; } n = 0; do { if (n < buflen && a->d.start < a->d.len) { unsigned size = a->d.len - a->d.start; if (size > buflen - n) size = buflen - n; if (buf) memcpy (buf, a->d.buf + a->d.start, size); n += size; a->d.start += size; if (buf) buf += size; } if (n < buflen) { if ((c = underflow (a)) == -1) { a->nbytes += n; return n ? n : -1 /*EOF*/; } if (buf) *buf++ = c; n++; } } while (n < buflen); a->nbytes += n; return n; } /**************** * Have a look at the iobuf. * NOTE: This only works in special cases. */ int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen) { int n = 0; if (a->filter_eof) return -1; if (!(a->d.start < a->d.len)) { if (underflow (a) == -1) return -1; /* and unget this character */ assert (a->d.start == 1); a->d.start = 0; } for (n = 0; n < buflen && (a->d.start + n) < a->d.len; n++, buf++) *buf = a->d.buf[n]; return n; } int iobuf_writebyte (iobuf_t a, unsigned int c) { int rc; if (a->directfp) BUG (); if (a->d.len == a->d.size) if ((rc=iobuf_flush (a))) return rc; assert (a->d.len < a->d.size); a->d.buf[a->d.len++] = c; return 0; } int iobuf_write (iobuf_t a, const void *buffer, unsigned int buflen) { const unsigned char *buf = (const unsigned char *)buffer; int rc; if (a->directfp) BUG (); do { if (buflen && a->d.len < a->d.size) { unsigned size = a->d.size - a->d.len; if (size > buflen) size = buflen; memcpy (a->d.buf + a->d.len, buf, size); buflen -= size; buf += size; a->d.len += size; } if (buflen) { rc = iobuf_flush (a); if (rc) return rc; } } while (buflen); return 0; } int iobuf_writestr (iobuf_t a, const char *buf) { int rc; for (; *buf; buf++) if ((rc=iobuf_writebyte (a, *buf))) return rc; return 0; } /**************** * copy the contents of TEMP to A. */ int iobuf_write_temp (iobuf_t a, iobuf_t temp) { while (temp->chain) pop_filter (temp, temp->filter, NULL); return iobuf_write (a, temp->d.buf, temp->d.len); } /**************** * copy the contents of the temp io stream to BUFFER. */ size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen) { size_t n = a->d.len; if (n > buflen) n = buflen; memcpy (buffer, a->d.buf, n); return n; } /**************** * Call this function to terminate processing of the temp stream * without closing it. This removes all filters from the stream * makes sure that iobuf_get_temp_{buffer,length}() returns correct * values. */ void iobuf_flush_temp (iobuf_t temp) { while (temp->chain) pop_filter (temp, temp->filter, NULL); } /**************** * Set a limit on how many bytes may be read from the input stream A. * Setting the limit to 0 disables this feature. */ void iobuf_set_limit (iobuf_t a, off_t nlimit) { if (nlimit) a->nofast |= 1; else a->nofast &= ~1; a->nlimit = nlimit; a->ntotal += a->nbytes; a->nbytes = 0; } /* Return the length of an open file A. IF OVERFLOW is not NULL it will be set to true if the file is larger than what off_t can cope with. The function return 0 on error or on overflow condition. */ off_t iobuf_get_filelength (iobuf_t a, int *overflow) { struct stat st; if (overflow) *overflow = 0; if( a->directfp ) { FILE *fp = a->directfp; if( !fstat(fileno(fp), &st) ) return st.st_size; log_error("fstat() failed: %s\n", strerror(errno) ); return 0; } /* Hmmm: file_filter may have already been removed */ for( ; a; a = a->chain ) if( !a->chain && a->filter == file_filter ) { file_filter_ctx_t *b = a->filter_ov; FILEP_OR_FD fp = b->fp; #if defined(HAVE_DOSISH_SYSTEM) && !defined(FILE_FILTER_USES_STDIO) ulong size; static int (* __stdcall get_file_size_ex) (void *handle, LARGE_INTEGER *size); static int get_file_size_ex_initialized; if (!get_file_size_ex_initialized) { void *handle; handle = dlopen ("kernel32.dll", RTLD_LAZY); if (handle) { get_file_size_ex = dlsym (handle, "GetFileSizeEx"); if (!get_file_size_ex) dlclose (handle); } get_file_size_ex_initialized = 1; } if (get_file_size_ex) { /* This is a newer system with GetFileSizeEx; we use this then becuase it seem that GetFileSize won't return a proper error in case a file is larger than 4GB. */ LARGE_INTEGER size; if (get_file_size_ex (fp, &size)) { if (!size.u.HighPart) return size.u.LowPart; if (overflow) *overflow = 1; return 0; } } else { if ((size=GetFileSize (fp, NULL)) != 0xffffffff) return size; } log_error ("GetFileSize for handle %p failed: %s\n", fp, w32_strerror (0)); #else if( !fstat(my_fileno(fp), &st) ) return st.st_size; log_error("fstat() failed: %s\n", strerror(errno) ); #endif break; } return 0; } /* Return the file descriptor of the underlying file or -1 if it is not available. */ int iobuf_get_fd (iobuf_t a) { if (a->directfp) return fileno ( (FILE*)a->directfp ); for ( ; a; a = a->chain ) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; FILEP_OR_FD fp = b->fp; return my_fileno (fp); } return -1; } /**************** * Tell the file position, where the next read will take place */ off_t iobuf_tell (iobuf_t a) { return a->ntotal + a->nbytes; } #if !defined(HAVE_FSEEKO) && !defined(fseeko) #ifdef HAVE_LIMITS_H # include #endif #ifndef LONG_MAX # define LONG_MAX ((long) ((unsigned long) -1 >> 1)) #endif #ifndef LONG_MIN # define LONG_MIN (-1 - LONG_MAX) #endif /**************** * A substitute for fseeko, for hosts that don't have it. */ static int fseeko (FILE * stream, off_t newpos, int whence) { while (newpos != (long) newpos) { long pos = newpos < 0 ? LONG_MIN : LONG_MAX; if (fseek (stream, pos, whence) != 0) return -1; newpos -= pos; whence = SEEK_CUR; } return fseek (stream, (long) newpos, whence); } #endif /**************** * This is a very limited implementation. It simply discards all internal * buffering and removes all filters but the first one. */ int iobuf_seek (iobuf_t a, off_t newpos) { file_filter_ctx_t *b = NULL; if (a->directfp) { FILE *fp = a->directfp; if (fseeko (fp, newpos, SEEK_SET)) { log_error ("can't seek: %s\n", strerror (errno)); return -1; } clearerr (fp); } else { for (; a; a = a->chain) { if (!a->chain && a->filter == file_filter) { b = a->filter_ov; break; } } if (!a) return -1; #ifdef FILE_FILTER_USES_STDIO if (fseeko (b->fp, newpos, SEEK_SET)) { log_error ("can't fseek: %s\n", strerror (errno)); return -1; } #else #ifdef HAVE_DOSISH_SYSTEM if (SetFilePointer (b->fp, newpos, NULL, FILE_BEGIN) == 0xffffffff) { log_error ("SetFilePointer failed on handle %p: ec=%d\n", b->fp, (int) GetLastError ()); return -1; } #else if (lseek (b->fp, newpos, SEEK_SET) == (off_t) - 1) { log_error ("can't lseek: %s\n", strerror (errno)); return -1; } #endif #endif } a->d.len = 0; /* discard buffer */ a->d.start = 0; a->nbytes = 0; a->nlimit = 0; a->nofast &= ~1; a->ntotal = newpos; a->error = 0; /* remove filters, but the last */ if (a->chain) log_debug ("pop_filter called in iobuf_seek - please report\n"); while (a->chain) pop_filter (a, a->filter, NULL); return 0; } /**************** * Retrieve the real filename */ const char * iobuf_get_real_fname (iobuf_t a) { if (a->real_fname) return a->real_fname; /* the old solution */ for (; a; a = a->chain) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; return b->print_only_name ? NULL : b->fname; } return NULL; } /**************** * Retrieve the filename */ const char * iobuf_get_fname (iobuf_t a) { for (; a; a = a->chain) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; return b->fname; } return NULL; } /**************** * enable partial block mode as described in the OpenPGP draft. * LEN is the first length byte on read, but ignored on writes. */ void iobuf_set_partial_block_mode (iobuf_t a, size_t len) { block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx); assert (a->use == 1 || a->use == 2); ctx->use = a->use; if (!len) { if (a->use == 1) log_debug ("pop_filter called in set_partial_block_mode" " - please report\n"); pop_filter (a, block_filter, NULL); } else { ctx->partial = 1; ctx->size = 0; ctx->first_c = len; iobuf_push_filter (a, block_filter, ctx); } } /**************** * Same as fgets() but if the buffer is too short a larger one will * be allocated up to some limit *max_length. * A line is considered a byte stream ending in a LF. * Returns the length of the line. EOF is indicated by a line of * length zero. The last LF may be missing due to an EOF. * is max_length is zero on return, the line has been truncated. * * Note: The buffer is allocated with enough space to append a CR,LF,EOL */ unsigned int iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, unsigned *length_of_buffer, unsigned *max_length) { int c; char *buffer = (char *)*addr_of_buffer; unsigned length = *length_of_buffer; unsigned nbytes = 0; unsigned maxlen = *max_length; char *p; if (!buffer) { /* must allocate a new buffer */ length = 256; buffer = xmalloc (length); *addr_of_buffer = (unsigned char *)buffer; *length_of_buffer = length; } length -= 3; /* reserve 3 bytes (cr,lf,eol) */ p = buffer; while ((c = iobuf_get (a)) != -1) { if (nbytes == length) { /* increase the buffer */ if (length > maxlen) { /* this is out limit */ /* skip the rest of the line */ while (c != '\n' && (c = iobuf_get (a)) != -1) ; *p++ = '\n'; /* always append a LF (we have reserved space) */ nbytes++; *max_length = 0; /* indicate truncation */ break; } length += 3; /* correct for the reserved byte */ length += length < 1024 ? 256 : 1024; buffer = xrealloc (buffer, length); *addr_of_buffer = (unsigned char *)buffer; *length_of_buffer = length; length -= 3; /* and reserve again */ p = buffer + nbytes; } *p++ = c; nbytes++; if (c == '\n') break; } *p = 0; /* make sure the line is a string */ return nbytes; } /* This is the non iobuf specific function */ int iobuf_translate_file_handle (int fd, int for_write) { #ifdef _WIN32 { int x; if (fd <= 2) return fd; /* do not do this for error, stdin, stdout, stderr */ x = _open_osfhandle (fd, for_write ? 1 : 0); if (x == -1) log_error ("failed to translate osfhandle %p\n", (void *) fd); else { /*log_info ("_open_osfhandle %p yields %d%s\n", (void*)fd, x, for_write? " for writing":"" ); */ fd = x; } } #endif return fd; } static int translate_file_handle (int fd, int for_write) { #ifdef _WIN32 #ifdef FILE_FILTER_USES_STDIO fd = iobuf_translate_file_handle (fd, for_write); #else { int x; if (fd == 0) x = (int) GetStdHandle (STD_INPUT_HANDLE); else if (fd == 1) x = (int) GetStdHandle (STD_OUTPUT_HANDLE); else if (fd == 2) x = (int) GetStdHandle (STD_ERROR_HANDLE); else x = fd; if (x == -1) log_debug ("GetStdHandle(%d) failed: ec=%d\n", fd, (int) GetLastError ()); fd = x; } #endif #endif return fd; } void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial) { if ( partial ) { for (;;) { if (a->nofast || a->d.start >= a->d.len) { if (iobuf_readbyte (a) == -1) { break; } } else { unsigned long count = a->d.len - a->d.start; a->nbytes += count; a->d.start = a->d.len; } } } else { unsigned long remaining = n; while (remaining > 0) { if (a->nofast || a->d.start >= a->d.len) { if (iobuf_readbyte (a) == -1) { break; } --remaining; } else { unsigned long count = a->d.len - a->d.start; if (count > remaining) { count = remaining; } a->nbytes += count; a->d.start += count; remaining -= count; } } } } diff --git a/common/iobuf.h b/common/iobuf.h index 3b8f4b572..a3dd7f1c5 100644 --- a/common/iobuf.h +++ b/common/iobuf.h @@ -1,173 +1,174 @@ /* iobuf.h - I/O buffer * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_IOBUF_H #define GNUPG_COMMON_IOBUF_H #include "../include/types.h" /* fixme: should be moved elsewhere. */ #define DBG_IOBUF iobuf_debug_mode #define IOBUFCTRL_INIT 1 #define IOBUFCTRL_FREE 2 #define IOBUFCTRL_UNDERFLOW 3 #define IOBUFCTRL_FLUSH 4 #define IOBUFCTRL_DESC 5 #define IOBUFCTRL_CANCEL 6 #define IOBUFCTRL_USER 16 typedef struct iobuf_struct *iobuf_t; typedef struct iobuf_struct *IOBUF; /* Compatibility with gpg 1.4. */ /* fixme: we should hide most of this stuff */ struct iobuf_struct { int use; /* 1 input , 2 output, 3 temp */ off_t nlimit; off_t nbytes; /* used together with nlimit */ off_t ntotal; /* total bytes read (position of stream) */ int nofast; /* used by the iobuf_get() */ void *directfp; struct { size_t size; /* allocated size */ size_t start; /* number of invalid bytes at the begin of the buffer */ size_t len; /* currently filled to this size */ byte *buf; } d; int filter_eof; int error; int (*filter) (void *opaque, int control, iobuf_t chain, byte * buf, size_t * len); void *filter_ov; /* value for opaque */ int filter_ov_owner; char *real_fname; iobuf_t chain; /* next iobuf used for i/o if any (passed to filter) */ int no, subno; const char *desc; void *opaque; /* can be used to hold any information this value is copied to all instances */ struct { size_t size; /* allocated size */ size_t start; /* number of invalid bytes at the begin of the buffer */ size_t len; /* currently filled to this size */ byte *buf; } unget; }; #ifndef EXTERN_UNLESS_MAIN_MODULE #if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE) #define EXTERN_UNLESS_MAIN_MODULE extern #else #define EXTERN_UNLESS_MAIN_MODULE #endif #endif EXTERN_UNLESS_MAIN_MODULE int iobuf_debug_mode; void iobuf_enable_special_filenames (int yes); int iobuf_is_pipe_filename (const char *fname); iobuf_t iobuf_alloc (int use, size_t bufsize); iobuf_t iobuf_temp (void); iobuf_t iobuf_temp_with_content (const char *buffer, size_t length); iobuf_t iobuf_open (const char *fname); iobuf_t iobuf_fdopen (int fd, const char *mode); iobuf_t iobuf_sockopen (int fd, const char *mode); iobuf_t iobuf_create (const char *fname); iobuf_t iobuf_append (const char *fname); iobuf_t iobuf_openrw (const char *fname); int iobuf_ioctl (iobuf_t a, int cmd, int intval, void *ptrval); int iobuf_close (iobuf_t iobuf); int iobuf_cancel (iobuf_t iobuf); int iobuf_push_filter (iobuf_t a, int (*f) (void *opaque, int control, iobuf_t chain, byte * buf, size_t * len), void *ov); 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); int iobuf_flush (iobuf_t a); void iobuf_clear_eof (iobuf_t a); #define iobuf_set_error(a) do { (a)->error = 1; } while(0) #define iobuf_error(a) ((a)->error) void iobuf_set_limit (iobuf_t a, off_t nlimit); off_t iobuf_tell (iobuf_t a); int iobuf_seek (iobuf_t a, off_t newpos); int iobuf_readbyte (iobuf_t a); int iobuf_read (iobuf_t a, void *buf, unsigned buflen); unsigned iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, unsigned *length_of_buffer, unsigned *max_length); int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen); int iobuf_writebyte (iobuf_t a, unsigned c); int iobuf_write (iobuf_t a, const void *buf, unsigned buflen); int iobuf_writestr (iobuf_t a, const char *buf); void iobuf_flush_temp (iobuf_t temp); int iobuf_write_temp (iobuf_t a, iobuf_t temp); size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen); void iobuf_unget_and_close_temp (iobuf_t a, iobuf_t temp); off_t iobuf_get_filelength (iobuf_t a, int *overflow); #define IOBUF_FILELENGTH_LIMIT 0xffffffff int iobuf_get_fd (iobuf_t a); const char *iobuf_get_real_fname (iobuf_t a); const char *iobuf_get_fname (iobuf_t a); void iobuf_set_partial_block_mode (iobuf_t a, size_t len); int iobuf_translate_file_handle (int fd, int for_write); void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial); /* get a byte form the iobuf; must check for eof prior to this function * this function returns values in the range 0 .. 255 or -1 to indicate EOF * iobuf_get_noeof() does not return -1 to indicate EOF, but masks the * returned value to be in the range 0 ..255. */ #define iobuf_get(a) \ ( ((a)->nofast || (a)->d.start >= (a)->d.len )? \ iobuf_readbyte((a)) : ( (a)->nbytes++, (a)->d.buf[(a)->d.start++] ) ) #define iobuf_get_noeof(a) (iobuf_get((a))&0xff) /* write a byte to the iobuf and return true on write error * This macro does only write the low order byte */ #define iobuf_put(a,c) iobuf_writebyte(a,c) #define iobuf_where(a) "[don't know]" #define iobuf_id(a) ((a)->no) #define iobuf_get_temp_buffer(a) ( (a)->d.buf ) #define iobuf_get_temp_length(a) ( (a)->d.len ) #define iobuf_is_temp(a) ( (a)->use == 3 ) #endif /*GNUPG_COMMON_IOBUF_H*/ diff --git a/common/isascii.c b/common/isascii.c index 565c71664..b71febe99 100644 --- a/common/isascii.c +++ b/common/isascii.c @@ -1,29 +1,30 @@ /* isascii.c - Replacement for isascii. * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H #include #endif int isascii (int c) { return (((c) & ~0x7f) == 0); } diff --git a/common/maperror.c b/common/maperror.c index 9efd64338..06546b501 100644 --- a/common/maperror.c +++ b/common/maperror.c @@ -1,104 +1,105 @@ /* maperror.c - Error mapping * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "util.h" #include "errors.h" /* Map Assuan error code ERR to an GPG_ERR_ code. We need to distinguish between genuine (and legacy) Assuan error codes and application error codes shared with all GnuPG modules. The rule is simple: All errors with a gpg_err_source of UNKNOWN are genuine Assuan codes all others are passed verbatim through. */ gpg_error_t map_assuan_err_with_source (int source, int err) { gpg_err_code_t ec; if (gpg_err_source (err)) return err; switch (err) { case -1: ec = GPG_ERR_EOF; break; case 0: ec = 0; break; case ASSUAN_Canceled: ec = GPG_ERR_CANCELED; break; case ASSUAN_Invalid_Index: ec = GPG_ERR_INV_INDEX; break; case ASSUAN_Not_Implemented: ec = GPG_ERR_NOT_IMPLEMENTED; break; case ASSUAN_Server_Fault: ec = GPG_ERR_ASSUAN_SERVER_FAULT; break; case ASSUAN_No_Public_Key: ec = GPG_ERR_NO_PUBKEY; break; case ASSUAN_No_Secret_Key: ec = GPG_ERR_NO_SECKEY; break; case ASSUAN_Cert_Revoked: ec = GPG_ERR_CERT_REVOKED; break; case ASSUAN_No_CRL_For_Cert: ec = GPG_ERR_NO_CRL_KNOWN; break; case ASSUAN_CRL_Too_Old: ec = GPG_ERR_CRL_TOO_OLD; break; case ASSUAN_Not_Trusted: ec = GPG_ERR_NOT_TRUSTED; break; case ASSUAN_Card_Error: ec = GPG_ERR_CARD; break; case ASSUAN_Invalid_Card: ec = GPG_ERR_INV_CARD; break; case ASSUAN_No_PKCS15_App: ec = GPG_ERR_NO_PKCS15_APP; break; case ASSUAN_Card_Not_Present: ec= GPG_ERR_CARD_NOT_PRESENT; break; case ASSUAN_Not_Confirmed: ec = GPG_ERR_NOT_CONFIRMED; break; case ASSUAN_Invalid_Id: ec = GPG_ERR_INV_ID; break; case ASSUAN_Locale_Problem: ec = GPG_ERR_LOCALE_PROBLEM; break; default: ec = err < 100? GPG_ERR_ASSUAN_SERVER_FAULT : GPG_ERR_ASSUAN; break; } return gpg_err_make (source, ec); } /* Map GPG_xERR_xx error codes to Assuan status codes */ int map_to_assuan_status (int rc) { gpg_err_code_t ec = gpg_err_code (rc); gpg_err_source_t es = gpg_err_source (rc); if (!rc) return 0; if (!es) { es = GPG_ERR_SOURCE_USER_4; /* This should not happen, but we need to make sure to pass a new Assuan errorcode along. */ log_debug ("map_to_assuan_status called with no error source\n"); } if (ec == -1) ec = GPG_ERR_NO_DATA; /* That used to be ASSUAN_No_Data_Available. */ return gpg_err_make (es, ec); } diff --git a/common/membuf.c b/common/membuf.c index 75f6bdb2a..2d35fefab 100644 --- a/common/membuf.c +++ b/common/membuf.c @@ -1,94 +1,95 @@ /* membuf.c - A simple implementation of a dynamic buffer * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include "membuf.h" #include "util.h" /* 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. */ void init_membuf (membuf_t *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 = errno; } void put_membuf (membuf_t *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 = errno; /* Wipe out what we already accumulated. This is required in case we are storing sensitive data here. The membuf API does not provide another way to cleanup after an error. */ memset (mb->buf, 0, mb->len); return; } mb->buf = p; } memcpy (mb->buf + mb->len, buf, len); mb->len += len; } void * get_membuf (membuf_t *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 = ENOMEM; /* hack to make sure it won't get reused. */ return p; } diff --git a/common/membuf.h b/common/membuf.h index c199363cc..9033be61e 100644 --- a/common/membuf.h +++ b/common/membuf.h @@ -1,41 +1,42 @@ /* membuf.h - A simple implementation of a dynamic buffer * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_MEMBUF_H #define GNUPG_COMMON_MEMBUF_H /* The definition of the structure is private, we only need it here, so it can be allocated on the stack. */ struct private_membuf_s { size_t len; size_t size; char *buf; int out_of_core; }; typedef struct private_membuf_s membuf_t; void init_membuf (membuf_t *mb, int initiallen); void put_membuf (membuf_t *mb, const void *buf, size_t len); void *get_membuf (membuf_t *mb, size_t *len); #endif /*GNUPG_COMMON_MEMBUF_H*/ diff --git a/common/miscellaneous.c b/common/miscellaneous.c index e9f8ed27f..da74f65bc 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -1,150 +1,151 @@ /* miscellaneous.c - Stuff not fitting elsewhere * Copyright (C) 2003, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include "util.h" #include "iobuf.h" /* Decide whether the filename is stdout or a real filename and return * an appropriate string. */ const char * print_fname_stdout (const char *s) { if( !s || (*s == '-' && !s[1]) ) return "[stdout]"; return s; } /* Decide whether the filename is stdin or a real filename and return * an appropriate string. */ const char * print_fname_stdin (const char *s) { if( !s || (*s == '-' && !s[1]) ) return "[stdin]"; return s; } /* fixme: Globally replace it by print_sanitized_buffer. */ void print_string( FILE *fp, const byte *p, size_t n, int delim ) { print_sanitized_buffer (fp, p, n, delim); } void print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim ) { print_sanitized_utf8_buffer (fp, p, n, delim); } void print_utf8_string( FILE *fp, const byte *p, size_t n ) { print_utf8_string2 (fp, p, n, 0); } char * make_printable_string (const void *p, size_t n, int delim ) { return sanitize_buffer (p, n, delim); } /* * Check if the file is compressed. */ int is_file_compressed (const char *s, int *ret_rc) { iobuf_t a; byte buf[4]; int i, rc = 0; int overflow; struct magic_compress_s { size_t len; byte magic[4]; } magic[] = { { 3, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */ { 3, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */ { 4, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */ }; if ( iobuf_is_pipe_filename (s) || !ret_rc ) return 0; /* We can't check stdin or no file was given */ a = iobuf_open( s ); if ( a == NULL ) { *ret_rc = gpg_error_from_errno (errno); return 0; } if ( iobuf_get_filelength( a, &overflow ) < 4 && !overflow) { *ret_rc = 0; goto leave; } if ( iobuf_read( a, buf, 4 ) == -1 ) { *ret_rc = a->error; goto leave; } for ( i = 0; i < DIM( magic ); i++ ) { if ( !memcmp( buf, magic[i].magic, magic[i].len ) ) { *ret_rc = 0; rc = 1; break; } } leave: iobuf_close( a ); return rc; } /* Try match against each substring of multistr, delimited by | */ int match_multistr (const char *multistr,const char *match) { do { size_t seglen = strcspn (multistr,"|"); if (!seglen) break; /* Using the localized strncasecmp! */ if (strncasecmp(multistr,match,seglen)==0) return 1; multistr += seglen; if (*multistr == '|') multistr++; } while (*multistr); return 0; } diff --git a/common/mkerrors b/common/mkerrors index 5a1ef33da..994c61352 100755 --- a/common/mkerrors +++ b/common/mkerrors @@ -1,72 +1,73 @@ #!/bin/sh # mkerrors - Extract error strings from errors.h # and create C source for gnupg_strerror # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. cat < #include #include "errors.h" /** * gnupg_strerror: * @err: Error code * * This function returns a textual representaion of the given * errorcode. If this is an unknown value, a string with the value * is returned (Beware: it is hold in a static buffer). * * Return value: String with the error description. **/ const char * gnupg_strerror (int err) { const char *s; static char buf[25]; switch (err) { EOF awk ' /GNUPG_No_Error/ { okay=1 } !okay {next} /}/ { exit 0 } /GNUPG_[A-Za-z_]*/ { print_code($1) } function print_code( s ) { printf " case %s: s=\"", s ; gsub(/_/, " ", s ); printf "%s\"; break;\n", tolower(substr(s,7)); } ' cat < /* Return the length of the next S-Exp part and update the pointer to the first data byte. 0 is returned on error */ static inline size_t snext (unsigned char const **buf) { const unsigned char *s; int n; s = *buf; for (n=0; *s && *s != ':' && (*s >= '0' && *s <= '9'); s++) n = n*10 + (*s - '0'); if (!n || *s != ':') return 0; /* we don't allow empty lengths */ *buf = s+1; return n; } /* Skip over the S-Expression BUF points to and update BUF to point to the chacter right behind. DEPTH gives the initial number of open lists and may be passed as a positive number to skip over the remainder of an S-Expression if the current position is somewhere in an S-Expression. The function may return an error code if it encounters an impossible conditions */ static inline gpg_error_t sskip (unsigned char const **buf, int *depth) { const unsigned char *s = *buf; size_t n; int d = *depth; while (d > 0) { if (*s == '(') { d++; s++; } else if (*s == ')') { d--; s++; } else { if (!d) return gpg_error (GPG_ERR_INV_SEXP); n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; } } *buf = s; *depth = d; return 0; } /* Check whether the the string at the address BUF points to matches the token. Return true on match and update BUF to point behind the token. Return false and dont update tha buffer if it does not match. */ static inline int smatch (unsigned char const **buf, size_t buflen, const char *token) { size_t toklen = strlen (token); if (buflen != toklen || memcmp (*buf, token, toklen)) return 0; *buf += toklen; return 1; } #endif /*SEXP_PARSE_H*/ diff --git a/common/sexputil.c b/common/sexputil.c index 8a27ad978..fe0870c56 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -1,144 +1,145 @@ /* sexputil.c - Utility fnctions for S-expressions. * Copyright (C) 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This file implements a few utility functions useful when working with canonical encrypted S-expresions (i.e. not the S-exprssion objects from libgcrypt). */ #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "util.h" /* Return the so called "keygrip" which is the SHA-1 hash of the public key parameters expressed in a way depended on the algorithm. KEY is expected to be an canonical encoded S-expression with a public or private key. KEYLEN is the length of that buffer. GRIP must be at least 20 bytes long On success 0 is return, on error an aerror code. */ gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip) { gpg_error_t err; gcry_sexp_t sexp; if (!grip) return gpg_error (GPG_ERR_INV_VALUE); err = gcry_sexp_sscan (&sexp, NULL, (const char *)key, keylen); if (err) return err; if (!gcry_pk_get_keygrip (sexp, grip)) err = gpg_error (GPG_ERR_INTERNAL); gcry_sexp_release (sexp); return err; } /* Compare two simple S-expressions like "(3:foo)". Returns 0 if they are identical or !0 if they are not. Not that this function can't be used for sorting. */ int cmp_simple_canon_sexp (const unsigned char *a_orig, const unsigned char *b_orig) { const char *a = (const char *)a_orig; const char *b = (const char *)b_orig; unsigned long n1, n2; char *endp; if (!a && !b) return 0; /* Both are NULL, they are identical. */ if (!a || !b) return 1; /* One is NULL, they are not identical. */ if (*a != '(' || *b != '(') log_bug ("invalid S-exp in cmp_simple_canon_sexp\n"); a++; n1 = strtoul (a, &endp, 10); a = endp; b++; n2 = strtoul (b, &endp, 10); b = endp; if (*a != ':' || *b != ':' ) log_bug ("invalid S-exp in cmp_simple_canon_sexp\n"); if (n1 != n2) return 1; /* Not the same. */ for (a++, b++; n1; n1--, a++, b++) if (*a != *b) return 1; /* Not the same. */ return 0; } /* Create a simple S-expression from the hex string at LIBNE. Returns a newly allocated buffer with that canonical encoded S-expression or NULL in case of an error. On return the number of characters scanned in LINE will be stored at NSCANNED. This fucntions stops converting at the first character not representing a hexdigit. Odd numbers of hex digits are allowed; a leading zero is then assumed. If no characters have been found, NULL is returned.*/ unsigned char * make_simple_sexp_from_hexstr (const char *line, size_t *nscanned) { size_t n, len; const char *s; unsigned char *buf; unsigned char *p; char numbuf[50]; for (n=0, s=line; hexdigitp (s); s++, n++) ; if (nscanned) *nscanned = n; if (!n) return NULL; len = ((n+1) & ~0x01)/2; sprintf (numbuf, "(%u:", (unsigned int)len); buf = xtrymalloc (strlen (numbuf) + len + 1 + 1); if (!buf) return NULL; p = (unsigned char *)stpcpy ((char *)buf, numbuf); s = line; if ((n&1)) { *p++ = xtoi_1 (s); s++; n--; } for (; n > 1; n -=2, s += 2) *p++ = xtoi_2 (s); *p++ = ')'; *p = 0; /* (Not really neaded.) */ return buf; } diff --git a/common/signal.c b/common/signal.c index 2837d7b72..0c79214b2 100644 --- a/common/signal.c +++ b/common/signal.c @@ -1,258 +1,259 @@ /* signal.c - signal handling * Copyright (C) 1998, 1999, 2000, 2001, 2002, * 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "util.h" #ifndef HAVE_DOSISH_SYSTEM static volatile int caught_fatal_sig; static volatile int caught_sigusr1; #endif static void (*cleanup_fnc)(void); #ifndef HAVE_DOSISH_SYSTEM static void init_one_signal (int sig, RETSIGTYPE (*handler)(int), int check_ign ) { # ifdef HAVE_SIGACTION struct sigaction oact, nact; if (check_ign) { /* we don't want to change an IGN handler */ sigaction (sig, NULL, &oact ); if (oact.sa_handler == SIG_IGN ) return; } nact.sa_handler = handler; sigemptyset (&nact.sa_mask); nact.sa_flags = 0; sigaction ( sig, &nact, NULL); # else RETSIGTYPE (*ohandler)(int); ohandler = signal (sig, handler); if (check_ign && ohandler == SIG_IGN) { /* Change it back if it was already set to IGN */ signal (sig, SIG_IGN); } # endif } #endif /*!HAVE_DOSISH_SYSTEM*/ #ifndef HAVE_DOSISH_SYSTEM static const char * get_signal_name( int signum ) { /* Note that we can't use strsignal(), because it is not reentrant. */ #if HAVE_DECL_SYS_SIGLIST && defined(NSIG) return (signum >= 0 && signum < NSIG) ? sys_siglist[signum] : "?"; #else return NULL; #endif } #endif /*!HAVE_DOSISH_SYSTEM*/ #ifndef HAVE_DOSISH_SYSTEM static RETSIGTYPE got_fatal_signal (int sig) { const char *s; if (caught_fatal_sig) raise (sig); caught_fatal_sig = 1; if (cleanup_fnc) cleanup_fnc (); /* Better don't translate these messages. */ write (2, "\n", 1 ); s = log_get_prefix (NULL); if (s) write(2, s, strlen (s)); write (2, ": signal ", 9 ); s = get_signal_name(sig); if (s) write (2, s, strlen(s) ); else { /* We are in a signal handler so we can't use any kind of printf even not sprintf. USe a straightforward algorithm. */ if (sig < 0 || sig >= 100000) write (2, "?", 1); else { int i, any=0; for (i=10000; i; i /= 10) { if (sig >= i || ((any || i==1) && !(sig/i))) { write (2, "0123456789"+(sig/i), 1); if ((sig/i)) any = 1; sig %= i; } } } } write (2, " caught ... exiting\n", 20); /* Reset action to default action and raise signal again */ init_one_signal (sig, SIG_DFL, 0); /* Fixme: remove_lockfiles ();*/ #ifdef __riscos__ close_fds (); #endif /* __riscos__ */ raise( sig ); } #endif /*!HAVE_DOSISH_SYSTEM*/ #ifndef HAVE_DOSISH_SYSTEM static RETSIGTYPE got_usr_signal (int sig) { caught_sigusr1 = 1; } #endif /*!HAVE_DOSISH_SYSTEM*/ void gnupg_init_signals (int mode, void (*fast_cleanup)(void)) { assert (!mode); cleanup_fnc = fast_cleanup; #ifndef HAVE_DOSISH_SYSTEM init_one_signal (SIGINT, got_fatal_signal, 1 ); init_one_signal (SIGHUP, got_fatal_signal, 1 ); init_one_signal (SIGTERM, got_fatal_signal, 1 ); init_one_signal (SIGQUIT, got_fatal_signal, 1 ); init_one_signal (SIGSEGV, got_fatal_signal, 1 ); init_one_signal (SIGUSR1, got_usr_signal, 0 ); init_one_signal (SIGPIPE, SIG_IGN, 0 ); #endif } void gnupg_pause_on_sigusr (int which) { #ifndef HAVE_DOSISH_SYSTEM # ifdef HAVE_SIGPROCMASK sigset_t mask, oldmask; assert (which == 1); sigemptyset( &mask ); sigaddset( &mask, SIGUSR1 ); sigprocmask( SIG_BLOCK, &mask, &oldmask ); while (!caught_sigusr1) sigsuspend (&oldmask); caught_sigusr1 = 0; sigprocmask (SIG_UNBLOCK, &mask, NULL); # else assert (which == 1); sighold (SIGUSR1); while (!caught_sigusr1) sigpause(SIGUSR1); caught_sigusr1 = 0; sigrelease(SIGUSR1); # endif /*!HAVE_SIGPROCMASK*/ #endif } static void do_block( int block ) { #ifndef HAVE_DOSISH_SYSTEM static int is_blocked; #ifdef HAVE_SIGPROCMASK static sigset_t oldmask; if (block) { sigset_t newmask; if (is_blocked) log_bug ("signals are already blocked\n"); sigfillset( &newmask ); sigprocmask( SIG_BLOCK, &newmask, &oldmask ); is_blocked = 1; } else { if (!is_blocked) log_bug("signals are not blocked\n"); sigprocmask (SIG_SETMASK, &oldmask, NULL); is_blocked = 0; } #else /*!HAVE_SIGPROCMASK*/ static void (*disposition[MAXSIG])(); int sig; if (block) { if (is_blocked) log_bug("signals are already blocked\n"); for (sig=1; sig < MAXSIG; sig++) { disposition[sig] = sigset (sig, SIG_HOLD); } is_blocked = 1; } else { if (!is_blocked) log_bug ("signals are not blocked\n"); for (sig=1; sig < MAXSIG; sig++) { sigset (sig, disposition[sig]); } is_blocked = 0; } #endif /*!HAVE_SIGPROCMASK*/ #endif /*HAVE_DOSISH_SYSTEM*/ } void gnupg_block_all_signals () { do_block(1); } void gnupg_unblock_all_signals () { do_block(0); } diff --git a/common/simple-gettext.c b/common/simple-gettext.c index b6b851c77..56a305fd8 100644 --- a/common/simple-gettext.c +++ b/common/simple-gettext.c @@ -1,437 +1,438 @@ /* simple-gettext.c - a simplified version of gettext. * Copyright (C) 1995, 1996, 1997, 1999 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This is a simplified version of gettext written by Ulrich Drepper. * It is used for the Win32 version of GnuPG beucase all the overhead * of gettext is not needed and we have to do some special Win32 stuff. * I decided that this is far easier than to tweak gettext for the special * cases (I tried it but it is a lot of code). wk 15.09.99 */ #include #ifdef USE_SIMPLE_GETTEXT #if !defined (_WIN32) && !defined (__CYGWIN32__) #error This file can only be used under Windows or Cygwin32 #endif #include #include #include #include #include #include #include #include "util.h" #include "sysutils.h" /* The magic number of the GNU message catalog format. */ #define MAGIC 0x950412de #define MAGIC_SWAPPED 0xde120495 /* Revision number of the currently used .mo (binary) file format. */ #define MO_REVISION_NUMBER 0 /* Header for binary .mo file format. */ struct mo_file_header { /* The magic number. */ u32 magic; /* The revision number of the file format. */ u32 revision; /* The number of strings pairs. */ u32 nstrings; /* Offset of table with start offsets of original strings. */ u32 orig_tab_offset; /* Offset of table with start offsets of translation strings. */ u32 trans_tab_offset; /* Size of hashing table. */ u32 hash_tab_size; /* Offset of first hashing entry. */ u32 hash_tab_offset; }; struct string_desc { /* Length of addressed string. */ u32 length; /* Offset of string in file. */ u32 offset; }; struct overflow_space_s { struct overflow_space_s *next; u32 idx; char d[1]; }; struct loaded_domain { char *data; int must_swap; u32 nstrings; char *mapped; /* 0 = not yet mapped, 1 = mapped, 2 = mapped to overflow space */ struct overflow_space_s *overflow_space; struct string_desc *orig_tab; struct string_desc *trans_tab; u32 hash_size; u32 *hash_tab; }; static struct loaded_domain *the_domain; static __inline__ u32 do_swap_u32( u32 i ) { return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24); } #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) ) /* We assume to have `unsigned long int' value with at least 32 bits. */ #define HASHWORDBITS 32 /* The so called `hashpjw' function by P.J. Weinberger [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools, 1986, 1987 Bell Telephone Laboratories, Inc.] */ static __inline__ ulong hash_string( const char *str_param ) { unsigned long int hval, g; const char *str = str_param; hval = 0; while (*str != '\0') { hval <<= 4; hval += (unsigned long int) *str++; g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4)); if (g != 0) { hval ^= g >> (HASHWORDBITS - 8); hval ^= g; } } return hval; } static struct loaded_domain * load_domain( const char *filename ) { FILE *fp; size_t size; struct stat st; struct mo_file_header *data = NULL; struct loaded_domain *domain = NULL; size_t to_read; char *read_ptr; fp = fopen( filename, "rb" ); if( !fp ) return NULL; /* can't open the file */ /* we must know about the size of the file */ if( fstat( fileno(fp ), &st ) || (size = (size_t)st.st_size) != st.st_size || size < sizeof (struct mo_file_header) ) { fclose( fp ); return NULL; } data = malloc( size ); if( !data ) { fclose( fp ); return NULL; /* out of memory */ } to_read = size; read_ptr = (char *) data; do { long int nb = fread( read_ptr, 1, to_read, fp ); if( nb < to_read ) { fclose (fp); free(data); return NULL; /* read error */ } read_ptr += nb; to_read -= nb; } while( to_read > 0 ); fclose (fp); /* Using the magic number we can test whether it really is a message * catalog file. */ if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) { /* The magic number is wrong: not a message catalog file. */ free( data ); return NULL; } domain = calloc( 1, sizeof *domain ); if( !domain ) { free( data ); return NULL; } domain->data = (char *) data; domain->must_swap = data->magic != MAGIC; /* Fill in the information about the available tables. */ switch( SWAPIT(domain->must_swap, data->revision) ) { case 0: domain->nstrings = SWAPIT(domain->must_swap, data->nstrings); domain->orig_tab = (struct string_desc *) ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset)); domain->trans_tab = (struct string_desc *) ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset)); domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size); domain->hash_tab = (u32 *) ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset)); break; default: /* This is an invalid revision. */ free( data ); free( domain ); return NULL; } /* Allocate an array to keep track of code page mappings. */ domain->mapped = calloc( 1, domain->nstrings ); if( !domain->mapped ) { free( data ); free( domain ); return NULL; } return domain; } /**************** * Set the file used for translations. Pass a NULL to disable * translation. A new filename may be set at anytime. * WARNING: After changing the filename you should not access any data * retrieved by gettext(). */ int set_gettext_file( const char *filename ) { struct loaded_domain *domain = NULL; if( filename && *filename ) { if( filename[0] == '/' #ifdef HAVE_DRIVE_LETTERS || ( isalpha(filename[0]) && filename[1] == ':' && (filename[2] == '/' || filename[2] == '\\') ) #endif ) { /* absolute path - use it as is */ domain = load_domain( filename ); } else { /* relative path - append ".mo" and get dir from the environment */ char *buf = NULL; char *dir; char *p; dir = read_w32_registry_string( NULL, "Control Panel\\Mingw32\\NLS", "MODir" ); if( dir && (buf=malloc(strlen(dir)+strlen(filename)+1+3+1)) ) { strcpy(stpcpy(stpcpy(stpcpy( buf, dir),"\\"), filename),".mo"); /* Better make sure that we don't mix forward and backward slashes. It seems that some Windoze versions don't accept this. */ for (p=buf; *p; p++) { if (*p == '/') *p = '\\'; } domain = load_domain( buf ); free(buf); } free(dir); } if( !domain ) return -1; } if( the_domain ) { struct overflow_space_s *os, *os2; free( the_domain->data ); free( the_domain->mapped ); for (os=the_domain->overflow_space; os; os = os2) { os2 = os->next; free (os); } free( the_domain ); the_domain = NULL; } the_domain = domain; return 0; } static const char* get_string( struct loaded_domain *domain, u32 idx ) { struct overflow_space_s *os; char *p; p = domain->data + SWAPIT(domain->must_swap, domain->trans_tab[idx].offset); if (!domain->mapped[idx]) { size_t plen, buflen; char *buf; domain->mapped[idx] = 1; plen = strlen (p); buf = utf8_to_native (p, plen, -1); buflen = strlen (buf); if (buflen <= plen) strcpy (p, buf); else { /* There is not enough space for the translation - store it in the overflow_space else and mark that in the mapped array. Because we expect that this won't happen too often, we use a simple linked list. */ os = malloc (sizeof *os + buflen); if (os) { os->idx = idx; strcpy (os->d, buf); os->next = domain->overflow_space; domain->overflow_space = os; p = os->d; } else p = "ERROR in GETTEXT MALLOC"; } xfree (buf); } else if (domain->mapped[idx] == 2) { /* We need to get the string from the overflow_space. */ for (os=domain->overflow_space; os; os = os->next) if (os->idx == idx) return (const char*)os->d; p = "ERROR in GETTEXT\n"; } return (const char*)p; } const char * gettext( const char *msgid ) { struct loaded_domain *domain; size_t act = 0; size_t top, bottom; if( !(domain = the_domain) ) goto not_found; /* Locate the MSGID and its translation. */ if( domain->hash_size > 2 && domain->hash_tab ) { /* Use the hashing table. */ u32 len = strlen (msgid); u32 hash_val = hash_string (msgid); u32 idx = hash_val % domain->hash_size; u32 incr = 1 + (hash_val % (domain->hash_size - 2)); u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]); if ( !nstr ) /* Hash table entry is empty. */ goto not_found; if( SWAPIT(domain->must_swap, domain->orig_tab[nstr - 1].length) == len && !strcmp( msgid, domain->data + SWAPIT(domain->must_swap, domain->orig_tab[nstr - 1].offset)) ) return get_string( domain, nstr - 1 ); for(;;) { if (idx >= domain->hash_size - incr) idx -= domain->hash_size - incr; else idx += incr; nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]); if( !nstr ) goto not_found; /* Hash table entry is empty. */ if ( SWAPIT(domain->must_swap, domain->orig_tab[nstr - 1].length) == len && !strcmp (msgid, domain->data + SWAPIT(domain->must_swap, domain->orig_tab[nstr - 1].offset))) return get_string( domain, nstr-1 ); } /* NOTREACHED */ } /* Now we try the default method: binary search in the sorted array of messages. */ bottom = 0; top = domain->nstrings; while( bottom < top ) { int cmp_val; act = (bottom + top) / 2; cmp_val = strcmp(msgid, domain->data + SWAPIT(domain->must_swap, domain->orig_tab[act].offset)); if (cmp_val < 0) top = act; else if (cmp_val > 0) bottom = act + 1; else return get_string( domain, act ); } not_found: return msgid; } #if 0 unsigned int cp1, cp2; cp1 = GetConsoleCP(); cp2 = GetConsoleOutputCP(); log_info("InputCP=%u OutputCP=%u\n", cp1, cp2 ); if( !SetConsoleOutputCP( 1252 ) ) log_info("SetConsoleOutputCP failed: %s\n", w32_strerror (0)); cp1 = GetConsoleCP(); cp2 = GetConsoleOutputCP(); log_info("InputCP=%u OutputCP=%u after switch1\n", cp1, cp2 ); #endif #endif /* USE_SIMPLE_GETTEXT */ diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index f156ca3f1..e405c1ec0 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -1,629 +1,630 @@ /* simple-pwquery.c - A simple password query client for gpg-agent * Copyright (C) 2002, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * 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 #endif #include #include #include #include #include #ifdef HAVE_W32_SYSTEM #include #else #include #include #endif #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_W32_SYSTEM #include "../jnlib/w32-afunix.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 #ifndef HAVE_STPCPY static char * my_stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } #define stpcpy(a,b) my_stpcpy((a), (b)) #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) { #ifdef HAVE_W32_SYSTEM nwritten = send (fd, buf, nleft, 0); #else nwritten = write (fd, buf, nleft); #endif 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) { #ifdef HAVE_W32_SYSTEM int n = recv (fd, buf, nleft, 0); #else int n = read (fd, buf, nleft); #endif 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"); #ifndef HAVE_W32_SYSTEM if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); #endif 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 || !*infostr ) { #ifdef SPWQ_USE_LOGGING log_error (_("gpg-agent is not available in this session\n")); #endif return SPWQ_NO_AGENT; } p = spwq_malloc (strlen (infostr)+1); if (!p) return SPWQ_OUT_OF_CORE; strcpy (p, infostr); infostr = p; if ( !(p = strchr ( infostr, PATHSEP_C)) || 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 != PATHSEP_C) 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; } #ifdef HAVE_W32_SYSTEM fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); #else fd = socket (AF_UNIX, SOCK_STREAM, 0); #endif if (fd == -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); #ifdef HAVE_W32_SYSTEM rc = _w32_sock_connect (fd, (struct sockaddr*)&client_addr, len ); #else rc = connect (fd, (struct sockaddr*)&client_addr, len ); #endif if (rc == -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 pointer 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; const unsigned char *s = (unsigned char *)text; char *p = buffer; for (i=0; s[i]; i++) { if (s[i] < ' ' || s[i] == '+') { sprintf (p, "%%%02X", s[i]); p += 3; } else if (s[i] == ' ') *p++ = '+'; else *p++ = s[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') ) || ((nread > 4 && !memcmp (pw, "ERR ", 4) && (strtoul (pw+4, NULL, 0) & 0xffff) == 99)) ) { /* 111 is the old Assuan code for canceled which might still be in use by old installations. 99 is GPG_ERR_CANCELED as used by modern gpg-agents; 0xffff is used to mask out the error source. */ #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_secure_free (pw); return result; } /* Ask the gpg-agent to clear the passphrase for the cache ID CACHEID. */ int simple_pwclear (const char *cacheid) { char line[500]; char *p; /* We need not more than 50 characters for the command and the terminating nul. */ if (strlen (cacheid) * 3 > sizeof (line) - 50) return SPWQ_PROTOCOL_ERROR; strcpy (line, "CLEAR_PASSPHRASE "); p = line + 17; p = copy_and_escape (p, cacheid); *p++ = '\n'; *p++ = '\0'; return simple_query (line); } /* Perform the simple query QUERY (which must be new-line and 0 terminated) and return the error code. */ int simple_query (const char *query) { int fd = -1; int nread; char response[500]; int rc; rc = agent_open (&fd); if (rc) goto leave; rc = writen (fd, query, strlen (query)); if (rc) goto leave; /* get response */ nread = readline (fd, response, 499); if (nread < 0) { rc = -nread; goto leave; } if (nread < 3) { rc = SPWQ_PROTOCOL_ERROR; goto leave; } if (response[0] == 'O' && response[1] == 'K') /* OK, do nothing. */; else if ((nread > 7 && !memcmp (response, "ERR 111", 7) && (response[7] == ' ' || response[7] == '\n') ) || ((nread > 4 && !memcmp (response, "ERR ", 4) && (strtoul (response+4, NULL, 0) & 0xffff) == 99)) ) { /* 111 is the old Assuan code for canceled which might still be in use by old installations. 99 is GPG_ERR_CANCELED as used by modern gpg-agents; 0xffff is used to mask out the error source. */ #ifdef SPWQ_USE_LOGGING log_info (_("canceled by user\n") ); #endif } else { #ifdef SPWQ_USE_LOGGING log_error (_("problem with the agent\n")); #endif rc = SPWQ_ERR_RESPONSE; } leave: if (fd != -1) close (fd); return rc; } diff --git a/common/simple-pwquery.h b/common/simple-pwquery.h index e3270d6c5..5b941d06f 100644 --- a/common/simple-pwquery.h +++ b/common/simple-pwquery.h @@ -1,75 +1,76 @@ /* simple-pwquery.c - A simple password query cleint 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef SIMPLE_PWQUERY_H #define SIMPLE_PWQUERY_H #ifdef SIMPLE_PWQUERY_IMPLEMENTATION /* Begin configuration stuff. */ /* Include whatever files you need. */ #include #include "../jnlib/logging.h" /* Try to write error message using the standard log mechanism. The current implementation requires that the HAVE_JNLIB_LOGGING is also defined. */ #define SPWQ_USE_LOGGING 1 /* Memory allocation functions used by the implementation. Note, that the returned value is expected to be freed with spwq_secure_free. */ #define spwq_malloc(a) gcry_malloc (a) #define spwq_free(a) gcry_free (a) #define spwq_secure_malloc(a) gcry_malloc_secure (a) #define spwq_secure_free(a) gcry_free (a) #endif /*SIMPLE_PWQUERY_IMPLEMENTATION*/ /* End configuration stuff. */ /* 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; this 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); /* Ask the gpg-agent to clear the passphrase for the cache ID CACHEID. */ int simple_pwclear (const char *cacheid); /* Perform the simple query QUERY (which must be new-line and 0 terminated) and return the error code. */ int simple_query (const char *query); #define SPWQ_OUT_OF_CORE 1 #define SPWQ_IO_ERROR 2 #define SPWQ_PROTOCOL_ERROR 3 #define SPWQ_ERR_RESPONSE 4 #define SPWQ_NO_AGENT 5 #define SPWQ_SYS_ERROR 6 #define SPWQ_GENERAL_ERROR 7 #endif /*SIMPLE_PWQUERY_H*/ diff --git a/common/sysutils.c b/common/sysutils.c index a8f6f6f5d..3e52cdaa3 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -1,230 +1,231 @@ /* sysutils.c - system helpers * Copyright (C) 1998, 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #ifdef HAVE_STAT #include #endif #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 #include #include #endif #ifdef HAVE_SETRLIMIT #include #include #include #endif #include "util.h" #include "i18n.h" #include "sysutils.h" #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 #warning using trap_unaligned static int setsysinfo(unsigned long op, void *buffer, unsigned long size, int *start, void *arg, unsigned long flag) { return syscall(__NR_osf_setsysinfo, op, buffer, size, start, arg, flag); } void trap_unaligned(void) { unsigned int buf[2]; buf[0] = SSIN_UACPROC; buf[1] = UAC_SIGBUS | UAC_NOPRINT; setsysinfo(SSI_NVPAIRS, buf, 1, 0, 0, 0); } #else void trap_unaligned(void) { /* dummy */ } #endif int disable_core_dumps (void) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else # ifdef HAVE_SETRLIMIT struct rlimit limit; /* We only set the current limit unless we were not able to retrieve the old value. */ if (getrlimit (RLIMIT_CORE, &limit)) limit.rlim_max = 0; limit.rlim_cur = 0; if( !setrlimit (RLIMIT_CORE, &limit) ) return 0; if( errno != EINVAL && errno != ENOSYS ) log_fatal (_("can't disable core dumps: %s\n"), strerror(errno) ); #endif return 1; #endif } int enable_core_dumps (void) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else # ifdef HAVE_SETRLIMIT struct rlimit limit; if (getrlimit (RLIMIT_CORE, &limit)) return 1; limit.rlim_cur = limit.rlim_max; setrlimit (RLIMIT_CORE, &limit); return 1; /* We always return true because trhis function is merely a debugging aid. */ # endif return 1; #endif } /* Return a string which is used as a kind of process ID */ const byte * get_session_marker( size_t *rlen ) { static byte marker[SIZEOF_UNSIGNED_LONG*2]; static int initialized; if ( !initialized ) { volatile ulong aa, bb; /* we really want the uninitialized value */ ulong a, b; initialized = 1; /* also this marker is guessable it is not easy to use this * for a faked control packet because an attacker does not * have enough control about the time the verification does * take place. Of course, we can add just more random but * than we need the random generator even for verification * tasks - which does not make sense. */ a = aa ^ (ulong)getpid(); b = bb ^ (ulong)time(NULL); memcpy( marker, &a, SIZEOF_UNSIGNED_LONG ); memcpy( marker+SIZEOF_UNSIGNED_LONG, &b, SIZEOF_UNSIGNED_LONG ); } *rlen = sizeof(marker); return marker; } #if 0 /* not yet needed - Note that this will require inclusion of cmacros.am in Makefile.am */ int check_permissions(const char *path,int extension,int checkonly) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) char *tmppath; struct stat statbuf; int ret=1; int isdir=0; if(opt.no_perm_warn) return 0; if(extension && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(GNUPG_LIBDIR,path,NULL); } else tmppath=m_strdup(path); /* It's okay if the file doesn't exist */ if(stat(tmppath,&statbuf)!=0) { ret=0; goto end; } isdir=S_ISDIR(statbuf.st_mode); /* Per-user files must be owned by the user. Extensions must be owned by the user or root. */ if((!extension && statbuf.st_uid != getuid()) || (extension && statbuf.st_uid!=0 && statbuf.st_uid!=getuid())) { if(!checkonly) log_info(_("Warning: unsafe ownership on %s \"%s\"\n"), isdir?"directory":extension?"extension":"file",path); goto end; } /* This works for both directories and files - basically, we don't care what the owner permissions are, so long as the group and other permissions are 0 for per-user files, and non-writable for extensions. */ if((extension && (statbuf.st_mode & (S_IWGRP|S_IWOTH)) !=0) || (!extension && (statbuf.st_mode & (S_IRWXG|S_IRWXO)) != 0)) { char *dir; /* However, if the directory the directory/file is in is owned by the user and is 700, then this is not a problem. Theoretically, we could walk this test up to the root directory /, but for the sake of sanity, I'm stopping at one level down. */ dir= make_dirname (tmppath); if(stat(dir,&statbuf)==0 && statbuf.st_uid==getuid() && S_ISDIR(statbuf.st_mode) && (statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) { xfree (dir); ret=0; goto end; } m_free(dir); if(!checkonly) log_info(_("Warning: unsafe permissions on %s \"%s\"\n"), isdir?"directory":extension?"extension":"file",path); goto end; } ret=0; end: m_free(tmppath); return ret; #endif /* HAVE_STAT && !HAVE_DOSISH_SYSTEM */ return 0; } #endif diff --git a/common/sysutils.h b/common/sysutils.h index 08198f685..c40dbfaa9 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -1,46 +1,47 @@ /* sysutils.h - System utility functions for Gnupg * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_SYSUTILS_H #define GNUPG_COMMON_SYSUTILS_H void trap_unaligned (void); int disable_core_dumps (void); int enable_core_dumps (void); const unsigned char *get_session_marker (size_t *rlen); int check_permissions (const char *path,int extension,int checkonly); #ifdef HAVE_W32_SYSTEM /* Windows declares sleep as obsolete, but provides a definition for _sleep but non for the still existing sleep. */ #define sleep(a) _sleep ((a)) /*-- w32reg.c --*/ char *read_w32_registry_string( const char *root, const char *dir, const char *name ); int write_w32_registry_string(const char *root, const char *dir, const char *name, const char *value); #endif /*HAVE_W32_SYSTEM*/ #endif /*GNUPG_COMMON_SYSUTILS_H*/ diff --git a/common/ttyio.c b/common/ttyio.c index c9f41c626..38883afa5 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -1,598 +1,599 @@ /* ttyio.c - tty i/O functions * Copyright (C) 1998,1999,2000,2001,2002,2003, * 2004, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #ifdef HAVE_TCGETATTR #include #else #ifdef HAVE_TERMIO_H /* simulate termios with termio */ #include #define termios termio #define tcsetattr ioctl #define TCSAFLUSH TCSETAF #define tcgetattr(A,B) ioctl(A,TCGETA,B) #define HAVE_TCGETATTR #endif #endif #ifdef _WIN32 /* use the odd Win32 functions */ #include #ifdef HAVE_TCGETATTR #error mingw32 and termios #endif #endif #include #include #ifdef HAVE_LIBREADLINE #include #include #endif #include "util.h" #include "memory.h" #include "ttyio.h" #define CONTROL_D ('D' - 'A' + 1) #ifdef _WIN32 /* use the odd Win32 functions */ static struct { HANDLE in, out; } con; #define DEF_INPMODE (ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT \ |ENABLE_PROCESSED_INPUT ) #define HID_INPMODE (ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT ) #define DEF_OUTMODE (ENABLE_WRAP_AT_EOL_OUTPUT|ENABLE_PROCESSED_OUTPUT) #else /* yeah, we have a real OS */ static FILE *ttyfp = NULL; #endif static int initialized; static int last_prompt_len; static int batchmode; static int no_terminal; #ifdef HAVE_TCGETATTR static struct termios termsave; static int restore_termios; #endif /* This is a wrapper around ttyname so that we can use it even when the standard streams are redirected. It figures the name out the first time and returns it in a statically allocated buffer. */ const char * tty_get_ttyname (void) { static char *name; /* On a GNU system ctermid() always return /dev/tty, so this does not make much sense - however if it is ever changed we do the Right Thing now. */ #ifdef HAVE_CTERMID static int got_name; if (!got_name) { const char *s; /* Note that despite our checks for these macros the function is not necessarily thread save. We mainly do this for portability reasons, in case L_ctermid is not defined. */ # if defined(_POSIX_THREAD_SAFE_FUNCTIONS) || defined(_POSIX_TRHEADS) char buffer[L_ctermid]; s = ctermid (buffer); # else s = ctermid (NULL); # endif if (s) name = strdup (s); got_name = 1; } #endif /*HAVE_CTERMID*/ /* Assume the standard tty on memory error or when tehre is no certmid. */ return name? name : "/dev/tty"; } #ifdef HAVE_TCGETATTR static void cleanup(void) { if( restore_termios ) { restore_termios = 0; /* do it prios in case it is interrupted again */ if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) ) log_error("tcsetattr() failed: %s\n", strerror(errno) ); } } #endif static void init_ttyfp(void) { if( initialized ) return; #if defined(_WIN32) { SECURITY_ATTRIBUTES sa; memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; con.out = CreateFileA( "CONOUT$", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0 ); if( con.out == INVALID_HANDLE_VALUE ) log_fatal("open(CONOUT$) failed: rc=%d", (int)GetLastError() ); memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; con.in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0 ); if( con.in == INVALID_HANDLE_VALUE ) log_fatal("open(CONIN$) failed: rc=%d", (int)GetLastError() ); } SetConsoleMode(con.in, DEF_INPMODE ); SetConsoleMode(con.out, DEF_OUTMODE ); #elif defined(__EMX__) ttyfp = stdout; /* Fixme: replace by the real functions: see wklib */ #else ttyfp = batchmode? stderr : fopen (tty_get_ttyname (), "r+"); if( !ttyfp ) { log_error("cannot open `%s': %s\n", tty_get_ttyname (), strerror(errno) ); exit(2); } #endif #ifdef HAVE_TCGETATTR atexit( cleanup ); #endif initialized = 1; } #ifdef HAVE_LIBREADLINE void tty_enable_completion(rl_completion_func_t *completer) { /* if( no_terminal ) */ /* return; */ /* if( !initialized ) */ /* init_ttyfp(); */ /* rl_attempted_completion_function=completer; */ /* rl_inhibit_completion=0; */ } void tty_disable_completion(void) { /* if( no_terminal ) */ /* return; */ /* if( !initialized ) */ /* init_ttyfp(); */ /* rl_inhibit_completion=1; */ } #endif /*HAVE_LIBREADLINE*/ int tty_batchmode( int onoff ) { int old = batchmode; if( onoff != -1 ) batchmode = onoff; return old; } int tty_no_terminal(int onoff) { int old = no_terminal; no_terminal = onoff ? 1 : 0; return old; } void tty_printf( const char *fmt, ... ) { va_list arg_ptr; if (no_terminal) return; if( !initialized ) init_ttyfp(); va_start( arg_ptr, fmt ) ; #ifdef _WIN32 { char *buf = NULL; int n; DWORD nwritten; n = vasprintf(&buf, fmt, arg_ptr); if( !buf ) log_bug("vasprintf() failed\n"); if( !WriteConsoleA( con.out, buf, n, &nwritten, NULL ) ) log_fatal("WriteConsole failed: rc=%d", (int)GetLastError() ); if( n != nwritten ) log_fatal("WriteConsole failed: %d != %d\n", n, (int)nwritten ); last_prompt_len += n; xfree (buf); } #else last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ; fflush(ttyfp); #endif va_end(arg_ptr); } /* Same as tty_printf but if FP is not NULL, behave like a regualr fprintf. */ void tty_fprintf (FILE *fp, const char *fmt, ... ) { va_list arg_ptr; if (fp) { va_start (arg_ptr, fmt) ; vfprintf (fp, fmt, arg_ptr ); va_end (arg_ptr); return; } if (no_terminal) return; if( !initialized ) init_ttyfp(); va_start( arg_ptr, fmt ) ; #ifdef _WIN32 { char *buf = NULL; int n; DWORD nwritten; n = vasprintf(&buf, fmt, arg_ptr); if( !buf ) log_bug("vasprintf() failed\n"); if( !WriteConsoleA( con.out, buf, n, &nwritten, NULL ) ) log_fatal("WriteConsole failed: rc=%d", (int)GetLastError() ); if( n != nwritten ) log_fatal("WriteConsole failed: %d != %d\n", n, (int)nwritten ); last_prompt_len += n; xfree (buf); } #else last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ; fflush(ttyfp); #endif va_end(arg_ptr); } /**************** * Print a string, but filter all control characters out. */ void tty_print_string ( const byte *p, size_t n ) { if (no_terminal) return; if( !initialized ) init_ttyfp(); #ifdef _WIN32 /* not so effective, change it if you want */ for( ; n; n--, p++ ) if( iscntrl( *p ) ) { if( *p == '\n' ) tty_printf("\\n"); else if( !*p ) tty_printf("\\0"); else tty_printf("\\x%02x", *p); } else tty_printf("%c", *p); #else for( ; n; n--, p++ ) if( iscntrl( *p ) ) { putc('\\', ttyfp); if( *p == '\n' ) putc('n', ttyfp); else if( !*p ) putc('0', ttyfp); else fprintf(ttyfp, "x%02x", *p ); } else putc(*p, ttyfp); #endif } void tty_print_utf8_string2( const byte *p, size_t n, size_t max_n ) { size_t i; char *buf; if (no_terminal) return; /* we can handle plain ascii simpler, so check for it first */ for(i=0; i < n; i++ ) { if( p[i] & 0x80 ) break; } if( i < n ) { buf = utf8_to_native( (const char *)p, n, 0 ); if( max_n && (strlen( buf ) > max_n )) { buf[max_n] = 0; } /*(utf8 conversion already does the control character quoting)*/ tty_printf("%s", buf ); xfree( buf ); } else { if( max_n && (n > max_n) ) { n = max_n; } tty_print_string( p, n ); } } void tty_print_utf8_string( const byte *p, size_t n ) { tty_print_utf8_string2( p, n, 0 ); } static char * do_get( const char *prompt, int hidden ) { char *buf; #ifndef __riscos__ byte cbuf[1]; #endif int c, n, i; if( batchmode ) { log_error("Sorry, we are in batchmode - can't get input\n"); exit(2); } if (no_terminal) { log_error("Sorry, no terminal at all requested - can't get input\n"); exit(2); } if( !initialized ) init_ttyfp(); last_prompt_len = 0; tty_printf( "%s", prompt ); buf = xmalloc((n=50)); i = 0; #ifdef _WIN32 /* windoze version */ if( hidden ) SetConsoleMode(con.in, HID_INPMODE ); for(;;) { DWORD nread; if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) ) log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() ); if( !nread ) continue; if( *cbuf == '\n' ) break; if( !hidden ) last_prompt_len++; c = *cbuf; if( c == '\t' ) c = ' '; else if( c > 0xa0 ) ; /* we don't allow 0xa0, as this is a protected blank which may * confuse the user */ else if( iscntrl(c) ) continue; if( !(i < n-1) ) { n += 50; buf = xrealloc (buf, n); } buf[i++] = c; } if( hidden ) SetConsoleMode(con.in, DEF_INPMODE ); #elif defined(__riscos__) do { c = riscos_getchar(); if (c == 0xa || c == 0xd) { /* Return || Enter */ c = (int) '\n'; } else if (c == 0x8 || c == 0x7f) { /* Backspace || Delete */ if (i>0) { i--; if (!hidden) { last_prompt_len--; fputc(8, ttyfp); fputc(32, ttyfp); fputc(8, ttyfp); fflush(ttyfp); } } else { fputc(7, ttyfp); fflush(ttyfp); } continue; } else if (c == (int) '\t') { /* Tab */ c = ' '; } else if (c > 0xa0) { ; /* we don't allow 0xa0, as this is a protected blank which may * confuse the user */ } else if (iscntrl(c)) { continue; } if(!(i < n-1)) { n += 50; buf = xrealloc (buf, n); } buf[i++] = c; if (!hidden) { last_prompt_len++; fputc(c, ttyfp); fflush(ttyfp); } } while (c != '\n'); i = (i>0) ? i-1 : 0; #else /* unix version */ if( hidden ) { #ifdef HAVE_TCGETATTR struct termios term; if( tcgetattr(fileno(ttyfp), &termsave) ) log_fatal("tcgetattr() failed: %s\n", strerror(errno) ); restore_termios = 1; term = termsave; term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) log_fatal("tcsetattr() failed: %s\n", strerror(errno) ); #endif } /* fixme: How can we avoid that the \n is echoed w/o disabling * canonical mode - w/o this kill_prompt can't work */ while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) { if( !hidden ) last_prompt_len++; c = *cbuf; if( c == CONTROL_D ) log_info("control d found\n"); if( c == '\t' ) c = ' '; else if( c > 0xa0 ) ; /* we don't allow 0xa0, as this is a protected blank which may * confuse the user */ else if( iscntrl(c) ) continue; if( !(i < n-1) ) { n += 50; buf = xrealloc (buf, n ); } buf[i++] = c; } if( *cbuf != '\n' ) { buf[0] = CONTROL_D; i = 1; } if( hidden ) { #ifdef HAVE_TCGETATTR if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) ) log_error("tcsetattr() failed: %s\n", strerror(errno) ); restore_termios = 0; #endif } #endif /* end unix version */ buf[i] = 0; return buf; } char * tty_get( const char *prompt ) { return do_get( prompt, 0 ); } char * tty_get_hidden( const char *prompt ) { return do_get( prompt, 1 ); } void tty_kill_prompt() { if ( no_terminal ) return; if( !initialized ) init_ttyfp(); if( batchmode ) last_prompt_len = 0; if( !last_prompt_len ) return; #ifdef _WIN32 tty_printf("\r%*s\r", last_prompt_len, ""); #else { int i; putc('\r', ttyfp); for(i=0; i < last_prompt_len; i ++ ) putc(' ', ttyfp); putc('\r', ttyfp); fflush(ttyfp); } #endif last_prompt_len = 0; } int tty_get_answer_is_yes( const char *prompt ) { int yes; char *p = tty_get( prompt ); tty_kill_prompt(); yes = answer_is_yes(p); xfree(p); return yes; } diff --git a/common/ttyio.h b/common/ttyio.h index 6148d644a..32d159863 100644 --- a/common/ttyio.h +++ b/common/ttyio.h @@ -1,60 +1,61 @@ /* ttyio.h * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_TTYIO_H #define GNUPG_COMMON_TTYIO_H #ifdef HAVE_LIBREADLINE #include #include #endif const char *tty_get_ttyname (void); int tty_batchmode (int onoff); #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) void tty_printf (const char *fmt, ... ) __attribute__ ((format (printf,1,2))); void tty_fprintf (FILE *fp, const char *fmt, ... ) __attribute__ ((format (printf,2,3))); #else void tty_printf (const char *fmt, ... ); void tty_fprintf (FILE *fp, const char *fmt, ... ); #endif void tty_print_string (const unsigned char *p, size_t n); void tty_print_utf8_string (const unsigned char *p, size_t n); void tty_print_utf8_string2 (const unsigned char *p, size_t n, size_t max_n); char *tty_get (const char *prompt); char *tty_get_hidden (const char *prompt); void tty_kill_prompt (void); int tty_get_answer_is_yes (const char *prompt); int tty_no_terminal (int onoff); #ifdef HAVE_LIBREADLINE void tty_enable_completion(rl_completion_func_t *completer); void tty_disable_completion(void); #else /* Use a macro to stub out these functions since a macro has no need to typedef a "rl_completion_func_t" which would be undefined without readline. */ #define tty_enable_completion(x) #define tty_disable_completion() #endif #endif /*GNUPG_COMMON_TTYIO_H*/ diff --git a/common/util.h b/common/util.h index 295d785c5..29106bf9c 100644 --- a/common/util.h +++ b/common/util.h @@ -1,208 +1,209 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need time_t. */ #include /* we need gpg-error_t. */ /* Common GNUlib includes (-I ../gl/). */ #include "strpbrk.h" #include "strsep.h" #include "vasprintf.h" /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* get all the stuff from jnlib */ #include "../jnlib/logging.h" #include "../jnlib/argparse.h" #include "../jnlib/stringhelp.h" #include "../jnlib/mischelp.h" #include "../jnlib/strlist.h" #include "../jnlib/dotlock.h" #include "../jnlib/utf8conv.h" /* Handy malloc macros - please use only them. */ #define xtrymalloc(a) gcry_malloc ((a)) #define xtrymalloc_secure(a) gcry_malloc_secure ((a)) #define xtrycalloc(a,b) gcry_calloc ((a),(b)) #define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b)) #define xtryrealloc(a,b) gcry_realloc ((a),(b)) #define xtrystrdup(a) gcry_strdup ((a)) #define xfree(a) gcry_free ((a)) #define xmalloc(a) gcry_xmalloc ((a)) #define xmalloc_secure(a) gcry_xmalloc_secure ((a)) #define xcalloc(a,b) gcry_xcalloc ((a),(b)) #define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) #define xrealloc(a,b) gcry_xrealloc ((a),(b)) #define xstrdup(a) gcry_xstrdup ((a)) /* For compatibility with gpg 1.4 we also define these: */ #define xmalloc_clear(a) gcry_xcalloc (1, (a)) #define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a)) /* A type to hold the ISO time. Note that this this is the same as the the KSBA type ksba_isotime_t. */ typedef char gnupg_isotime_t[16]; /*-- maperror.c --*/ int map_kbx_err (int err); gpg_error_t map_assuan_err_with_source (int source, int err); int map_to_assuan_status (int rc); /*-- gettime.c --*/ time_t gnupg_get_time (void); void gnupg_get_isotime (gnupg_isotime_t timebuf); void gnupg_set_time (time_t newtime, int freeze); int gnupg_faked_time_p (void); u32 make_timestamp (void); u32 scan_isodatestr (const char *string); u32 add_days_to_timestamp (u32 stamp, u16 days); const char *strtimevalue (u32 stamp); const char *strtimestamp (u32 stamp); /* GMT */ const char *isotimestamp (u32 stamp); /* GMT */ const char *asctimestamp (u32 stamp); /* localized */ /* Copy one iso ddate to another, this is inline so that we can do a sanity check. */ static inline void gnupg_copy_time (gnupg_isotime_t d, const gnupg_isotime_t s) { if (*s && (strlen (s) != 15 || s[8] != 'T')) BUG(); strcpy (d, s); } /*-- signal.c --*/ void gnupg_init_signals (int mode, void (*fast_cleanup)(void)); void gnupg_pause_on_sigusr (int which); void gnupg_block_all_signals (void); void gnupg_unblock_all_signals (void); /*-- yesno.c --*/ int answer_is_yes (const char *s); int answer_is_yes_no_default (const char *s, int def_answer); int answer_is_yes_no_quit (const char *s); int answer_is_okay_cancel (const char *s, int def_answer); /*-- xreadline.c --*/ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, size_t *max_length); /*-- b64enc.c --*/ struct b64state { unsigned int flags; int idx; int quad_count; FILE *fp; char *title; unsigned char radbuf[4]; }; gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); gpg_error_t b64enc_write (struct b64state *state, const void *buffer, size_t nbytes); gpg_error_t b64enc_finish (struct b64state *state); /*-- sexputil.c */ gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); /*-- homedir.c --*/ const char *default_homedir (void); /*-- miscellaneous.c --*/ /* Same as asprintf but return an allocated buffer suitable to be freed using xfree. This function simply dies on memory failure, thus no extra check is required. */ char *xasprintf (const char *fmt, ...) JNLIB_GCC_A_PRINTF(1,2); /* Same as asprintf but return an allocated buffer suitable to be freed using xfree. This function returns NULL on memory failure and sets errno. */ char *xtryasprintf (const char *fmt, ...) JNLIB_GCC_A_PRINTF(1,2); const char *print_fname_stdout (const char *s); const char *print_fname_stdin (const char *s); void print_string (FILE *fp, const byte *p, size_t n, int delim); void print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim); void print_utf8_string (FILE *fp, const byte *p, size_t n); char *make_printable_string (const void *p, size_t n, int delim); int is_file_compressed (const char *s, int *ret_rc); int match_multistr (const char *multistr,const char *match); /*-- Simple replacement functions. */ #ifndef HAVE_TTYNAME /* Systems without ttyname (W32) will merely return NULL. */ static inline char * ttyname (int fd) { return NULL }; #endif /* !HAVE_TTYNAME */ #ifndef HAVE_ISASCII static inline int isascii (int c) { return (((c) & ~0x7f) == 0); } #endif /* !HAVE_ISASCII */ /*-- Macros to replace ctype ones to avoid locale problems. --*/ #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* Note this isn't identical to a C locale isspace() without \f and \v, but works for the purposes used here. */ #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') /* The atoi macros assume that the buffer has only valid digits. */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #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)) #define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2)) #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/common/vasprintf.c b/common/vasprintf.c index 9efea33f2..4bed8de66 100644 --- a/common/vasprintf.c +++ b/common/vasprintf.c @@ -1,169 +1,169 @@ /* Like vsprintf but provides a pointer to malloc'd storage, which must be freed by the caller. Copyright (C) 1994, 2002 Free Software Foundation, Inc. This file is part of the libiberty library. Libiberty is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Libiberty 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with libiberty; see the file COPYING.LIB. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. */ +not, write to the Free Software Foundation, Inc., 51 Franklin Street, +Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef TEST int global_total_width; #endif int vasprintf (char **result, const char *format, va_list args) { const char *p = format; /* Add one to make sure that it is never zero, which might cause malloc to return NULL. */ int total_width = strlen (format) + 1; va_list ap; #ifdef va_copy va_copy (ap, args); #else #ifdef __va_copy __va_copy (ap, args); #else memcpy (&ap, args, sizeof (va_list)); #endif /* __va_copy */ #endif /* va_copy */ while (*p != '\0') { if (*p++ == '%') { while (strchr ("-+ #0", *p)) ++p; if (*p == '*') { ++p; total_width += abs (va_arg (ap, int)); } else total_width += strtoul (p, (char**)&p, 10); if (*p == '.') { ++p; if (*p == '*') { ++p; total_width += abs (va_arg (ap, int)); } else total_width += strtoul (p, (char**)&p, 10); } while (strchr ("hlL", *p)) ++p; /* Should be big enough for any format specifier except %s and floats. */ total_width += 30; switch (*p) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'c': (void) va_arg (ap, int); break; case 'f': case 'e': case 'E': case 'g': case 'G': (void) va_arg (ap, double); /* Since an ieee double can have an exponent of 307, we'll make the buffer wide enough to cover the gross case. */ total_width += 307; break; case 's': { char *tmp = va_arg (ap, char *); if (tmp) total_width += strlen (tmp); else /* in case the vsprintf does prints a text */ total_width += 25; /* e.g. "(null pointer reference)" */ } break; case 'p': case 'n': (void) va_arg (ap, char *); break; } } } #ifdef TEST global_total_width = total_width; #endif *result = malloc (total_width); if (*result != NULL) return vsprintf (*result, format, args); else return 0; } int asprintf (char **buf, const char *fmt, ...) { int status; va_list ap; va_start (ap, fmt); status = vasprintf (buf, fmt, ap); va_end (ap); return status; } #ifdef TEST void checkit (const char* format, ...) { va_list args; char *result; va_start (args, format); vasprintf (&result, format, args); if (strlen (result) < global_total_width) printf ("PASS: "); else printf ("FAIL: "); printf ("%d %s\n", global_total_width, result); } int main (void) { checkit ("%d", 0x12345678); checkit ("%200d", 5); checkit ("%.300d", 6); checkit ("%100.150d", 7); checkit ("%s", "jjjjjjjjjiiiiiiiiiiiiiiioooooooooooooooooppppppppppppaa\n\ 777777777777777777333333333333366666666666622222222222777777777777733333"); checkit ("%f%s%d%s", 1.0, "foo", 77, "asdjffffffffffffffiiiiiiiiiiixxxxx"); } #endif /* TEST */ diff --git a/common/w32reg.c b/common/w32reg.c index a85ac7348..84308e916 100644 --- a/common/w32reg.c +++ b/common/w32reg.c @@ -1,173 +1,174 @@ /* w32reg.c - MS-Windows Registry access * Copyright (C) 1999, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #ifdef HAVE_W32_SYSTEM /* This module is only used in this environment */ #include #include #include #include #include #include "util.h" #include "sysutils.h" static HKEY get_root_key(const char *root) { HKEY root_key; if( !root ) root_key = HKEY_CURRENT_USER; else if( !strcmp( root, "HKEY_CLASSES_ROOT" ) ) root_key = HKEY_CLASSES_ROOT; else if( !strcmp( root, "HKEY_CURRENT_USER" ) ) root_key = HKEY_CURRENT_USER; else if( !strcmp( root, "HKEY_LOCAL_MACHINE" ) ) root_key = HKEY_LOCAL_MACHINE; else if( !strcmp( root, "HKEY_USERS" ) ) root_key = HKEY_USERS; else if( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) ) root_key = HKEY_PERFORMANCE_DATA; else if( !strcmp( root, "HKEY_CURRENT_CONFIG" ) ) root_key = HKEY_CURRENT_CONFIG; else return NULL; return root_key; } /**************** * Return a string from the Win32 Registry or NULL in case of * error. Caller must release the return value. A NULL for root * is an alias for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. * NOTE: The value is allocated with a plain malloc() - use free() and not * the usual m_free()!!! */ char * read_w32_registry_string( const char *root, const char *dir, const char *name ) { HKEY root_key, key_handle; DWORD n1, nbytes, type; char *result = NULL; if ( !(root_key = get_root_key(root) ) ) return NULL; if( RegOpenKeyEx( root_key, dir, 0, KEY_READ, &key_handle ) ) { if (root) return NULL; /* no need for a RegClose, so return direct */ /* It seems to be common practise to fall back to HLM. */ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) return NULL; /* still no need for a RegClose, so return direct */ } nbytes = 1; if( RegQueryValueEx( key_handle, name, 0, NULL, NULL, &nbytes ) ) goto leave; result = malloc( (n1=nbytes+1) ); if( !result ) goto leave; if( RegQueryValueEx( key_handle, name, 0, &type, result, &n1 ) ) { free(result); result = NULL; goto leave; } result[nbytes] = 0; /* make sure it is really a string */ if (type == REG_EXPAND_SZ && strchr (result, '%')) { char *tmp; n1 += 1000; tmp = malloc (n1+1); if (!tmp) goto leave; nbytes = ExpandEnvironmentStrings (result, tmp, n1); if (nbytes && nbytes > n1) { free (tmp); n1 = nbytes; tmp = malloc (n1 + 1); if (!tmp) goto leave; nbytes = ExpandEnvironmentStrings (result, tmp, n1); if (nbytes && nbytes > n1) { free (tmp); /* oops - truncated, better don't expand at all */ goto leave; } tmp[nbytes] = 0; free (result); result = tmp; } else if (nbytes) { /* okay, reduce the length */ tmp[nbytes] = 0; free (result); result = malloc (strlen (tmp)+1); if (!result) result = tmp; else { strcpy (result, tmp); free (tmp); } } else { /* error - don't expand */ free (tmp); } } leave: RegCloseKey( key_handle ); return result; } int write_w32_registry_string(const char *root, const char *dir, const char *name, const char *value) { HKEY root_key, reg_key; if ( !(root_key = get_root_key(root) ) ) return -1; if ( RegOpenKeyEx( root_key, dir, 0, KEY_WRITE, ®_key ) != ERROR_SUCCESS ) return -1; if ( RegSetValueEx( reg_key, name, 0, REG_SZ, (BYTE *)value, strlen( value ) ) != ERROR_SUCCESS ) { if ( RegCreateKey( root_key, name, ®_key ) != ERROR_SUCCESS ) { RegCloseKey(reg_key); return -1; } if ( RegSetValueEx( reg_key, name, 0, REG_SZ, (BYTE *)value, strlen( value ) ) != ERROR_SUCCESS ) { RegCloseKey(reg_key); return -1; } } RegCloseKey( reg_key ); return 0; } #endif /*HAVE_W32_SYSTEM*/ diff --git a/common/xasprintf.c b/common/xasprintf.c index 46740a2e6..75ae18072 100644 --- a/common/xasprintf.c +++ b/common/xasprintf.c @@ -1,62 +1,63 @@ /* xasprintf.c * Copyright (C) 2003, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include "util.h" #include "iobuf.h" /* Same as asprintf but return an allocated buffer suitable to be freed using xfree. This function simply dies on memory failure, thus no extra check is required. */ char * xasprintf (const char *fmt, ...) { va_list ap; char *buf, *p; va_start (ap, fmt); if (vasprintf (&buf, fmt, ap) < 0) log_fatal ("asprintf failed: %s\n", strerror (errno)); va_end (ap); p = xstrdup (buf); free (buf); return p; } /* Same as above but return NULL on memory failure. */ char * xtryasprintf (const char *fmt, ...) { int rc; va_list ap; char *buf, *p; va_start (ap, fmt); rc = vasprintf (&buf, fmt, ap); va_end (ap); if (rc < 0) return NULL; p = xtrystrdup (buf); free (buf); return p; } diff --git a/common/xreadline.c b/common/xreadline.c index 23aa35269..8400df330 100644 --- a/common/xreadline.c +++ b/common/xreadline.c @@ -1,116 +1,117 @@ /* xreadline.c - fgets replacement function * Copyright (C) 1999, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include "util.h" /* Same as fgets() but if the provided buffer is too short a larger one will be allocated. This is similar to getline. A line is considered a byte stream ending in a LF. If MAX_LENGTH is not NULL, it shall point to a value with the maximum allowed allocation. Returns the length of the line. EOF is indicated by a line of length zero. A truncated line is indicated my setting the value at MAX_LENGTH to 0. If the returned value is less then 0 not enough memory was enable and ERRNO is set accordingly. If a line has been truncated, the file pointer is moved forward to the end of the line so that the next read start with the next line. Note that MAX_LENGTH must be re-initialzied in this case.. Note: The returned buffer is allocated with enough extra space to append a CR,LF,Nul */ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, size_t *max_length) { int c; char *buffer = *addr_of_buffer; size_t length = *length_of_buffer; size_t nbytes = 0; size_t maxlen = max_length? *max_length : 0; char *p; if (!buffer) { /* No buffer given - allocate a new one. */ length = 256; buffer = xtrymalloc (length); *addr_of_buffer = buffer; if (!buffer) { *length_of_buffer = 0; if (max_length) *max_length = 0; return -1; } *length_of_buffer = length; } length -= 3; /* Reserve 3 bytes for CR,LF,EOL. */ p = buffer; while ((c = getc (fp)) != EOF) { if (nbytes == length) { /* Enlarge the buffer. */ if (maxlen && length > maxlen) /* But not beyond our limit. */ { /* Skip the rest of the line. */ while (c != '\n' && (c=getc (fp)) != EOF) ; *p++ = '\n'; /* Always append a LF (we reserved some space). */ nbytes++; if (max_length) *max_length = 0; /* Indicate truncation. */ break; /* the while loop. */ } length += 3; /* Adjust for the reserved bytes. */ length += length < 1024? 256 : 1024; *addr_of_buffer = xtryrealloc (buffer, length); if (!*addr_of_buffer) { int save_errno = errno; xfree (buffer); *length_of_buffer = *max_length = 0; errno = save_errno; return -1; } buffer = *addr_of_buffer; *length_of_buffer = length; length -= 3; p = buffer + nbytes; } *p++ = c; nbytes++; if (c == '\n') break; } *p = 0; /* Make sure the line is a string. */ return nbytes; } diff --git a/common/yesno.c b/common/yesno.c index 737071691..9ca513740 100644 --- a/common/yesno.c +++ b/common/yesno.c @@ -1,138 +1,139 @@ /* yesno.c - Yes/No questions * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include "i18n.h" #include "util.h" int answer_is_yes_no_default( const char *s, int def_answer ) { /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_yes = _("yes"); const char *short_yes = _("yY"); /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_no = _("no"); const char *short_no = _("nN"); /* Note: we have to use the local dependent compare here. */ if ( match_multistr(long_yes,s) ) return 1; if ( *s && strchr( short_yes, *s ) && !s[1] ) return 1; /* Test for "no" strings to catch ambiguities for the next test. */ if ( match_multistr(long_no,s) ) return 0; if ( *s && strchr( short_no, *s ) && !s[1] ) return 0; /* Test for the english version (for those who are used to type yes). */ if ( !ascii_strcasecmp(s, "yes" ) ) return 1; if ( *s && strchr( "yY", *s ) && !s[1] ) return 1; return def_answer; } int answer_is_yes ( const char *s ) { return answer_is_yes_no_default(s,0); } /**************** * Return 1 for yes, -1 for quit, or 0 for no */ int answer_is_yes_no_quit ( const char *s ) { /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_yes = _("yes"); /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_no = _("no"); /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_quit = _("quit"); const char *short_yes = _("yY"); const char *short_no = _("nN"); const char *short_quit = _("qQ"); /* Note: we have to use a local dependent compare here. */ if ( match_multistr(long_no,s) ) return 0; if ( match_multistr(long_yes,s) ) return 1; if ( match_multistr(long_quit,s) ) return -1; if ( *s && strchr( short_no, *s ) && !s[1] ) return 0; if ( *s && strchr( short_yes, *s ) && !s[1] ) return 1; if ( *s && strchr( short_quit, *s ) && !s[1] ) return -1; /* but not here. */ if ( !ascii_strcasecmp(s, "yes" ) ) return 1; if ( !ascii_strcasecmp(s, "quit" ) ) return -1; if ( *s && strchr( "yY", *s ) && !s[1] ) return 1; if ( *s && strchr( "qQ", *s ) && !s[1] ) return -1; return 0; } /* Return 1 for okay, 0 for for cancel or DEF_ANSWER for default. */ int answer_is_okay_cancel (const char *s, int def_answer) { /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_okay = _("okay|okay"); /* TRANSLATORS: See doc/TRANSLATE about this string. */ const char *long_cancel = _("cancel|cancel"); const char *short_okay = _("oO"); const char *short_cancel = _("cC"); /* Note: We have to use the locale dependent compare. */ if ( match_multistr(long_okay,s) ) return 1; if ( match_multistr(long_cancel,s) ) return 0; if ( *s && strchr( short_okay, *s ) && !s[1] ) return 1; if ( *s && strchr( short_cancel, *s ) && !s[1] ) return 0; /* Always test for the English values (not locale here). */ if ( !ascii_strcasecmp(s, "okay" ) ) return 1; if ( !ascii_strcasecmp(s, "ok" ) ) return 1; if ( !ascii_strcasecmp(s, "cancel" ) ) return 0; if ( *s && strchr( "oO", *s ) && !s[1] ) return 1; if ( *s && strchr( "cC", *s ) && !s[1] ) return 0; return def_answer; } diff --git a/configure.ac b/configure.ac index f3066a0a9..d77093a63 100644 --- a/configure.ac +++ b/configure.ac @@ -1,1265 +1,1266 @@ # configure.ac - for GnuPG 1.9 # Copyright (C) 1998, 1999, 2000, 2001, 2002, # 2003, 2004, 2005 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. # Process this file with autoconf to produce a configure script. AC_PREREQ(2.52) min_automake_version="1.9.3" # Remember to change the version number immediately *after* a release. # Set my_issvn to "yes" for non-released code. Remember to run an # "svn up" and "autogen.sh" right before creating a distribution. m4_define([my_version], [1.9.21]) m4_define([my_issvn], [yes]) m4_define([svn_revision], m4_esyscmd([echo -n $((svn info 2>/dev/null \ || echo 'Revision: 0')|sed -n '/^Revision:/ {s/[^0-9]//gp;q}')])) AC_INIT([gnupg], my_version[]m4_if(my_issvn,[yes],[-svn[]svn_revision]), [gnupg-devel@gnupg.org]) # Set development_version to yes if the minor number is odd or you # feel that the default check for a development version is not # sufficient. development_version=yes NEED_GPG_ERROR_VERSION=1.0 NEED_LIBGCRYPT_API=1 NEED_LIBGCRYPT_VERSION=1.1.94 NEED_LIBASSUAN_VERSION=0.6.10 NEED_KSBA_VERSION=0.9.13 PACKAGE=$PACKAGE_NAME PACKAGE_GT=${PACKAGE_NAME}2 VERSION=$PACKAGE_VERSION AC_CONFIG_AUX_DIR(scripts) AC_CONFIG_SRCDIR(sm/gpgsm.c) AM_CONFIG_HEADER(config.h) AC_CANONICAL_TARGET() AM_INIT_AUTOMAKE($PACKAGE, $VERSION) AC_GNU_SOURCE # Some status variables to give feedback at the end of a configure run have_gpg_error=no have_libgcrypt=no have_libassuan=no have_ksba=no have_pth=no GNUPG_BUILD_PROGRAM(gpg, no) GNUPG_BUILD_PROGRAM(gpgsm, yes) GNUPG_BUILD_PROGRAM(agent, yes) GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(symcryptrun, no) AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) AC_SUBST(VERSION) AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package]) AC_DEFINE_UNQUOTED(PACKAGE_GT, "$PACKAGE_GT", [Name of this package for gettext]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package]) AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT", [Bug report address]) AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION", [Required version of Libgcrypt]) AC_DEFINE_UNQUOTED(NEED_KSBA_VERSION, "$NEED_KSBA_VERSION", [Required version of Libksba]) # The default is to use the modules from this package and the few # other packages in a standard place; i.e where this package gets # installed. With these options it is possible to override these # ${prefix} depended values with fixed paths, which can't be replaced # at make time. See also am/cmacros.am and the defaults in AH_BOTTOM. AC_ARG_WITH(agent-pgm, [ --with-agent-pgm=PATH Use PATH as the default for the agent)], GNUPG_AGENT_PGM="$withval", GNUPG_AGENT_PGM="" ) AC_SUBST(GNUPG_AGENT_PGM) AM_CONDITIONAL(GNUPG_AGENT_PGM, test -n "$GNUPG_AGENT_PGM") show_gnupg_agent_pgm="(default)" test -n "$GNUPG_AGENT_PGM" && show_gnupg_agent_pgm="$GNUPG_AGENT_PGM" AC_ARG_WITH(pinentry-pgm, [ --with-pinentry-pgm=PATH Use PATH as the default for the pinentry)], GNUPG_PINENTRY_PGM="$withval", GNUPG_PINENTRY_PGM="" ) AC_SUBST(GNUPG_PINENTRY_PGM) AM_CONDITIONAL(GNUPG_PINENTRY_PGM, test -n "$GNUPG_PINENTRY_PGM") show_gnupg_pinentry_pgm="(default)" test -n "$GNUPG_PINENTRY_PGM" && show_gnupg_pinentry_pgm="$GNUPG_PINENTRY_PGM" AC_ARG_WITH(scdaemon-pgm, [ --with-scdaemon-pgm=PATH Use PATH as the default for the scdaemon)], GNUPG_SCDAEMON_PGM="$withval", GNUPG_SCDAEMON_PGM="" ) AC_SUBST(GNUPG_SCDAEMON_PGM) AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM") show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" AC_ARG_WITH(dirmngr-pgm, [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM") show_gnupg_dirmngr_pgm="(default)" test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM" AC_ARG_WITH(protect-tool-pgm, [ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)], GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" ) AC_SUBST(GNUPG_PROTECT_TOOL_PGM) AM_CONDITIONAL(GNUPG_PROTECT_TOOL_PGM, test -n "$GNUPG_PROTECT_TOOL_PGM") show_gnupg_protect_tool_pgm="(default)" test -n "$GNUPG_PROTECT_TOOL_PGM" \ && show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM" # Some folks want to use only the agent form this packet. Make it # easier for them by providing the configure option # --enable-only-agent. AC_ARG_ENABLE(agent-only, AC_HELP_STRING([--enable-agent-only],[build only the gpg-agent]), build_agent_only=$enableval) # Allow disabling of bzib2 support. # It is defined only after we confirm the library is available later use_bzip2=yes AC_MSG_CHECKING([whether to enable the BZIP2 compression algorithm]) AC_ARG_ENABLE(bzip2, AC_HELP_STRING([--disable-bzip2],[disable the BZIP2 compression algorithm]), use_bzip2=$enableval) AC_MSG_RESULT($use_bzip2) # Configure option to allow or disallow execution of external # programs, like a photo viewer. AC_MSG_CHECKING([whether to enable external program execution]) AC_ARG_ENABLE(exec, AC_HELP_STRING([--disable-exec],[disable all external program execution]), use_exec=$enableval) AC_MSG_RESULT($use_exec) if test "$use_exec" = no ; then AC_DEFINE(NO_EXEC,1,[Define to disable all external program execution]) fi if test "$use_exec" = yes ; then AC_MSG_CHECKING([whether to enable photo ID viewing]) AC_ARG_ENABLE(photo-viewers, [ --disable-photo-viewers disable photo ID viewers], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_PHOTO_VIEWER,1,[define to disable photo viewing]) fi],enableval=yes) gnupg_cv_enable_photo_viewers=$enableval AC_MSG_RESULT($enableval) if test "$gnupg_cv_enable_photo_viewers" = yes ; then AC_MSG_CHECKING([whether to use a fixed photo ID viewer]) AC_ARG_WITH(photo-viewer, [ --with-photo-viewer=FIXED_VIEWER set a fixed photo ID viewer], [if test "$withval" = yes ; then withval=no elif test "$withval" != no ; then AC_DEFINE_UNQUOTED(FIXED_PHOTO_VIEWER,"$withval", [if set, restrict photo-viewer to this]) fi],withval=no) AC_MSG_RESULT($withval) fi AC_MSG_CHECKING([whether to enable external keyserver helpers]) AC_ARG_ENABLE(keyserver-helpers, [ --disable-keyserver-helpers disable all external keyserver support], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_KEYSERVER_HELPERS,1, [define to disable keyserver helpers]) fi],enableval=yes) gnupg_cv_enable_keyserver_helpers=$enableval AC_MSG_RESULT($enableval) if test "$gnupg_cv_enable_keyserver_helpers" = yes ; then AC_MSG_CHECKING([whether LDAP keyserver support is requested]) AC_ARG_ENABLE(ldap, [ --disable-ldap disable LDAP keyserver interface], try_ldap=$enableval, try_ldap=yes) AC_MSG_RESULT($try_ldap) AC_MSG_CHECKING([whether HKP keyserver support is requested]) AC_ARG_ENABLE(hkp, [ --disable-hkp disable HKP keyserver interface], try_hkp=$enableval, try_hkp=yes) AC_MSG_RESULT($try_hkp) if test "$try_hkp" = yes ; then AC_SUBST(GPGKEYS_HKP,"gpgkeys_hkp$EXEEXT") fi AC_MSG_CHECKING([whether email keyserver support is requested]) AC_ARG_ENABLE(mailto, [ --disable-mailto disable email keyserver interface], try_mailto=$enableval, try_mailto=yes) AC_MSG_RESULT($try_mailto) fi AC_MSG_CHECKING([whether keyserver exec-path is enabled]) AC_ARG_ENABLE(keyserver-path, [ --disable-keyserver-path disable the exec-path option for keyserver helpers], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_KEYSERVER_PATH,1,[define to disable exec-path for keyserver helpers]) fi],enableval=yes) AC_MSG_RESULT($enableval) fi dnl dnl Check for the key/uid cache size. This can't be zero, but can be dnl pretty small on embedded systems. dnl AC_MSG_CHECKING([for the size of the key and uid cache]) AC_ARG_ENABLE(key-cache, AC_HELP_STRING([--enable-key-cache=SIZE],[Set key cache to SIZE (default 4096)]),,enableval=4096) if test "$enableval" = "no"; then enableval=5 elif test "$enableval" = "yes" || test "$enableval" = ""; then enableval=4096 fi changequote(,)dnl key_cache_size=`echo "$enableval" | sed 's/[A-Za-z]//g'` changequote([,])dnl if test "$enableval" != "$key_cache_size" || test "$key_cache_size" -lt 5; then AC_MSG_ERROR([invalid key-cache size]) fi AC_MSG_RESULT($key_cache_size) AC_DEFINE_UNQUOTED(PK_UID_CACHE_SIZE,$key_cache_size,[Size of the key and UID caches]) dnl dnl Check whether we want to use Linux capabilities dnl AC_MSG_CHECKING([whether use of capabilities is requested]) AC_ARG_WITH(capabilities, [ --with-capabilities use linux capabilities [default=no]], [use_capabilities="$withval"],[use_capabilities=no]) AC_MSG_RESULT($use_capabilities) AH_BOTTOM([ /* Some global constants. */ #ifdef HAVE_DRIVE_LETTERS #define GNUPG_DEFAULT_HOMEDIR "c:/gnupg" #elif defined(__VMS) #define GNUPG_DEFAULT_HOMEDIR "/SYS\$LOGIN/gnupg" #else #define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" /* Tell libgcrypt not to use its own libgpg-error implementation. */ #define USE_LIBGPG_ERROR 1 /* This is the major version number of GnuPG so that source included files can test for this. Note, that\ we use 2 here even for GnuPG 1.9.x. */ #define GNUPG_MAJOR_VERSION 2 /* Now to separate file name parts. Please note that the string version must not contain more than one character because the code assumes strlen()==1 */ #ifdef HAVE_DOSISH_SYSTEM #define DIRSEP_C '\\' #define DIRSEP_S "\\" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ';' #define PATHSEP_S ";" #define EXEEXT_S ".exe" #else #define DIRSEP_C '/' #define DIRSEP_S "/" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ':' #define PATHSEP_S ":" #define EXEEXT_S "" #endif /* This is the same as VERSION, but should be overridden if the platform cannot handle things like dots '.' in filenames. Set SAFE_VERSION_DOT and SAFE_VERSION_DASH to whatever SAFE_VERSION uses for dots and dashes. */ #define SAFE_VERSION VERSION #define SAFE_VERSION_DOT '.' #define SAFE_VERSION_DASH '-' /* For some systems (DOS currently), we hardcode the path here. For POSIX systems the values are constructed by the Makefiles, so that the values may be overridden by the make invocations; this is to comply with the GNU coding standards. */ #ifdef HAVE_DRIVE_LETTERS #define GNUPG_BINDIR "c:\\gnupg" #define GNUPG_LIBEXECDIR "c:\\gnupg" #define GNUPG_LIBDIR "c:\\gnupg" #define GNUPG_DATADIR "c:\\gnupg" #endif /* Setup the hardwired names of modules. */ #ifndef GNUPG_DEFAULT_AGENT #define GNUPG_DEFAULT_AGENT ( GNUPG_BINDIR DIRSEP_S "gpg-agent" EXEEXT_S ) #endif #ifndef GNUPG_DEFAULT_PINENTRY #define GNUPG_DEFAULT_PINENTRY ( GNUPG_BINDIR DIRSEP_S "pinentry" EXEEXT_S ) #endif #ifndef GNUPG_DEFAULT_SCDAEMON #define GNUPG_DEFAULT_SCDAEMON ( GNUPG_BINDIR DIRSEP_S "scdaemon" EXEEXT_S ) #endif #ifndef GNUPG_DEFAULT_DIRMNGR #define GNUPG_DEFAULT_DIRMNGR ( GNUPG_BINDIR DIRSEP_S "dirmngr" EXEEXT_S ) #endif #ifndef GNUPG_DEFAULT_PROTECT_TOOL #define GNUPG_DEFAULT_PROTECT_TOOL \ ( GNUPG_LIBEXECDIR DIRSEP_S "gpg-protect-tool" EXEEXT_S ) #endif /* Derive some other constants. */ #if !(defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)) #define EXEC_TEMPFILE_ONLY #endif /* Temporary hacks to avoid requring a libgpg-error update. */ #if !HAVE_DECL_GPG_ERR_LOCKED #define GPG_ERR_LOCKED 173 #endif ]) AM_MAINTAINER_MODE # Checks for programs. AC_PROG_MAKE_SET AM_SANITY_CHECK missing_dir=`cd $ac_aux_dir && pwd` AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir) AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir) AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir) AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB AC_CHECK_TOOL(AR, ar, :) AC_PATH_PROG(PERL,"perl") AC_ISC_POSIX gl_EARLY AC_SYS_LARGEFILE AC_CHECK_PROG(DOCBOOK_TO_MAN, docbook-to-man, yes, no) AM_CONDITIONAL(HAVE_DOCBOOK_TO_MAN, test "$ac_cv_prog_DOCBOOK_TO_MAN" = yes) GNUPG_CHECK_FAQPROG GNUPG_CHECK_DOCBOOK_TO_TEXI try_gettext=yes have_dosish_system=no have_w32_system=no case "${host}" in *-mingw32*) # special stuff for Windoze NT ac_cv_have_dev_random=no AC_DEFINE(USE_ONLY_8DOT3,1, [set this to limit filenames to the 8.3 format]) AC_DEFINE(HAVE_DRIVE_LETTERS,1, [defined if we must run on a stupid file system]) AC_DEFINE(USE_SIMPLE_GETTEXT,1, [because the Unix gettext has too much overhead on MingW32 systems and these systems lack Posix functions, we use a simplified version of gettext]) have_dosish_system=yes have_w32_system=yes try_gettext="no" ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" ;; i?86-*-msdosdjgpp*) # DOS with the DJGPP environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" ;; *-*-freebsd*) # FreeBSD CPPFLAGS="$CPPFLAGS -I/usr/local/include" LDFLAGS="$LDFLAGS -L/usr/local/lib" ;; *-*-hpux*) if test -z "$GCC" ; then CFLAGS="$CFLAGS -Ae -D_HPUX_SOURCE" fi ;; *-dec-osf4*) if test -z "$GCC" ; then # Suppress all warnings # to get rid of the unsigned/signed char mismatch warnings. CFLAGS="$CFLAGS -w" fi ;; *-dec-osf5*) if test -z "$GCC" ; then # Use the newer compiler `-msg_disable ptrmismatch' to # get rid of the unsigned/signed char mismatch warnings. # Using this may hide other pointer mismatch warnings, but # it at least lets other warning classes through CFLAGS="$CFLAGS -msg_disable ptrmismatch" fi ;; m68k-atari-mint) ;; *) ;; esac if test "$have_dosish_system" = yes; then AC_DEFINE(HAVE_DOSISH_SYSTEM,1, [Defined if we run on some of the PCDOS like systems (DOS, Windoze. OS/2) with special properties like no file modes]) fi AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes) if test "$have_w32_system" = yes; then AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on a W32 API based system]) fi AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes) # These need to go after AC_PROG_CC so that $EXEEXT is defined AC_DEFINE_UNQUOTED(EXEEXT,"$EXEEXT",[The executable file extension, if any]) # # Checks for libraries. # # # libgpg-error is a library with error codes shared between GnuPG # related projects. # AM_PATH_GPG_ERROR("$NEED_GPG_ERROR_VERSION", have_gpg_error=yes,have_gpg_error=no) _tmp_gpg_error_save_cflags="$CFLAGS" CFLAGS="$CFLAGS $GPG_ERROR_CFLAGS" AC_CHECK_DECLS(GPG_ERR_LOCKED,,,[#include ]) CFLAGS="${_tmp_gpg_error_save_cflags}" # # Libgcrypt is our generic crypto library # AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION", have_libgcrypt=yes,have_libgcrypt=no) # # libassuan is used for IPC # AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_VERSION", have_libassuan=yes,have_libassuan=no) # # libksba is our X.509 support library # AM_PATH_KSBA("$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no) # # libusb allows us to use the integrated CCID smartcard reader driver. # AC_CHECK_LIB(usb, usb_bulk_write, [ LIBUSB_LIBS="$LIBUSB_LIBS -lusb" AC_DEFINE(HAVE_LIBUSB,1, [defined if libusb is available]) ]) AC_SUBST(LIBUSB_LIBS) AC_CHECK_FUNCS(usb_create_match) # # Check wether it is necessary to link against libdl. # LIBS="" AC_SEARCH_LIBS(dlopen, c dl,,,) DL_LIBS=$LIBS AC_SUBST(DL_LIBS) # # Checks for symcryptrun: # # libutil has openpty() and login_tty(). AC_CHECK_LIB(util, openpty, [ LIBUTIL_LIBS="$LIBUTIL_LIBS -lutil" AC_DEFINE(HAVE_LIBUTIL,1, [defined if libutil is available]) ]) AC_SUBST(LIBUTIL_LIBS) # shred is used to clean temporary plain text files. AC_PATH_PROG(SHRED, shred, /usr/bin/shred) AC_DEFINE_UNQUOTED(SHRED, "${SHRED}", [defines the filename of the shred program]) # # Check whether the (highly desirable) GNU Pth library is available # Note, that we include a Pth emulation for W32. # AC_ARG_WITH(pth-prefix, AC_HELP_STRING([--with-pth-prefix=PFX], [prefix where GNU Pth is installed (optional)]), pth_config_prefix="$withval", pth_config_prefix="") if test x$pth_config_prefix != x ; then PTH_CONFIG="$pth_config_prefix/bin/pth-config" fi AC_PATH_PROG(PTH_CONFIG, pth-config, no) if test "$have_w32_system" = no; then if test "$PTH_CONFIG" = "no"; then AC_MSG_WARN([[ *** *** To support concurrent access to the gpg-agent and the SCdaemon *** we need the support of the GNU Portable Threads Library. *** Download it from ftp://ftp.gnu.org/gnu/pth/ *** On a Debian GNU/Linux system you might want to try *** apt-get install libpth-dev ***]]) else GNUPG_PTH_VERSION_CHECK(1.3.7) if test $have_pth = yes; then PTH_CFLAGS=`$PTH_CONFIG --cflags` PTH_LIBS=`$PTH_CONFIG --ldflags` PTH_LIBS="$PTH_LIBS `$PTH_CONFIG --libs --all`" AC_DEFINE(USE_GNU_PTH, 1, [Defined if the GNU Portable Thread Library should be used]) AC_DEFINE(HAVE_PTH, 1, [Defined if the GNU Pth is available]) fi fi else have_pth=yes PTH_CFLAGS="" PTH_LIBS="" AC_DEFINE(USE_GNU_PTH, 1) AC_DEFINE(HAVE_PTH, 1) fi AC_SUBST(PTH_CFLAGS) AC_SUBST(PTH_LIBS) dnl Must check for network library requirements before doing link tests dnl for ldap, for example. If ldap libs are static (or dynamic and without dnl ELF runtime link paths), then link will fail and LDAP support won't dnl be detected. AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname, [NETLIBS="-lnsl $NETLIBS"])) AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt, [NETLIBS="-lsocket $NETLIBS"])) dnl Now try for the resolver functions so we can use DNS SRV AC_ARG_ENABLE(dns-srv, AC_HELP_STRING([--disable-dns-srv],[disable the use of DNS SRV in HKP]), use_dns_srv=$enableval,use_dns_srv=yes) if test x"$try_hkp" = xyes && test x"$use_dns_srv" = xyes ; then _srv_save_libs=$LIBS LIBS="" # the double underscore thing is a glibc-ism? AC_SEARCH_LIBS(res_query,resolv bind,, AC_SEARCH_LIBS(__res_query,resolv bind,,use_dns_srv=no)) AC_SEARCH_LIBS(dn_expand,resolv bind,, AC_SEARCH_LIBS(__dn_expand,resolv bind,,use_dns_srv=no)) AC_SEARCH_LIBS(dn_skipname,resolv bind,, AC_SEARCH_LIBS(__dn_skipname,resolv bind,,use_dns_srv=no)) if test x"$use_dns_srv" = xyes ; then AC_DEFINE(USE_DNS_SRV,1,[define to use DNS SRV]) SRVLIBS=$LIBS else AC_MSG_WARN([Resolver functions not found. Disabling DNS SRV.]) fi LIBS=$_srv_save_libs fi AC_SUBST(SRVLIBS) # Try and link a LDAP test program to weed out unusable LDAP # libraries. -lldap [-llber [-lresolv]] is for OpenLDAP. OpenLDAP in # general is terrible with creating weird dependencies. If all else # fails, the user can play guess-the-dependency by using something # like ./configure LDAPLIBS="-Lfoo -lbar" if test "$try_ldap" = yes ; then for MY_LDAPLIBS in ${LDAPLIBS+"$LDAPLIBS"} "-lldap" "-lldap -llber" "-lldap -llber -lresolv"; do _ldap_save_libs=$LIBS LIBS="$MY_LDAPLIBS $NETLIBS $LIBS" AC_MSG_CHECKING([whether LDAP via \"$MY_LDAPLIBS\" is present and sane]) AC_TRY_LINK([#include ],[ldap_open("foobar",1234);], [gnupg_cv_func_ldap_init=yes],[gnupg_cv_func_ldap_init=no]) AC_MSG_RESULT([$gnupg_cv_func_ldap_init]) if test $gnupg_cv_func_ldap_init = no; then AC_MSG_CHECKING([whether I can make LDAP be sane with lber.h]) AC_TRY_LINK([#include #include ],[ldap_open("foobar",1234);], [gnupg_cv_func_ldaplber_init=yes],[gnupg_cv_func_ldaplber_init=no]) AC_MSG_RESULT([$gnupg_cv_func_ldaplber_init]) fi if test "$gnupg_cv_func_ldaplber_init" = yes ; then AC_DEFINE(NEED_LBER_H,1,[Define if the LDAP library requires including lber.h before ldap.h]) fi if test "$gnupg_cv_func_ldap_init" = yes || \ test "$gnupg_cv_func_ldaplber_init" = yes ; then LDAPLIBS=$MY_LDAPLIBS GPGKEYS_LDAP="gpgkeys_ldap$EXEEXT" AC_MSG_CHECKING([whether LDAP supports ldap_get_option]) if test "$gnupg_cv_func_ldap_init" = yes ; then AC_TRY_LINK([#include ], [ldap_get_option((void *)0,0,(void *)0);], [gnupg_cv_func_ldap_get_option=yes], [gnupg_cv_func_ldap_get_option=no]) else AC_TRY_LINK([#include #include ],[ldap_get_option((void *)0,0,(void *)0);], [gnupg_cv_func_ldap_get_option=yes], [gnupg_cv_func_ldap_get_option=no]) fi AC_MSG_RESULT([$gnupg_cv_func_ldap_get_option]) if test "$gnupg_cv_func_ldap_get_option" = yes ; then AC_DEFINE(HAVE_LDAP_GET_OPTION,1,[Define if the LDAP library has ldap_get_option]) else AC_MSG_CHECKING([whether LDAP supports ld_errno]) if test "$gnupg_cv_func_ldap_init" = yes ; then AC_TRY_COMPILE([#include ], [LDAP *ldap; ldap->ld_errno;], [gnupg_cv_func_ldap_ld_errno=yes], [gnupg_cv_func_ldap_ld_errno=no]) else AC_TRY_LINK([#include #include ],[LDAP *ldap; ldap->ld_errno;], [gnupg_cv_func_ldap_ld_errno=yes], [gnupg_cv_func_ldap_ld_errno=no]) fi AC_MSG_RESULT([$gnupg_cv_func_ldap_ld_errno]) if test "$gnupg_cv_func_ldap_ld_errno" = yes ; then AC_DEFINE(HAVE_LDAP_LD_ERRNO,1,[Define if the LDAP library supports ld_errno]) fi fi fi LIBS=$_ldap_save_libs if test "$GPGKEYS_LDAP" != "" ; then break; fi done fi AC_SUBST(GPGKEYS_LDAP) AC_SUBST(LDAPLIBS) dnl This isn't necessarily sendmail itself, but anything that gives a dnl sendmail-ish interface to the outside world. That includes qmail, dnl postfix, etc. Basically, anything that can handle "sendmail -t". if test "$try_mailto" = yes ; then AC_ARG_WITH(mailprog,[ --with-mailprog=NAME use "NAME -t" for mail transport],,with_mailprog=yes) if test "$with_mailprog" = yes ; then AC_PATH_PROG(SENDMAIL,sendmail,,$PATH:/usr/sbin:/usr/libexec:/usr/lib) if test "$ac_cv_path_SENDMAIL" ; then GPGKEYS_MAILTO="gpgkeys_mailto" fi elif test "$with_mailprog" != no ; then AC_MSG_CHECKING([for a mail transport program]) AC_SUBST(SENDMAIL,$with_mailprog) AC_MSG_RESULT($with_mailprog) GPGKEYS_MAILTO="gpgkeys_mailto" fi fi AC_SUBST(GPGKEYS_MAILTO) case "${host}" in *-mingw32*) PRINTABLE_OS_NAME="MingW32" ;; *-*-cygwin*) PRINTABLE_OS_NAME="Cygwin" ;; i?86-emx-os2 | i?86-*-os2*emx ) PRINTABLE_OS_NAME="OS/2" ;; i?86-*-msdosdjgpp*) PRINTABLE_OS_NAME="MSDOS/DJGPP" try_dynload=no ;; *-linux*) PRINTABLE_OS_NAME="GNU/Linux" ;; *) PRINTABLE_OS_NAME=`uname -s || echo "Unknown"` ;; esac AC_DEFINE_UNQUOTED(PRINTABLE_OS_NAME, "$PRINTABLE_OS_NAME", [A human readable text with the name of the OS]) AM_GNU_GETTEXT_VERSION(0.14.1) if test "$try_gettext" = yes; then AM_GNU_GETTEXT(,[need-ngettext]) # gettext requires some extra checks. These really should be part of # the basic AM_GNU_GETTEXT macro. TODO: move other gettext-specific # function checks to here. AC_CHECK_FUNCS(strchr) else USE_NLS=no USE_INCLUDED_LIBINTL=no BUILD_INCLUDED_LIBINTL=no AC_SUBST(USE_NLS) AC_SUBST(USE_INCLUDED_LIBINTL) AC_SUBST(BUILD_INCLUDED_LIBINTL) fi # Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS(string.h unistd.h langinfo.h termio.h locale.h) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_INLINE AC_TYPE_SIZE_T AC_TYPE_SIGNAL AC_DECL_SYS_SIGLIST GNUPG_CHECK_ENDIAN GNUPG_CHECK_TYPEDEF(byte, HAVE_BYTE_TYPEDEF) GNUPG_CHECK_TYPEDEF(ushort, HAVE_USHORT_TYPEDEF) GNUPG_CHECK_TYPEDEF(ulong, HAVE_ULONG_TYPEDEF) GNUPG_CHECK_TYPEDEF(u16, HAVE_U16_TYPEDEF) GNUPG_CHECK_TYPEDEF(u32, HAVE_U32_TYPEDEF) AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) # Ensure that we have UINT64_C before we bother to check for uint64_t # fixme: really needed in gnupg? I think it is only useful in libcgrypt. AC_CACHE_CHECK([for UINT64_C],[gnupg_cv_uint64_c_works], AC_COMPILE_IFELSE(AC_LANG_PROGRAM([#include uint64_t foo=UINT64_C(42);]),gnupg_cv_uint64_c_works=yes,gnupg_cv_uint64_c_works=no)) if test "$gnupg_cv_uint64_c_works" = "yes" ; then AC_CHECK_SIZEOF(uint64_t) fi if test "$ac_cv_sizeof_unsigned_short" = "0" \ || test "$ac_cv_sizeof_unsigned_int" = "0" \ || test "$ac_cv_sizeof_unsigned_long" = "0"; then AC_MSG_WARN([Hmmm, something is wrong with the sizes - using defaults]); fi dnl Do we have any 64-bit data types? if test "$ac_cv_sizeof_unsigned_int" != "8" \ && test "$ac_cv_sizeof_unsigned_long" != "8" \ && test "$ac_cv_sizeof_unsigned_long_long" != "8" \ && test "$ac_cv_sizeof_uint64_t" != "8"; then AC_MSG_WARN([No 64-bit types. Disabling SHA-384, and SHA-512]) else if test x"$use_sha512" = xyes ; then AC_SUBST(SHA512_O,sha512.o) AC_DEFINE(USE_SHA512,1,[Define to include the SHA-384 and SHA-512 digests]) fi fi # fixme: do we really need this - it should be encapsulated in libassuan GNUPG_SYS_SO_PEERCRED # Checks for library functions. AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK AC_CHECK_FUNCS(strerror stpcpy strsep strlwr tcgetattr strtoul mmap) AC_CHECK_FUNCS(strcasecmp strncasecmp ctermid times gmtime_r) AC_CHECK_FUNCS(memmove gettimeofday getrusage setrlimit clock_gettime) AC_CHECK_FUNCS(atexit raise getpagesize strftime nl_langinfo setlocale) AC_CHECK_FUNCS(waitpid wait4 sigaction sigprocmask rand pipe stat getaddrinfo) AC_CHECK_FUNCS(fseeko ftello ttyname isascii) AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include ]) # gnulib checks gl_SOURCE_BASE(gl) gl_M4_BASE(gl/m4) gl_MODULES(setenv strsep mkdtemp vasprintf xsize) gl_INIT # These are needed by libjnlib - fixme: we should have macros for them AC_CHECK_FUNCS(memicmp stpcpy strlwr strtoul memmove stricmp strtol) AC_CHECK_FUNCS(getrusage setrlimit stat setlocale) AC_CHECK_FUNCS(flockfile funlockfile fopencookie funopen) # # check for gethrtime and run a testprogram to see whether # it is broken. It has been reported that some Solaris and HP UX systems # raise an SIGILL # # fixme: Do we need this - iirc, this is only used by libgcrypt. # AC_CACHE_CHECK([for gethrtime], [gnupg_cv_func_gethrtime], [AC_TRY_LINK([#include ],[ hrtime_t tv; tv = gethrtime(); ], [gnupg_cv_func_gethrtime=yes], [gnupg_cv_func_gethrtime=no]) ]) if test $gnupg_cv_func_gethrtime = yes; then AC_DEFINE([HAVE_GETHRTIME], 1, [Define if you have the `gethrtime(2)' function.]) AC_CACHE_CHECK([whether gethrtime is broken], [gnupg_cv_func_broken_gethrtime], [AC_TRY_RUN([ #include int main () { hrtime_t tv; tv = gethrtime(); } ], [gnupg_cv_func_broken_gethrtime=no], [gnupg_cv_func_broken_gethrtime=yes], [gnupg_cv_func_broken_gethrtime=assume-no]) ]) if test $gnupg_cv_func_broken_gethrtime = yes; then AC_DEFINE([HAVE_BROKEN_GETHRTIME], 1, [Define if `gethrtime(2)' does not work correctly i.e. issues a SIGILL.]) fi fi GNUPG_CHECK_MLOCK GNUPG_FUNC_MKDIR_TAKES_ONE_ARG dnl dnl Check whether we can use Linux capabilities as requested dnl # fixme: Still required? # if test "$use_capabilities" = "yes" ; then use_capabilities=no AC_CHECK_HEADERS(sys/capability.h) if test "$ac_cv_header_sys_capability_h" = "yes" ; then AC_CHECK_LIB(cap, cap_init, ac_need_libcap=1) if test "$ac_cv_lib_cap_cap_init" = "yes"; then AC_DEFINE(USE_CAPABILITIES,1, [define if capabilities should be used]) AC_SUBST(CAPLIBS,"-lcap") use_capabilities=yes fi fi if test "$use_capabilities" = "no" ; then AC_MSG_WARN([[ *** *** The use of capabilities on this system is not possible. *** You need a recent Linux kernel and some patches: *** fcaps-2.2.9-990610.patch (kernel patch for 2.2.9) *** fcap-module-990613.tar.gz (kernel module) *** libcap-1.92.tar.gz (user mode library and utilities) *** And you have to configure the kernel with CONFIG_VFS_CAP_PLUGIN *** set (filesystems menu). Be warned: This code is *really* ALPHA. ***]]) fi fi # Sanity check regex. Tests adapted from mutt. AC_MSG_CHECKING([whether regular expression support is requested]) AC_ARG_ENABLE(regex, [ --disable-regex do not handle regular expressions in trust sigs], use_regex=$enableval, use_regex=yes) AC_MSG_RESULT($use_regex) if test "$use_regex" = yes ; then AC_MSG_CHECKING([whether the included regex lib is requested]) AC_ARG_WITH(included-regex, [ --with-included-regex use the included GNU regex library], [gnupg_cv_included_regex="$withval"],[gnupg_cv_included_regex=no]) AC_MSG_RESULT($gnupg_cv_included_regex) if test $gnupg_cv_included_regex = no ; then # Does the system have regex functions at all? AC_CHECK_FUNC(regcomp,gnupg_cv_included_regex=no, gnupg_cv_included_regex=yes) fi if test $gnupg_cv_included_regex = no ; then AC_CACHE_CHECK([whether your system's regexp library is broken], [gnupg_cv_regex_broken], AC_TRY_RUN([ #include #include main() { regex_t blah ; regmatch_t p; p.rm_eo = p.rm_eo; return regcomp(&blah, "foo.*bar", REG_NOSUB) || regexec (&blah, "foobar", 0, NULL, 0); }], gnupg_cv_regex_broken=no, gnupg_cv_regex_broken=yes, gnupg_cv_regex_broken=yes)) if test $gnupg_cv_regex_broken = yes ; then AC_MSG_WARN(your regex is broken - using the included GNU regex instead.) gnupg_cv_included_regex=yes fi fi if test $gnupg_cv_included_regex = yes; then AC_DEFINE(USE_GNU_REGEX,1,[ Define if you want to use the included regex lib ]) AC_SUBST(REGEX_O,regex.o) fi else AC_DEFINE(DISABLE_REGEX,1,[ Define to disable regular expression support ]) fi # # Do we have zlib? Must do it here because Solaris failed # when compiling a conftest (due to the "-lz" from LIBS). # Note that we combine zlib and bzlib2 in ZLIBS. # _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(zlib, [ --with-zlib=DIR use libz in DIR],[ if test -d "$withval"; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ]) AC_CHECK_HEADER(zlib.h, AC_CHECK_LIB(z, deflateInit2_, ZLIBS="-lz", CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}) # # Check whether we can support bzip2 # if test "$use_bzip2" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(bzip2, AC_HELP_STRING([--with-bzip2=DIR],[look for bzip2 in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Checking alongside stdio.h as an early version of bzip2 (1.0) # required stdio.h to be included before bzlib.h, and Solaris 9 is # woefully out of date. if test "$withval" != no ; then AC_CHECK_HEADER(bzlib.h, AC_CHECK_LIB(bz2,BZ2_bzCompressInit, [ have_bz2=yes ZLIBS="$ZLIBS -lbz2" AC_DEFINE(HAVE_BZIP2,1, [Defined if the bz2 compression library is available]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags},[#include ]) fi fi AM_CONDITIONAL(ENABLE_BZIP2_SUPPORT,test x"$have_bz2" = "xyes") AC_SUBST(ZLIBS) # See wether we want to run the long test suite. AC_ARG_WITH(pkits-tests, AC_HELP_STRING([--with-pkits-tests],[run the PKITS based tests]), [run_pkits_tests=$withval], [run_pkits_tests=no]) AM_CONDITIONAL(RUN_PKITS_TESTS, test "$run_pkits_tests" = "yes") # Allow users to append something to the version string without # flagging it as development version. The user version parts is # considered everything after a dash. if test "$development_version" != yes; then changequote(,)dnl tmp_pat='[a-zA-Z]' changequote([,])dnl if echo "$VERSION" | sed 's/-.*//' | grep "$tmp_pat" >/dev/null ; then development_version=yes fi fi if test "$development_version" = yes; then AC_DEFINE(IS_DEVELOPMENT_VERSION,1, [Defined if this is not a regular release]) fi AM_CONDITIONAL(CROSS_COMPILING, test x$cross_compiling = xyes) GNUPG_CHECK_GNUMAKE # Add some extra libs here so that previous tests don't fail for # mysterious reasons - the final link step should bail out. if test "$have_w32_system" = yes; then W32LIBS="-lwsock32" fi if test "$GCC" = yes; then if test "$USE_MAINTAINER_MODE" = "yes"; then CFLAGS="$CFLAGS -Wall -Wcast-align -Wshadow -Wstrict-prototypes" CFLAGS="$CFLAGS -Wno-format-y2k -Wformat-security" else CFLAGS="$CFLAGS -Wall" fi fi # # This is handy for debugging so the compiler doesn't rearrange # things and eliminate variables. # AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable compiler optimization]), [if test $enableval = no ; then CFLAGS=`echo $CFLAGS | sed 's/-O[[0-9]]//'` fi]) AC_SUBST(NETLIBS) AC_SUBST(W32LIBS) # We use jnlib, so tell other modules about it AC_DEFINE(HAVE_JNLIB_LOGGING, 1, [Defined if jnlib style logging functions are available]) # For W32 we need to use our Pth emulation code if test "$have_w32_system" = yes; then AC_CONFIG_LINKS(pth.h:jnlib/w32-pth.h) fi # # Decide what to build # missing_pth=no if test $have_ksba = no; then build_gpgsm=no build_scdaemon=no fi build_agent_threaded="" if test "$build_agent" = "yes"; then if test $have_pth = no; then build_agent_threaded="(not multi-threaded)" missing_pth=yes fi fi build_scdaemon_extra="" if test "$build_scdaemon" = "yes"; then tmp="" if test $have_pth = no; then build_scdaemon_extra="not multi-threaded" tmp=", " missing_pth=yes fi if test -n "$build_scdaemon_extra"; then build_scdaemon_extra="(${build_scdaemon_extra})" fi fi if test "$build_agent_only" = "yes" ; then build_gpg=no build_gpgsm=no build_scdaemon=no fi AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") AM_CONDITIONAL(BUILD_SYMCRYPTRUN, test "$build_symcryptrun" = "yes") # # Print errors here so that they are visible all # together and the user can acquire them all together. # die=no if test "$have_gpg_error" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgpg-error to build this program. ** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libgpg-error *** (at least version $NEED_GPG_ERROR_VERSION is required.) ***]]) fi if test "$have_libgcrypt" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgcrypt to build this program. ** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libgcrypt/ *** (at least version $NEED_LIBGCRYPT_VERSION using API $NEED_LIBGCRYPT_API) is required.) ***]]) fi if test "$have_libassuan" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libassuan to build this program. *** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/alpha/libassuan/ *** (at least version $NEED_LIBASSUAN_VERSION is required). ***]]) fi if test "$have_ksba" = "no"; then AC_MSG_NOTICE([[ *** *** You need libksba to build this program. *** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/alpha/libksba/ *** (at least version $NEED_KSBA_VERSION is required). ***]]) fi if test "$missing_pth" = "yes"; then AC_MSG_NOTICE([[ *** *** It is now required to build with support for the *** GNU Portable Threads Library (Pth). Please install this *** library first. The library is for example available at *** ftp://ftp.gnu.org/gnu/pth/ *** On a Debian GNU/Linux system you can install it using *** apt-get install libpth-dev ***]]) die=yes fi if test "$die" = "yes"; then AC_MSG_ERROR([[ *** *** Required libraries not found. Please consult the above messages *** and install them before running configure again. ***]]) fi AC_CONFIG_FILES([ m4/Makefile Makefile po/Makefile.in intl/Makefile gl/Makefile jnlib/Makefile common/Makefile kbx/Makefile g10/Makefile sm/Makefile agent/Makefile scd/Makefile tools/Makefile doc/Makefile tests/Makefile ]) AC_OUTPUT #tests/pkits/Makefile echo " GnuPG v${VERSION} has been configured as follows: Platform: $PRINTABLE_OS_NAME ($host) OpenPGP: $build_gpg S/MIME: $build_gpgsm Agent: $build_agent $build_agent_threaded Smartcard: $build_scdaemon $build_scdaemon_extra Protect tool: $show_gnupg_protect_tool_pgm Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm PKITS based tests: $run_pkits_tests " diff --git a/doc/Makefile.am b/doc/Makefile.am index 47dd36208..dae053ec2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,51 +1,52 @@ # Copyright (C) 2002, 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in EXTRA_DIST = gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \ gnupg-card-architecture.eps gnupg-card-architecture.png BUILT_SOURCES = gnupg-card-architecture.eps gnupg-card-architecture.png info_TEXINFOS = gnupg.texi dist_pkgdata_DATA = qualified.txt gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi assuan.texi \ tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ sysnotes.texi gnupg-card-architecture.fig DISTCLEANFILES = gnupg.tmp gnupg.ops .fig.png: fig2dev -L png `test -f '$<' || echo '$(srcdir)/'`$< $@ .fig.jpg: fig2dev -L jpg `test -f '$<' || echo '$(srcdir)/'`$< $@ .fig.eps: fig2dev -L eps `test -f '$<' || echo '$(srcdir)/'`$< $@ .fig.pdf: fig2dev -L pdf `test -f '$<' || echo '$(srcdir)/'`$< $@ diff --git a/doc/gnupg-card-architecture.fig b/doc/gnupg-card-architecture.fig index e5772cd0f..49351c720 100644 --- a/doc/gnupg-card-architecture.fig +++ b/doc/gnupg-card-architecture.fig @@ -1,419 +1,420 @@ #FIG 3.2 Produced by xfig version 3.2.5-alpha5 # Copyright 2005 Werner Koch # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. Landscape Center Metric A4 100.00 Single -2 1200 2 0 32 #414541 0 33 #808080 0 34 #c0c0c0 0 35 #c6b797 0 36 #eff8ff 0 37 #dccba6 0 38 #e0e0e0 0 39 #8e8f8e 0 40 #aaaaaa 0 41 #555555 0 42 #404040 0 43 #868286 0 44 #c7c3c7 0 45 #e7e3e7 0 46 #8e8e8e 0 47 #444444 0 48 #868686 0 49 #c7c7c7 0 50 #666666 0 51 #e2e2ee 0 52 #94949a 0 53 #dbdbdb 0 54 #a1a1b7 0 55 #9c0000 0 56 #ededed 0 57 #86acff 0 58 #7070ff 0 59 #bebebe 0 60 #515151 0 61 #000049 0 62 #797979 0 63 #303430 0 64 #c7b696 0 65 #d7d7d7 0 66 #aeaeae 0 67 #85807d 0 68 #d2d2d2 0 69 #3a3a3a 0 70 #4573aa 0 71 #000000 0 72 #e7e7e7 0 73 #f7f7f7 0 74 #d6d7d6 0 75 #7b79a5 0 76 #effbff 0 77 #9e9e9e 0 78 #717571 0 79 #73758c 0 80 #414141 0 81 #635dce 0 82 #565151 0 83 #dd9d93 0 84 #f1ece0 0 85 #c3c3c3 0 86 #e2c8a8 0 87 #e1e1e1 0 88 #da7a1a 0 89 #f1e41a 0 90 #887dc2 0 91 #d6d6d6 0 92 #8c8ca5 0 93 #4a4a4a 0 94 #8c6b6b 0 95 #5a5a5a 0 96 #636363 0 97 #b79b73 0 98 #4193ff 0 99 #bf703b 0 100 #db7700 0 101 #dab800 0 102 #006400 0 103 #5a6b3b 0 104 #d3d3d3 0 105 #8e8ea4 0 106 #f3b95d 0 107 #89996b 0 108 #646464 0 109 #b7e6ff 0 110 #86c0ec 0 111 #bdbdbd 0 112 #d39552 0 113 #98d2fe 0 114 #8c9c6b 0 115 #f76b00 0 116 #5a6b39 0 117 #8c9c6b 0 118 #8c9c7b 0 119 #184a18 0 120 #adadad 0 121 #f7bd5a 0 122 #636b9c 0 123 #de0000 0 124 #adadad 0 125 #f7bd5a 0 126 #adadad 0 127 #f7bd5a 0 128 #636b9c 0 129 #526b29 0 130 #949494 0 131 #006300 0 132 #00634a 0 133 #7b844a 0 134 #e7bd7b 0 135 #a5b5c6 0 136 #6b6b94 0 137 #846b6b 0 138 #529c4a 0 139 #d6e7e7 0 140 #526363 0 141 #186b4a 0 142 #9ca5b5 0 143 #ff9400 0 144 #ff9400 0 145 #00634a 0 146 #7b844a 0 147 #63737b 0 148 #e7bd7b 0 149 #184a18 0 150 #f7bd5a 0 151 #dedede 0 152 #f3eed3 0 153 #f5ae5d 0 154 #95ce99 0 155 #b5157d 0 156 #eeeeee 0 157 #848484 0 158 #7b7b7b 0 159 #005a00 0 160 #e77373 0 161 #ffcb31 0 162 #29794a 0 163 #de2821 0 164 #2159c6 0 165 #f8f8f8 0 166 #e6e6e6 0 167 #21845a 0 168 #ff9408 0 169 #007000 0 170 #d00000 0 171 #fed600 0 172 #d82010 0 173 #003484 0 174 #d62010 0 175 #389000 0 176 #ba0000 0 177 #003380 0 178 #00a7bd 0 179 #ffc500 0 180 #087bd0 0 181 #fbc100 0 182 #840029 0 183 #07399c 0 184 #0063bd 0 185 #39acdf 0 186 #42c0e0 0 187 #31ceff 0 188 #ffde00 0 189 #085a00 0 190 #ff2100 0 191 #f75e08 0 192 #ef7b08 0 193 #ff8200 0 194 #007d00 0 195 #0000be 0 196 #757575 0 197 #f3f3f3 0 198 #d7d3d7 0 199 #aeaaae 0 200 #c2c2c2 0 201 #303030 0 202 #515551 0 203 #f7f3f7 0 204 #717171 6 9270 1980 13230 6570 6 9471 3906 13014 5677 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 10540 4394 10540 3936 9471 3936 9471 4394 10540 4394 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 10387 5616 10387 5158 9471 5158 9471 5616 10387 5616 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 12984 5005 12984 4547 9471 4547 9471 5005 12984 5005 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 12984 5616 12984 5158 12067 5158 12067 5616 12984 5616 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 11701 5627 11701 5168 10784 5168 10784 5627 11701 5627 4 0 0 50 -1 16 11 0.0000 4 173 835 9623 4242 OpenPGP\001 4 0 0 50 -1 16 11 0.0000 4 132 2770 9776 4853 APDU and ISO-7816 access code\001 4 0 0 50 -1 16 11 0.0000 4 132 448 9623 5464 CCID\001 4 0 0 50 -1 16 11 0.0000 4 132 601 12220 5464 CT-API\001 4 0 0 50 -1 16 11 0.0000 4 132 560 10957 5464 PC/SC\001 -6 6 10693 3906 13014 4394 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 11762 4394 11762 3936 10693 3936 10693 4394 11762 4394 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 12984 4394 12984 3936 11915 3936 11915 4394 12984 4394 4 0 0 50 -1 16 11 0.0000 4 132 377 10998 4242 NKS\001 4 0 0 50 -1 16 11 0.0000 4 132 804 12067 4242 PKCS#15\001 -6 2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 13137 2072 9318 2072 9318 5739 13137 5739 13137 2072 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 9318 3753 13137 3753 2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 11691 6360 10774 6360 10774 5901 11691 5901 11691 6360 2 1 2 2 0 7 50 -1 -1 4.500 0 0 -1 0 0 1 11762 5739 2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 4 10693 5739 10693 6502 11762 6502 11762 5739 4 0 0 50 -1 18 15 0.0000 4 183 1293 10540 2989 SCDaemon\001 4 0 0 50 -1 16 11 0.0000 4 133 662 10896 6176 wrapper\001 -6 6 90 1980 4050 5760 6 306 3906 3849 5677 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 1375 4394 1375 3936 306 3936 306 4394 1375 4394 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 1222 5616 1222 5158 306 5158 306 5616 1222 5616 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 3819 5005 3819 4547 306 4547 306 5005 3819 5005 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 3819 5616 3819 5158 2902 5158 2902 5616 3819 5616 2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 2536 5627 2536 5168 1619 5168 1619 5627 2536 5627 4 0 0 50 -1 16 11 0.0000 4 173 835 458 4242 OpenPGP\001 4 0 0 50 -1 16 11 0.0000 4 132 2770 611 4853 APDU and ISO-7816 access code\001 4 0 0 50 -1 16 11 0.0000 4 132 448 458 5464 CCID\001 4 0 0 50 -1 16 11 0.0000 4 132 601 3055 5464 CT-API\001 4 0 0 50 -1 16 11 0.0000 4 132 560 1792 5464 PC/SC\001 -6 6 2139 3753 3208 4211 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 3208 4211 3208 3753 2139 3753 2139 4211 3208 4211 4 0 0 50 -1 16 11 0.0000 4 132 784 2291 4058 Gluecode\001 -6 2 1 2 2 0 7 50 -1 -1 4.500 0 0 -1 0 0 1 2597 5739 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 1 1 1.00 40.73 81.47 2139 4028 1405 4150 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 4 153 3753 1833 3753 1833 4364 3972 4364 2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 3972 2072 153 2072 153 5739 3972 5739 3972 2072 4 0 0 50 -1 18 15 0.0000 4 224 866 1375 2989 gpg 1.4\001 -6 6 4888 4058 5346 5433 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 5346 5433 5346 4058 4888 4058 4888 5433 5346 5433 4 0 0 50 -1 16 11 1.5708 4 132 611 5194 5128 Assuan\001 -6 6 4680 1980 8640 5760 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 5346 3753 5346 2378 4888 2378 4888 3753 5346 3753 2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 8554 5739 4735 5739 4735 2072 8554 2072 8554 5739 4 0 0 50 -1 16 11 1.5708 4 173 804 5194 3447 ssh-agent\001 -6 6 5805 3447 7332 4975 6 5957 3447 7179 4211 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 7179 4211 7179 3447 5957 3447 5957 4211 7179 4211 4 0 0 50 -1 16 11 0.0000 4 173 937 6110 3753 Private Key\001 4 0 0 50 -1 16 11 0.0000 4 173 896 6110 4058 Operations\001 -6 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 1 7195 4883 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 1 7195 4883 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 7332 4975 7332 4517 6721 4517 6721 4975 7332 4975 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 1 2 1 1 1.00 40.73 81.47 1 1 1.00 40.73 81.47 6568 4211 7027 4517 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 1 2 1 1 1.00 40.73 81.47 1 1 1.00 40.73 81.47 6568 4211 6110 4517 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 6416 4975 6416 4517 5805 4517 5805 4975 6416 4975 4 0 0 50 -1 16 11 0.0000 4 132 397 6874 4822 Card\001 4 0 0 50 -1 16 11 0.0000 4 132 356 5957 4822 Disk\001 -6 6 7638 3600 8401 4058 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 8401 4058 8401 3600 7638 3600 7638 4058 8401 4058 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 1 7638 3814 4 0 0 50 -1 16 11 0.0000 4 132 530 7790 3905 Cache\001 -6 6 9471 2225 9929 3600 2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 9929 3600 9929 2225 9471 2225 9471 3600 9929 3600 4 0 0 50 -1 16 11 1.5708 4 132 611 9776 3294 Assuan\001 -6 6 6480 360 8640 1440 2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 8554 1339 6568 1339 6568 423 8554 423 8554 1339 4 0 0 50 -1 18 15 0.0000 4 234 967 7027 881 pinentry\001 4 0 0 50 -1 16 10 0.0000 4 153 1375 6874 1187 (GTK+, Qt, Curses)\001 -6 6 10570 270 13137 1003 2 1 1 1 1 2 50 -1 -1 4.000 0 0 -1 1 0 2 1 1 1.00 40.73 81.47 10632 331 11181 331 2 1 0 2 1 2 50 -1 -1 6.000 0 0 -1 1 0 2 1 1 2.00 81.47 162.94 10632 637 11181 637 2 1 0 1 0 2 50 -1 -1 4.000 0 0 -1 1 0 2 1 1 1.00 40.73 81.47 10632 942 11181 942 4 0 0 50 -1 16 10 0.0000 4 163 1762 11365 392 Alternative access paths\001 4 0 0 50 -1 16 10 0.0000 4 163 1426 11365 698 IPC (pipe or socket)\001 4 0 0 50 -1 16 10 0.0000 4 122 1232 11365 1003 Internal data flow\001 -6 # Smartcard ID-1 6 6840 6120 8550 7200 6 7069 6526 7307 6746 2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 7234 6691 7307 6691 2 1 0 1 0 0 48 -1 20 0.000 0 0 -1 0 0 2 7069 6636 7143 6636 2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 7069 6581 7143 6581 2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 7069 6691 7143 6691 2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 7143 6526 7143 6746 2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 3 7307 6581 7234 6581 7234 6746 2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 7234 6636 7307 6636 2 4 0 1 0 31 49 -1 20 0.000 0 0 1 0 0 5 7069 6526 7307 6526 7307 6746 7069 6746 7069 6526 -6 2 4 0 1 -1 7 50 -1 20 0.000 0 0 1 0 0 5 8472 7185 6904 7185 6904 6197 8472 6197 8472 7185 -6 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 1 1 1.00 40.73 81.47 5346 3142 5957 3753 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 1 1 1.00 40.73 81.47 5346 4669 5957 3905 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 1 2 1 1 1.00 40.73 81.47 1 1 1.00 40.73 81.47 7179 3814 7638 3814 2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 11731 7480 10693 7480 10693 6991 11731 6991 11731 7480 3 2 0 2 1 2 50 -1 -1 6.000 0 1 0 3 1 1 2.00 81.47 162.94 8022 3600 8096 2225 7513 1360 0.000 -1.000 0.000 3 2 0 2 1 2 50 -1 -1 0.000 0 1 0 3 0 0 2.00 81.47 162.94 7332 4730 8737 4486 9471 2897 0.000 -1.000 0.000 3 2 0 2 1 2 50 -1 -1 6.000 0 1 0 3 1 1 2.00 81.47 162.94 3238 3997 4216 4242 4888 4730 0.000 -1.000 0.000 3 2 0 2 1 2 50 -1 -1 6.000 0 1 0 3 1 1 2.00 81.47 162.94 11243 6502 11304 6747 11181 6991 0.000 -1.000 0.000 3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 1 1 1.00 40.73 81.47 10693 7235 9471 7174 8493 6869 0.000 -1.000 0.000 3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 1 1 1.00 40.73 81.47 9898 5647 9532 6380 8493 6563 0.000 -1.000 0.000 3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 1 1 1.00 40.73 81.47 12465 5647 11731 6624 8493 6747 0.000 -1.000 0.000 3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 1 1 1.00 40.73 81.47 2077 5647 3177 6502 6843 6624 0.000 -1.000 0.000 3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 1 1 1.00 40.73 81.47 733 5647 2444 6808 6843 6747 0.000 -1.000 0.000 3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 1 1 1.00 40.73 81.47 3361 5647 4155 6319 6843 6502 0.000 -1.000 0.000 4 0 0 50 -1 18 15 0.0000 4 214 1191 5957 2989 gpg-agent\001 4 0 0 50 -1 16 11 0.0000 4 173 387 10998 7297 pcsd\001 diff --git a/g10/Makefile.am b/g10/Makefile.am index aeb24d7b3..fb54dd9f0 100644 --- a/g10/Makefile.am +++ b/g10/Makefile.am @@ -1,120 +1,121 @@ # Copyright (C) 1998, 1999, 2000, 2001, 2002, # 2003, 2006 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in EXTRA_DIST = options.skel AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common \ -I$(top_srcdir)/include -I$(top_srcdir)/intl include $(top_srcdir)/am/cmacros.am AM_CFLAGS = $(LIBGCRYPT_CFLAGS) -Wno-pointer-sign needed_libs = ../gl/libgnu.a ../common/libcommon.a ../jnlib/libjnlib.a bin_PROGRAMS = gpg2 gpgv2 common_source = \ gpg.h \ build-packet.c \ compress.c \ compress-bz2.c \ filter.h \ free-packet.c \ getkey.c \ keydb.c keydb.h \ keyring.c keyring.h \ seskey.c \ kbnode.c \ main.h \ mainproc.c \ armor.c \ mdfilter.c \ textfilter.c \ progress.c \ misc.c \ options.h \ openfile.c \ keyid.c \ packet.h \ parse-packet.c \ status.c \ status.h \ plaintext.c \ sig-check.c \ keylist.c \ pkglue.c pkglue.h gpg2_SOURCES = gpg.c \ $(common_source) \ pkclist.c \ skclist.c \ pubkey-enc.c \ passphrase.c \ seckey-cert.c \ encr-data.c \ cipher.c \ encode.c \ sign.c \ verify.c \ revoke.c \ decrypt.c \ keyedit.c \ dearmor.c \ import.c \ export.c \ trustdb.c \ trustdb.h \ tdbdump.c \ tdbio.c \ tdbio.h \ delkey.c \ keygen.c \ helptext.c \ keyserver.c \ keyserver-internal.h \ photoid.c photoid.h \ call-agent.c call-agent.h \ card-util.c \ exec.c exec.h gpgv2_SOURCES = gpgv.c \ $(common_source) \ verify.c #gpgd_SOURCES = gpgd.c \ # ks-proto.h \ # ks-proto.c \ # ks-db.c \ # ks-db.h \ # $(common_source) LDADD = $(needed_libs) $(ZLIBS) @LIBINTL@ @CAPLIBS@ @W32LIBS@ gpg2_LDADD = $(LIBGCRYPT_LIBS) $(LDADD) -lassuan -lgpg-error gpgv2_LDADD = $(LIBGCRYPT_LIBS) $(LDADD) -lassuan -lgpg-error $(PROGRAMS): $(needed_libs) install-data-local: $(mkinstalldirs) $(DESTDIR)$(pkgdatadir) $(INSTALL_DATA) $(srcdir)/options.skel \ $(DESTDIR)$(pkgdatadir)/gpg-conf.skel diff --git a/g10/call-agent.c b/g10/call-agent.c index 55fc62569..e3bd7ed57 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1,924 +1,925 @@ /* call-agent.c - divert operations to 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #if 0 /* let Emacs display a red warning */ #error fixme: this shares a lot of code with the file in ../sm #endif #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include #include "gpg.h" #include "util.h" #include "membuf.h" #include "options.h" #include "i18n.h" #include "call-agent.h" #ifndef DBG_ASSUAN # define DBG_ASSUAN 1 #endif static ASSUAN_CONTEXT agent_ctx = NULL; static int force_pipe_server = 1; /* FIXME: set this back to 0. */ struct cipher_parm_s { ASSUAN_CONTEXT ctx; const char *ciphertext; size_t ciphertextlen; }; struct genkey_parm_s { ASSUAN_CONTEXT ctx; const char *sexp; size_t sexplen; }; /* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_agent (void) { int rc = 0; char *infostr, *p; ASSUAN_CONTEXT ctx; char *dft_display = NULL; char *dft_ttyname = NULL; char *dft_ttytype = NULL; char *old_lc = NULL; char *dft_lc = NULL; if (agent_ctx) return 0; /* fixme: We need a context for each thread or serialize the access to the agent. */ infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO"); if (!infostr || !*infostr) { const char *pgmname; const char *argv[3]; int no_close_list[3]; int i; if (opt.verbose) log_info (_("no running gpg-agent - starting one\n")); if (fflush (NULL)) { gpg_error_t tmperr = gpg_error_from_errno (errno); log_error ("error flushing pending output: %s\n", strerror (errno)); return tmperr; } if (!opt.agent_program || !*opt.agent_program) opt.agent_program = GNUPG_DEFAULT_AGENT; if ( !(pgmname = strrchr (opt.agent_program, '/'))) pgmname = opt.agent_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; 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 agent and perform initial handshaking */ rc = assuan_pipe_connect (&ctx, opt.agent_program, (char**)argv, no_close_list); } else { int prot; int pid; infostr = xstrdup (infostr); if ( !(p = strchr (infostr, ':')) || p == infostr) { log_error (_("malformed GPG_AGENT_INFO environment variable\n")); xfree (infostr); force_pipe_server = 1; return start_agent (); } *p++ = 0; pid = atoi (p); while (*p && *p != ':') p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("gpg-agent protocol version %d is not supported\n"), prot); xfree (infostr); force_pipe_server = 1; return start_agent (); } rc = assuan_socket_connect (&ctx, infostr, pid); xfree (infostr); if (rc == ASSUAN_Connect_Failed) { log_error (_("can't connect to the agent - trying fall back\n")); force_pipe_server = 1; return start_agent (); } } if (rc) { log_error ("can't connect to the agent: %s\n", assuan_strerror (rc)); return gpg_error (GPG_ERR_NO_AGENT); } agent_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to agent established\n"); rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); #ifdef __GNUC__ #warning put this code into common/asshelp.c #endif dft_display = getenv ("DISPLAY"); if (opt.display || dft_display) { char *optstr; if (asprintf (&optstr, "OPTION display=%s", opt.display ? opt.display : dft_display) < 0) return gpg_error_from_errno (errno); rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (rc) return map_assuan_err (rc); } if (!opt.ttyname) { dft_ttyname = getenv ("GPG_TTY"); if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); } if (opt.ttyname || dft_ttyname) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", opt.ttyname ? opt.ttyname : dft_ttyname) < 0) return gpg_error_from_errno (errno); rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (rc) return map_assuan_err (rc); } dft_ttytype = getenv ("TERM"); if (opt.ttytype || (dft_ttyname && dft_ttytype)) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", opt.ttyname ? opt.ttytype : dft_ttytype) < 0) return gpg_error_from_errno (errno); rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (rc) return map_assuan_err (rc); } #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) return gpg_error_from_errno (errno); } dft_lc = setlocale (LC_CTYPE, ""); #endif if (opt.lc_ctype || (dft_ttyname && dft_lc)) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", opt.lc_ctype ? opt.lc_ctype : dft_lc) < 0) rc = gpg_error_from_errno (errno); else { rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (rc) rc = map_assuan_err (rc); } } #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) if (old_lc) { setlocale (LC_CTYPE, old_lc); free (old_lc); } #endif if (rc) return rc; #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) return gpg_error_from_errno (errno); } dft_lc = setlocale (LC_MESSAGES, ""); #endif if (opt.lc_messages || (dft_ttyname && dft_lc)) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", opt.lc_messages ? opt.lc_messages : dft_lc) < 0) rc = gpg_error_from_errno (errno); else { rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); free (optstr); if (rc) rc = map_assuan_err (rc); } } #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) if (old_lc) { setlocale (LC_MESSAGES, old_lc); free (old_lc); } #endif return rc; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. */ static char * unescape_status_string (const unsigned char *s) { char *buffer, *d; buffer = d = xtrymalloc (strlen (s)+1); if (!buffer) return NULL; while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d = xtoi_2 (s); if (!*d) *d = '\xff'; d++; s += 2; } else if (*s == '+') { *d++ = ' '; s++; } else *d++ = *s++; } *d = 0; return buffer; } /* Take a 20 byte hexencoded string and put it into the the provided 20 byte buffer FPR in binary format. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if (*s || (n != 40)) return 0; /* no fingerprint (invalid or wrong length). */ n /= 2; for (s=hexstr, n=0; *s; s += 2, n++) fpr[n] = xtoi_2 (s); return 1; /* okay */ } /* Take the serial number from LINE and return it verbatim in a newly allocated string. We make sure that only hex characters are returned. */ static char * store_serialno (const char *line) { const char *s; char *p; for (s=line; hexdigitp (s); s++) ; p = xtrymalloc (s + 1 - line); if (p) { memcpy (p, line, s-line); p[s-line] = 0; } return p; } #if 0 /* Handle a KEYPARMS inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static AssuanError inq_genkey_parms (void *opaque, const char *keyword) { struct genkey_parm_s *parm = opaque; AssuanError rc; rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen); return rc; } /* Call the agent to generate a new key */ int agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey) { int rc; struct genkey_parm_s gk_parm; membuf_t data; size_t len; char *buf; *r_pubkey = NULL; rc = start_agent (); if (rc) return rc; rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); init_membuf (&data, 1024); gk_parm.ctx = agent_ctx; gk_parm.sexp = keyparms; gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL); if (!gk_parm.sexplen) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_transact (agent_ctx, "GENKEY", membuf_data_cb, &data, inq_genkey_parms, &gk_parm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } buf = get_membuf (&data, &len); if (!buf) return gpg_error (GPG_ERR_ENOMEM); if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } *r_pubkey = buf; return 0; } #endif /*0*/ /* Ask the agent whether the corresponding secret key is available for the given keygrip. */ int agent_havekey (const char *hexkeygrip) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_agent (); if (rc) return rc; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } /* Release the card info structure INFO. */ void agent_release_card_info (struct agent_card_info_s *info) { if (!info) return; xfree (info->serialno); info->serialno = NULL; xfree (info->disp_name); info->disp_name = NULL; xfree (info->disp_lang); info->disp_lang = NULL; xfree (info->pubkey_url); info->pubkey_url = NULL; xfree (info->login_data); info->login_data = NULL; info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0; info->fpr1valid = info->fpr2valid = info->fpr3valid = 0; } static AssuanError learn_status_cb (void *opaque, const char *line) { struct agent_card_info_s *parm = opaque; const char *keyword = line; int keywordlen; int i; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { xfree (parm->serialno); parm->serialno = store_serialno (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { xfree (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) { xfree (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) { xfree (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) { xfree (parm->login_data); parm->login_data = unescape_status_string (line); } else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) { while (spacep (p)) p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; for (i=0; *p && i < 3; i++) { parm->chvmaxlen[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } for (i=0; *p && i < 3; i++) { parm->chvretry[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } xfree (buf); } } else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1valid = unhexify_fpr (line, parm->fpr1); else if (no == 2) parm->fpr2valid = unhexify_fpr (line, parm->fpr2); else if (no == 3) parm->fpr3valid = unhexify_fpr (line, parm->fpr3); } else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1); else if (no == 2) parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2); else if (no == 3) parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3); } return 0; } /* Call the agent to learn about a smartcard */ int agent_learn (struct agent_card_info_s *info) { int rc; rc = start_agent (); if (rc) return rc; memset (info, 0, sizeof *info); rc = assuan_transact (agent_ctx, "LEARN --send", NULL, NULL, NULL, NULL, learn_status_cb, info); return map_assuan_err (rc); } /* Call the agent to retrieve a data object. This function returns the data in the same structure as used by the learn command. It is allowed to update such a structure using this commmand. */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info) { int rc; char line[ASSUAN_LINELENGTH]; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "SCD GETATTR "), name); rc = start_agent (); if (rc) return rc; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, learn_status_cb, info); return map_assuan_err (rc); } /* Send an setattr command to the SCdaemon. SERIALNO is not actually used here but required by gpg 1.4's implementation of this code in cardglue.c. */ int agent_scd_setattr (const char *name, const unsigned char *value, size_t valuelen, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; char *p; if (!*name || !valuelen) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); p = stpcpy (stpcpy (line, "SCD SETATTR "), name); *p++ = ' '; for (; valuelen; value++, valuelen--) { if (p >= line + DIM(line)-5 ) return gpg_error (GPG_ERR_TOO_LARGE); if (*value < ' ' || *value == '+' || *value == '%') { sprintf (p, "%%%02X", *value); p += 3; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } *p = 0; rc = start_agent (); if (rc) return rc; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } /* Status callback for the SCD GENKEY command. */ static AssuanError scd_genkey_cb (void *opaque, const char *line) { struct agent_card_genkey_s *parm = opaque; const char *keyword = line; int keywordlen; gpg_error_t rc; log_debug ("got status line `%s'\n", line); for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { parm->fprvalid = unhexify_fpr (line, parm->fpr); } if (keywordlen == 8 && !memcmp (keyword, "KEY-DATA", keywordlen)) { gcry_mpi_t a; const char *name = line; while (*line && !spacep (line)) line++; while (spacep (line)) line++; rc = gcry_mpi_scan (&a, GCRYMPI_FMT_HEX, line, 0, NULL); if (rc) log_error ("error parsing received key data: %s\n", gpg_strerror (rc)); else if (*name == 'n' && spacep (name+1)) parm->n = a; else if (*name == 'e' && spacep (name+1)) parm->e = a; else { log_info ("unknown parameter name in received key data\n"); gcry_mpi_release (a); } } else if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) { parm->created_at = (u32)strtoul (line, NULL, 10); } return 0; } /* Send a GENKEY command to the SCdaemon. SERIALNO is not used in this implementation. */ int agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_agent (); if (rc) return rc; memset (info, 0, sizeof *info); snprintf (line, DIM(line)-1, "SCD GENKEY %s%d", force? "--force ":"", keyno); line[DIM(line)-1] = 0; memset (info, 0, sizeof *info); rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, scd_genkey_cb, info); return map_assuan_err (rc); } static AssuanError membuf_data_cb (void *opaque, const void *buffer, size_t length) { membuf_t *data = opaque; if (buffer) put_membuf (data, buffer, length); return 0; } /* Send a sign command to the scdaemon via gpg-agent's pass thru mechanism. */ int agent_scd_pksign (const char *serialno, int hashalgo, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; /* Note, hashalgo is not yet used but hardwired to SHA1 in SCdaemon. */ *r_buf = NULL; *r_buflen = 0; rc = start_agent (); if (rc) return rc; if (indatalen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); sprintf (line, "SCD SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); init_membuf (&data, 1024); #if 0 if (!hashalgo) /* Temporary test hack. */ snprintf (line, DIM(line)-1, "SCD PKAUTH %s", serialno); else #endif snprintf (line, DIM(line)-1, "SCD PKSIGN %s", serialno); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } *r_buf = get_membuf (&data, r_buflen); return 0; } /* Decrypt INDATA of length INDATALEN using the card identified by SERIALNO. Return the plaintext in a nwly allocated buffer stored at the address of R_BUF. Note, we currently support only RSA or more exactly algorithms taking one input data element. */ int agent_scd_pkdecrypt (const char *serialno, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; *r_buf = NULL; rc = start_agent (); if (rc) return rc; /* FIXME: use secure memory where appropriate */ if (indatalen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); sprintf (line, "SCD SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "SCD PKDECRYPT %s", serialno); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return gpg_error (GPG_ERR_ENOMEM); return 0; } /* Change the PIN of an OpenPGP card or reset the retry counter. CHVNO 1: Change the PIN 2: Same as 1 3: Change the admin PIN 101: Set a new PIN and reset the retry counter 102: Same as 101 SERIALNO is not used. */ int agent_scd_change_pin (int chvno, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; const char *reset = ""; if (chvno >= 100) reset = "--reset"; chvno %= 100; rc = start_agent (); if (rc) return rc; snprintf (line, DIM(line)-1, "SCD PASSWD %s %d", reset, chvno); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } /* Perform a CHECKPIN operation. SERIALNO should be the serial number of the card - optionally followed by the fingerprint; however the fingerprint is ignored here. */ int agent_scd_checkpin (const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_agent (); if (rc) return rc; snprintf (line, DIM(line)-1, "SCD CHECKPIN %s", serialno); line[DIM(line)-1] = 0; return assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } /* Dummy function, only used by the gpg 1.4 implementation. */ void agent_clear_pin_cache (const char *sn) { } diff --git a/g10/call-agent.h b/g10/call-agent.h index 71044e38b..d09b87e3a 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -1,109 +1,110 @@ /* call-agent.h - Divert operations to the agent * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_G10_CALL_AGENT_H #define GNUPG_G10_CALL_AGENT_H struct agent_card_info_s { int error; /* private. */ char *serialno; /* malloced hex string. */ char *disp_name; /* malloced. */ char *disp_lang; /* malloced. */ int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ char *pubkey_url; /* malloced. */ char *login_data; /* malloced. */ char *private_do[4]; /* malloced. */ char cafpr1valid; char cafpr2valid; char cafpr3valid; char cafpr1[20]; char cafpr2[20]; char cafpr3[20]; char fpr1valid; char fpr2valid; char fpr3valid; char fpr1[20]; char fpr2[20]; char fpr3[20]; u32 fpr1time; u32 fpr2time; u32 fpr3time; unsigned long sig_counter; int chv1_cached; /* True if a PIN is not required for each signing. Note that the gpg-agent might cache it anyway. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ }; struct agent_card_genkey_s { char fprvalid; char fpr[20]; u32 created_at; gcry_mpi_t n; gcry_mpi_t e; }; /* Release the card info structure. */ void agent_release_card_info (struct agent_card_info_s *info); /* Return card info. */ int agent_learn (struct agent_card_info_s *info); /* Update INFO with the attribute NAME. */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info); /* Check whether the secret key for the key identified by HEXKEYGRIP is available. Return 0 for yes or an error code. */ int agent_havekey (const char *hexkeygrip); /* Send a SETATTR command to the SCdaemon. */ int agent_scd_setattr (const char *name, const unsigned char *value, size_t valuelen, const char *serialno); /* Send a GENKEY command to the SCdaemon. */ int agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, const char *serialno); /* Send a PKSIGN command to the SCdaemon. */ int agent_scd_pksign (const char *keyid, int hashalgo, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen); /* Send a PKDECRYPT command to the SCdaemon. */ int agent_scd_pkdecrypt (const char *serialno, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen); /* Change the PIN of an OpenPGP card or reset the retry counter. */ int agent_scd_change_pin (int chvno, const char *serialno); /* Send the CHECKPIN command to the SCdaemon. */ int agent_scd_checkpin (const char *serialno); /* Dummy function, only implemented by gpg 1.4. */ void agent_clear_pin_cache (const char *sn); #endif /*GNUPG_G10_CALL_AGENT_H*/ diff --git a/g10/comment.c b/g10/comment.c index b52104cd7..193087107 100644 --- a/g10/comment.c +++ b/g10/comment.c @@ -1,112 +1,113 @@ /* comment.c - write comment stuff * Copyright (C) 1998, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "options.h" #include "packet.h" #include "errors.h" #include "iobuf.h" #include "memory.h" #include "util.h" #include "main.h" #include "keydb.h" int write_comment( iobuf_t out, const char *s ) { PACKET pkt; size_t n = strlen(s); int rc=0; pkt.pkttype = PKT_COMMENT; if( *s != '#' ) { pkt.pkt.comment = xmalloc ( sizeof *pkt.pkt.comment + n ); pkt.pkt.comment->len = n+1; *pkt.pkt.comment->data = '#'; strcpy(pkt.pkt.comment->data+1, s); } else { pkt.pkt.comment = xmalloc ( sizeof *pkt.pkt.comment + n - 1 ); pkt.pkt.comment->len = n; strcpy(pkt.pkt.comment->data, s); } if( (rc = build_packet( out, &pkt )) ) log_error("build_packet(comment) failed: %s\n", gpg_strerror (rc) ); free_packet( &pkt ); return rc; } KBNODE make_comment_node_from_buffer (const char *s, size_t n) { PACKET *pkt; pkt = gcry_xcalloc( 1, sizeof *pkt ); pkt->pkttype = PKT_COMMENT; pkt->pkt.comment = gcry_xmalloc( sizeof *pkt->pkt.comment + n - 1 ); pkt->pkt.comment->len = n; strcpy(pkt->pkt.comment->data, s); return new_kbnode( pkt ); } KBNODE make_comment_node( const char *s ) { return make_comment_node_from_buffer (s, strlen (s)); } KBNODE make_mpi_comment_node( const char *s, gcry_mpi_t a ) { PACKET *pkt; byte *buf, *pp; size_t n1, nb1; size_t n = strlen(s); nb1 = mpi_get_nbits( a ); if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &n1, a)) BUG (); /* fixme: allocate it on the stack */ buf = xmalloc (n1); if (gcry_mpi_print (GCRYMPI_FMT_PGP, buf, n1, &n1, a)) BUG (); pkt = xcalloc (1, sizeof *pkt ); pkt->pkttype = PKT_COMMENT; pkt->pkt.comment = xmalloc ( sizeof *pkt->pkt.comment + n + 2 + n1 ); pkt->pkt.comment->len = n+1+2+n1; pp = pkt->pkt.comment->data; memcpy(pp, s, n+1); memcpy(pp+n+1, buf, n1 ); xfree (buf); return new_kbnode( pkt ); } diff --git a/g10/gpg.c b/g10/gpg.c index 52ae553c1..4235f3f7a 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1,4096 +1,4105 @@ /* gpg.c - The GnuPG utility (main for gpg) * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, * 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ #include #include #include #include #include #include #include #include #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #ifdef HAVE_STAT #include /* for stat() */ #endif #include #include #ifdef HAVE_W32_SYSTEM #include #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" #include "packet.h" #include "../common/iobuf.h" #include "util.h" #include "main.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "cipher.h" #include "filter.h" #include "ttyio.h" #include "i18n.h" #include "status.h" #include "keyserver-internal.h" #include "exec.h" #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #ifndef S_IRGRP # define S_IRGRP 0 # define S_IWGRP 0 #endif #else #define MY_O_BINARY 0 #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', oInteractive = 'i', oKOption = 'k', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', oHiddenRecipient = 'R', aSign = 's', oTextmodeShort= 't', oLocalUser = 'u', oVerbose = 'v', oCompress = 'z', oSetNotation = 'N', aListSecretKeys = 'K', oBatch = 500, oMaxOutput, oSigNotation, oCertNotation, oShowNotation, oNoShowNotation, aEncrFiles, aEncrSym, aDecryptFiles, aClearsign, aStore, aKeygen, aSignEncr, aSignEncrSym, aSignSym, aSignKey, aLSignKey, aListConfig, aGPGConfList, aListPackets, aEditKey, aDeleteKeys, aDeleteSecretKeys, aDeleteSecretAndPublicKeys, aKMode, aKModeC, aImport, aFastImport, aVerify, aVerifyFiles, aListKeys, aListSigs, aSendKeys, aRecvKeys, aSearchKeys, aRefreshKeys, aFetchKeys, aExport, aExportSecret, aExportSecretSub, aCheckKeys, aGenRevoke, aDesigRevoke, aPrimegen, aPrintMD, aPrintMDs, aCheckTrustDB, aUpdateTrustDB, aFixTrustDB, aListTrustDB, aListTrustPath, aExportOwnerTrust, aListOwnerTrust, aImportOwnerTrust, aDeArmor, aEnArmor, aGenRandom, aRebuildKeydbCaches, aCardStatus, aCardEdit, aChangePIN, oTextmode, oNoTextmode, oExpert, oNoExpert, oDefSigExpire, oAskSigExpire, oNoAskSigExpire, oDefCertExpire, oAskCertExpire, oNoAskCertExpire, oDefCertLevel, oMinCertLevel, oAskCertLevel, oNoAskCertLevel, oFingerprint, oWithFingerprint, oAnswerYes, oAnswerNo, oKeyring, oPrimaryKeyring, oSecretKeyring, oShowKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugCCIDDriver, oStatusFD, oStatusFile, oAttributeFD, oAttributeFile, oEmitVersion, oNoEmitVersion, oCompletesNeeded, oMarginalsNeeded, oMaxCertDepth, oLoadExtension, oGnuPG, oRFC1991, oRFC2440, oOpenPGP, oPGP2, oPGP6, oPGP7, oPGP8, oRFC2440Text, oNoRFC2440Text, oCipherAlgo, oDigestAlgo, oCertDigestAlgo, oCompressAlgo, oCompressLevel, oBZ2CompressLevel, oBZ2DecompressLowmem, oPasswd, oPasswdFD, oPasswdFile, oCommandFD, oCommandFile, oQuickRandom, oNoVerbose, oTrustDBName, oNoSecmemWarn, oRequireSecmem, oNoRequireSecmem, oNoPermissionWarn, oNoMDCWarn, oNoArmor, oNoDefKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oWithColons, oWithKeyData, oSkipVerify, oCompressKeys, oCompressSigs, oAlwaysTrust, oTrustModel, oForceOwnertrust, oSetFilename, oForYourEyesOnly, oNoForYourEyesOnly, oSetPolicyURL, oSigPolicyURL, oCertPolicyURL, oShowPolicyURL, oNoShowPolicyURL, oSigKeyserverURL, oUseEmbeddedFilename, oNoUseEmbeddedFilename, oComment, oDefaultComment, oNoComments, oThrowKeyids, oNoThrowKeyids, oShowPhotos, oNoShowPhotos, oPhotoViewer, oForceV3Sigs, oNoForceV3Sigs, oForceV4Certs, oNoForceV4Certs, oForceMDC, oNoForceMDC, oDisableMDC, oNoDisableMDC, oS2KMode, oS2KDigest, oS2KCipher, oSimpleSKChecksum, oDisplayCharset, oNotDashEscaped, oEscapeFrom, oNoEscapeFrom, oLockOnce, oLockMultiple, oLockNever, oKeyServer, oKeyServerOptions, oImportOptions, oExportOptions, oListOptions, oVerifyOptions, oTempDir, oExecPath, oEncryptTo, oHiddenEncryptTo, oNoEncryptTo, oLoggerFD, oLoggerFile, oUtf8Strings, oNoUtf8Strings, oDisableCipherAlgo, oDisablePubkeyAlgo, oAllowNonSelfsignedUID, oNoAllowNonSelfsignedUID, oAllowFreeformUID, oNoAllowFreeformUID, oAllowSecretKeyImport, oEnableSpecialFilenames, oNoLiteral, oSetFilesize, oHonorHttpProxy, oFastListMode, oListOnly, oIgnoreTimeConflict, oIgnoreValidFrom, oIgnoreCrcError, oIgnoreMDCError, oShowSessionKey, oOverrideSessionKey, oNoRandomSeedFile, oAutoKeyRetrieve, oNoAutoKeyRetrieve, oUseAgent, oNoUseAgent, oGpgAgentInfo, oMergeOnly, oTryAllSecrets, oTrustedKey, oNoExpensiveTrustChecks, oFixedListMode, oNoSigCache, oNoSigCreateCheck, oAutoCheckTrustDB, oNoAutoCheckTrustDB, oPreservePermissions, oDefaultPreferenceList, oPersonalCipherPreferences, oPersonalDigestPreferences, oPersonalCompressPreferences, oAgentProgram, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oGroup, oUnGroup, oNoGroups, oStrict, oNoStrict, oMangleDosFilenames, oNoMangleDosFilenames, oEnableProgressFilter, oMultifile, oKeyidFormat, oExitOnStatusWriteError, oLimitCardInsertTries, oRequireCrossCert, oNoRequireCrossCert, oAutoKeyLocate, oNoAutoKeyLocate, oAllowMultisigVerification, oEnableDSA2, oDisableDSA2, + oDebugAllowRun, oNoop }; static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aSign, "sign", 256, N_("|[file]|make a signature")}, { aClearsign, "clearsign", 256, N_("|[file]|make a clear text signature")}, { aDetachedSign, "detach-sign", 256, N_("make a detached signature")}, { aEncr, "encrypt", 256, N_("encrypt data")}, { aEncrFiles, "encrypt-files", 256, "@"}, { aSym, "symmetric", 256, N_("encryption only with symmetric cipher")}, { aStore, "store", 256, "@"}, { aDecrypt, "decrypt", 256, N_("decrypt data (default)")}, { aDecryptFiles, "decrypt-files", 256, "@"}, { aVerify, "verify" , 256, N_("verify a signature")}, { aVerifyFiles, "verify-files" , 256, "@" }, { aListKeys, "list-keys", 256, N_("list keys")}, { aListKeys, "list-public-keys", 256, "@" }, { aListSigs, "list-sigs", 256, N_("list keys and signatures")}, { aCheckKeys, "check-sigs",256, N_("list and check key signatures")}, { oFingerprint, "fingerprint", 256, N_("list keys and fingerprints")}, { aListSecretKeys, "list-secret-keys", 256, N_("list secret keys")}, { aKeygen, "gen-key", 256, N_("generate a new key pair")}, { aDeleteKeys,"delete-keys",256,N_("remove keys from the public keyring")}, { aDeleteSecretKeys, "delete-secret-keys",256, N_("remove keys from the secret keyring")}, { aSignKey, "sign-key" ,256, N_("sign a key")}, { aLSignKey, "lsign-key" ,256, N_("sign a key locally")}, { aEditKey, "edit-key" ,256, N_("sign or edit a key")}, { aGenRevoke, "gen-revoke",256, N_("generate a revocation certificate")}, { aDesigRevoke, "desig-revoke",256, "@" }, { aExport, "export" , 256, N_("export keys") }, { aSendKeys, "send-keys" , 256, N_("export keys to a key server") }, { aRecvKeys, "recv-keys" , 256, N_("import keys from a key server") }, { aSearchKeys, "search-keys" , 256, N_("search for keys on a key server") }, { aRefreshKeys, "refresh-keys", 256, N_("update all keys from a keyserver")}, { aFetchKeys, "fetch-keys" , 256, "@" }, { aExportSecret, "export-secret-keys" , 256, "@" }, { aExportSecretSub, "export-secret-subkeys" , 256, "@" }, { aImport, "import", 256 , N_("import/merge keys")}, { aFastImport, "fast-import", 256 , "@"}, #ifdef ENABLE_CARD_SUPPORT { aCardStatus, "card-status", 256, N_("print the card status")}, { aCardEdit, "card-edit", 256, N_("change data on a card")}, { aChangePIN, "change-pin", 256, N_("change a card's PIN")}, #endif { aListConfig, "list-config", 256, "@"}, { aGPGConfList, "gpgconf-list", 256, "@" }, { aListPackets, "list-packets",256, "@"}, { aExportOwnerTrust, "export-ownertrust", 256, "@"}, { aImportOwnerTrust, "import-ownertrust", 256, "@"}, { aUpdateTrustDB, "update-trustdb",0 , N_("update the trust database")}, { aCheckTrustDB, "check-trustdb", 0, "@"}, { aFixTrustDB, "fix-trustdb", 0, "@"}, { aDeArmor, "dearmor", 256, "@"}, { aDeArmor, "dearmour", 256, "@"}, { aEnArmor, "enarmor", 256, "@"}, { aEnArmor, "enarmour", 256, "@"}, { aPrintMD, "print-md" , 256, N_("|algo [files]|print message digests")}, { aPrimegen, "gen-prime" , 256, "@" }, { aGenRandom, "gen-random" , 256, "@" }, { 301, NULL, 0, N_("@\nOptions:\n ") }, { oArmor, "armor", 0, N_("create ascii armored output")}, { oArmor, "armour", 0, "@" }, { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")}, { oHiddenRecipient, "hidden-recipient", 2, "@" }, { oRecipient, "remote-user", 2, "@"}, /* old option name */ { oDefRecipient, "default-recipient", 2, "@"}, { oDefRecipientSelf, "default-recipient-self", 0, "@"}, { oNoDefRecipient, "no-default-recipient", 0, "@" }, { oTempDir, "temp-directory", 2, "@" }, { oExecPath, "exec-path", 2, "@" }, { oEncryptTo, "encrypt-to", 2, "@" }, { oHiddenEncryptTo, "hidden-encrypt-to", 2, "@" }, { oNoEncryptTo, "no-encrypt-to", 0, "@" }, { oLocalUser, "local-user",2, N_("use this user-id to sign or decrypt")}, { oCompress, NULL, 1, N_("|N|set compress level N (0 disables)") }, { oCompressLevel, "compress-level", 1, "@" }, { oBZ2CompressLevel, "bzip2-compress-level", 1, "@" }, { oBZ2DecompressLowmem, "bzip2-decompress-lowmem", 0, "@" }, { oTextmodeShort, NULL, 0, "@"}, { oTextmode, "textmode", 0, N_("use canonical text mode")}, { oNoTextmode, "no-textmode", 0, "@"}, { oExpert, "expert", 0, "@"}, { oNoExpert, "no-expert", 0, "@"}, { oDefSigExpire, "default-sig-expire", 2, "@"}, { oAskSigExpire, "ask-sig-expire", 0, "@"}, { oNoAskSigExpire, "no-ask-sig-expire", 0, "@"}, { oDefCertExpire, "default-cert-expire", 2, "@"}, { oAskCertExpire, "ask-cert-expire", 0, "@"}, { oNoAskCertExpire, "no-ask-cert-expire", 0, "@"}, { oDefCertLevel, "default-cert-level", 1, "@"}, { oMinCertLevel, "min-cert-level", 1, "@"}, { oAskCertLevel, "ask-cert-level", 0, "@"}, { oNoAskCertLevel, "no-ask-cert-level", 0, "@"}, { oOutput, "output", 2, N_("use as output file")}, { oMaxOutput, "max-output", 16|4, "@" }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, "@"}, { oNoTTY, "no-tty", 0, "@"}, { oForceV3Sigs, "force-v3-sigs", 0, "@"}, { oNoForceV3Sigs, "no-force-v3-sigs", 0, "@"}, { oForceV4Certs, "force-v4-certs", 0, "@"}, { oNoForceV4Certs, "no-force-v4-certs", 0, "@"}, { oForceMDC, "force-mdc", 0, "@"}, { oNoForceMDC, "no-force-mdc", 0, "@" }, { oDisableMDC, "disable-mdc", 0, "@"}, { oNoDisableMDC, "no-disable-mdc", 0, "@" }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oInteractive, "interactive", 0, N_("prompt before overwriting") }, { oUseAgent, "use-agent",0, "@"}, { oNoUseAgent, "no-use-agent",0, "@"}, { oGpgAgentInfo, "gpg-agent-info",2, "@"}, { oBatch, "batch", 0, "@"}, { oAnswerYes, "yes", 0, "@"}, { oAnswerNo, "no", 0, "@"}, { oKeyring, "keyring", 2, "@"}, { oPrimaryKeyring, "primary-keyring",2, "@" }, { oSecretKeyring, "secret-keyring", 2, "@"}, { oShowKeyring, "show-keyring", 0, "@"}, { oDefaultKey, "default-key", 2, "@"}, { oKeyServer, "keyserver", 2, "@"}, { oKeyServerOptions, "keyserver-options",2,"@"}, { oImportOptions, "import-options",2,"@"}, { oExportOptions, "export-options",2,"@"}, { oListOptions, "list-options",2,"@"}, { oVerifyOptions, "verify-options",2,"@"}, { oDisplayCharset, "display-charset", 2, "@"}, { oDisplayCharset, "charset", 2, "@"}, { oOptions, "options", 2, "@"}, { oDebug, "debug" ,4|16, "@"}, { oDebugLevel, "debug-level" ,2, "@"}, { oDebugAll, "debug-all" ,0, "@"}, { oStatusFD, "status-fd" ,1, "@"}, { oStatusFile, "status-file" ,2, "@"}, { oAttributeFD, "attribute-fd" ,1, "@" }, { oAttributeFile, "attribute-file" ,2, "@" }, { oNoop, "sk-comments", 0, "@"}, { oNoop, "no-sk-comments", 0, "@"}, { oCompletesNeeded, "completes-needed", 1, "@"}, { oMarginalsNeeded, "marginals-needed", 1, "@"}, { oMaxCertDepth, "max-cert-depth", 1, "@" }, { oTrustedKey, "trusted-key", 2, "@"}, { oLoadExtension, "load-extension", 2, "@"}, { oGnuPG, "gnupg", 0, "@"}, { oGnuPG, "no-pgp2", 0, "@"}, { oGnuPG, "no-pgp6", 0, "@"}, { oGnuPG, "no-pgp7", 0, "@"}, { oGnuPG, "no-pgp8", 0, "@"}, { oRFC1991, "rfc1991", 0, "@"}, { oRFC2440, "rfc2440", 0, "@" }, { oOpenPGP, "openpgp", 0, N_("use strict OpenPGP behavior")}, { oPGP2, "pgp2", 0, N_("generate PGP 2.x compatible messages")}, { oPGP6, "pgp6", 0, "@"}, { oPGP7, "pgp7", 0, "@"}, { oPGP8, "pgp8", 0, "@"}, { oRFC2440Text, "rfc2440-text", 0, "@"}, { oNoRFC2440Text, "no-rfc2440-text", 0, "@"}, { oS2KMode, "s2k-mode", 1, "@"}, { oS2KDigest, "s2k-digest-algo", 2, "@"}, { oS2KCipher, "s2k-cipher-algo", 2, "@"}, { oSimpleSKChecksum, "simple-sk-checksum", 0, "@"}, { oCipherAlgo, "cipher-algo", 2, "@"}, { oDigestAlgo, "digest-algo", 2, "@"}, { oCertDigestAlgo, "cert-digest-algo", 2 , "@" }, { oCompressAlgo,"compress-algo", 2, "@"}, { oCompressAlgo, "compression-algo", 2, "@"}, /* Alias */ { oThrowKeyids, "throw-keyid", 0, "@"}, { oThrowKeyids, "throw-keyids", 0, "@"}, { oNoThrowKeyids, "no-throw-keyid", 0, "@" }, { oNoThrowKeyids, "no-throw-keyids", 0, "@" }, { oShowPhotos, "show-photos", 0, "@" }, { oNoShowPhotos, "no-show-photos", 0, "@" }, { oPhotoViewer, "photo-viewer", 2, "@" }, { oSetNotation, "set-notation", 2, "@" }, { oSetNotation, "notation-data", 2, "@" }, /* Alias */ { oSigNotation, "sig-notation", 2, "@" }, { oCertNotation, "cert-notation", 2, "@" }, { 302, NULL, 0, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )}, { 303, NULL, 0, N_("@\nExamples:\n\n" " -se -r Bob [file] sign and encrypt for user Bob\n" " --clearsign [file] make a clear text signature\n" " --detach-sign [file] make a detached signature\n" " --list-keys [names] show keys\n" " --fingerprint [names] show fingerprints\n" ) }, /* hidden options */ { aListOwnerTrust, "list-ownertrust", 256, "@"}, /* deprecated */ { aPrintMDs, "print-mds" , 256, "@"}, /* old */ { aListTrustDB, "list-trustdb",0 , "@"}, /* Not yet used */ /* { aListTrustPath, "list-trust-path",0, "@"}, */ { oKOption, NULL, 0, "@"}, { oPasswd, "passphrase",2, "@" }, { oPasswdFD, "passphrase-fd",1, "@" }, { oPasswdFile, "passphrase-file",2, "@" }, { oCommandFD, "command-fd",1, "@" }, { oCommandFile, "command-file",2, "@" }, { oQuickRandom, "quick-random", 0, "@"}, { oNoVerbose, "no-verbose", 0, "@"}, { oTrustDBName, "trustdb-name", 2, "@" }, { oNoSecmemWarn, "no-secmem-warning", 0, "@" }, { oRequireSecmem,"require-secmem", 0, "@" }, { oNoRequireSecmem,"no-require-secmem", 0, "@" }, { oNoPermissionWarn, "no-permission-warning", 0, "@" }, { oNoMDCWarn, "no-mdc-warning", 0, "@" }, { oNoArmor, "no-armor", 0, "@"}, { oNoArmor, "no-armour", 0, "@"}, { oNoDefKeyring, "no-default-keyring", 0, "@" }, { oNoGreeting, "no-greeting", 0, "@" }, { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */ { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ { oNoBatch, "no-batch", 0, "@" }, { oWithColons, "with-colons", 0, "@"}, { oWithKeyData,"with-key-data", 0, "@"}, { aListKeys, "list-key", 0, "@" }, /* alias */ { aListSigs, "list-sig", 0, "@" }, /* alias */ { aCheckKeys, "check-sig",0, "@" }, /* alias */ { oSkipVerify, "skip-verify",0, "@" }, { oCompressKeys, "compress-keys",0, "@"}, { oCompressSigs, "compress-sigs",0, "@"}, { oDefCertLevel, "default-cert-check-level", 1, "@"}, /* Old option */ { oAlwaysTrust, "always-trust", 0, "@"}, { oTrustModel, "trust-model", 2, "@"}, { oForceOwnertrust, "force-ownertrust", 2, "@"}, { oSetFilename, "set-filename", 2, "@" }, { oForYourEyesOnly, "for-your-eyes-only", 0, "@" }, { oNoForYourEyesOnly, "no-for-your-eyes-only", 0, "@" }, { oSetPolicyURL, "set-policy-url", 2, "@" }, { oSigPolicyURL, "sig-policy-url", 2, "@" }, { oCertPolicyURL, "cert-policy-url", 2, "@" }, { oShowPolicyURL, "show-policy-url", 0, "@" }, { oNoShowPolicyURL, "no-show-policy-url", 0, "@" }, { oSigKeyserverURL, "sig-keyserver-url", 2, "@" }, { oShowNotation, "show-notation", 0, "@" }, { oNoShowNotation, "no-show-notation", 0, "@" }, { oComment, "comment", 2, "@" }, { oDefaultComment, "default-comment", 0, "@" }, { oNoComments, "no-comments", 0, "@" }, { oEmitVersion, "emit-version", 0, "@"}, { oNoEmitVersion, "no-emit-version", 0, "@"}, { oNoEmitVersion, "no-version", 0, "@"}, /* alias */ { oNotDashEscaped, "not-dash-escaped", 0, "@" }, { oEscapeFrom, "escape-from-lines", 0, "@" }, { oNoEscapeFrom, "no-escape-from-lines", 0, "@" }, { oLockOnce, "lock-once", 0, "@" }, { oLockMultiple, "lock-multiple", 0, "@" }, { oLockNever, "lock-never", 0, "@" }, { oLoggerFD, "logger-fd",1, "@" }, { oLoggerFile, "log-file",2, "@" }, { oUseEmbeddedFilename, "use-embedded-filename", 0, "@" }, { oNoUseEmbeddedFilename, "no-use-embedded-filename", 0, "@" }, { oUtf8Strings, "utf8-strings", 0, "@" }, { oNoUtf8Strings, "no-utf8-strings", 0, "@" }, { oWithFingerprint, "with-fingerprint", 0, "@" }, { oDisableCipherAlgo, "disable-cipher-algo", 2, "@" }, { oDisablePubkeyAlgo, "disable-pubkey-algo", 2, "@" }, { oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", 0, "@" }, { oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", 0, "@" }, { oAllowFreeformUID, "allow-freeform-uid", 0, "@" }, { oNoAllowFreeformUID, "no-allow-freeform-uid", 0, "@" }, { oNoLiteral, "no-literal", 0, "@" }, { oSetFilesize, "set-filesize", 20, "@" }, { oHonorHttpProxy,"honor-http-proxy", 0, "@" }, { oFastListMode,"fast-list-mode", 0, "@" }, { oFixedListMode,"fixed-list-mode", 0, "@" }, { oListOnly, "list-only", 0, "@"}, { oIgnoreTimeConflict, "ignore-time-conflict", 0, "@" }, { oIgnoreValidFrom, "ignore-valid-from", 0, "@" }, { oIgnoreCrcError, "ignore-crc-error", 0,"@" }, { oIgnoreMDCError, "ignore-mdc-error", 0,"@" }, { oShowSessionKey, "show-session-key", 0, "@" }, { oOverrideSessionKey, "override-session-key", 2, "@" }, { oNoRandomSeedFile, "no-random-seed-file", 0, "@" }, { oAutoKeyRetrieve, "auto-key-retrieve", 0, "@" }, { oNoAutoKeyRetrieve, "no-auto-key-retrieve", 0, "@" }, { oNoSigCache, "no-sig-cache", 0, "@" }, { oNoSigCreateCheck, "no-sig-create-check", 0, "@" }, { oAutoCheckTrustDB, "auto-check-trustdb", 0, "@"}, { oNoAutoCheckTrustDB, "no-auto-check-trustdb", 0, "@"}, { oMergeOnly, "merge-only", 0, "@" }, { oAllowSecretKeyImport, "allow-secret-key-import", 0, "@" }, { oTryAllSecrets, "try-all-secrets", 0, "@" }, { oEnableSpecialFilenames, "enable-special-filenames", 0, "@" }, { oNoExpensiveTrustChecks, "no-expensive-trust-checks", 0, "@" }, { aDeleteSecretAndPublicKeys, "delete-secret-and-public-keys",256, "@" }, { aRebuildKeydbCaches, "rebuild-keydb-caches", 256, "@"}, { oPreservePermissions, "preserve-permissions", 0, "@"}, { oDefaultPreferenceList, "default-preference-list", 2, "@"}, { oPersonalCipherPreferences, "personal-cipher-preferences", 2, "@"}, { oPersonalDigestPreferences, "personal-digest-preferences", 2, "@"}, { oPersonalCompressPreferences, "personal-compress-preferences", 2, "@"}, /* Aliases. I constantly mistype these, and assume other people do as well. */ { oPersonalCipherPreferences, "personal-cipher-prefs", 2, "@"}, { oPersonalDigestPreferences, "personal-digest-prefs", 2, "@"}, { oPersonalCompressPreferences, "personal-compress-prefs", 2, "@"}, { oAgentProgram, "agent-program", 2 , "@" }, { oDisplay, "display", 2, "@" }, { oTTYname, "ttyname", 2, "@" }, { oTTYtype, "ttytype", 2, "@" }, { oLCctype, "lc-ctype", 2, "@" }, { oLCmessages, "lc-messages", 2, "@" }, { oGroup, "group", 2, "@" }, { oUnGroup, "ungroup", 2, "@" }, { oNoGroups, "no-groups", 0, "@" }, { oStrict, "strict", 0, "@" }, { oNoStrict, "no-strict", 0, "@" }, { oMangleDosFilenames, "mangle-dos-filenames", 0, "@" }, { oNoMangleDosFilenames, "no-mangle-dos-filenames", 0, "@" }, { oEnableProgressFilter, "enable-progress-filter", 0, "@" }, { oMultifile, "multifile", 0, "@" }, { oKeyidFormat, "keyid-format", 2, "@" }, { oExitOnStatusWriteError, "exit-on-status-write-error", 0, "@" }, { oLimitCardInsertTries, "limit-card-insert-tries", 1, "@"}, { oAllowMultisigVerification, "allow-multisig-verification", 0, "@"}, { oEnableDSA2, "enable-dsa2", 0, "@"}, { oDisableDSA2, "disable-dsa2", 0, "@"}, /* These two are aliases to help users of the PGP command line product use gpg with minimal pain. Many commands are common already as they seem to have borrowed commands from us. Now I'm returning the favor. */ { oLocalUser, "sign-with", 2, "@" }, { oRecipient, "user", 2, "@" }, { oRequireCrossCert, "require-backsigs", 0, "@"}, { oRequireCrossCert, "require-cross-certification", 0, "@"}, { oNoRequireCrossCert, "no-require-backsigs", 0, "@"}, { oNoRequireCrossCert, "no-require-cross-certification", 0, "@"}, { oAutoKeyLocate, "auto-key-locate", 2, "@"}, { oNoAutoKeyLocate, "no-auto-key-locate", 0, "@"}, + + { oDebugAllowRun, "debug_allow_run", 0, "@"}, {0,NULL,0,NULL} }; #ifdef ENABLE_SELINUX_HACKS #define ALWAYS_ADD_KEYRINGS 1 #else #define ALWAYS_ADD_KEYRINGS 0 #endif int g10_errors_seen = 0; static int utf8_strings = 0; static int maybe_setuid = 1; static char *build_list( const char *text, char letter, const char *(*mapf)(int), int (*chkf)(int) ); static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void print_mds( const char *fname, int algo ); static void add_notation_data( const char *string, int which ); static void add_policy_url( const char *string, int which ); static void add_keyserver_url( const char *string, int which ); static void emergency_cleanup (void); static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers, *zips; const char *p; switch( level ) { case 11: p = "gpg (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to .\n"); break; #ifdef IS_DEVELOPMENT_VERSION case 20: p="NOTE: THIS IS A DEVELOPMENT VERSION!"; break; case 21: p="It is only intended for test purposes and should NOT be"; break; case 22: p="used in a production environment or with production keys!"; break; #endif case 1: case 40: p = _("Usage: gpg [options] [files] (-h for help)"); break; case 41: p = _("Syntax: gpg [options] [files]\n" "sign, check, encrypt or decrypt\n" "default operation depends on the input data\n"); break; case 31: p = "\nHome: "; break; #ifndef __riscos__ case 32: p = opt.homedir; break; #else /* __riscos__ */ case 32: p = make_filename(opt.homedir, NULL); break; #endif /* __riscos__ */ case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!pubkeys) pubkeys = build_list (_("Pubkey: "), 0, gcry_pk_algo_name, openpgp_pk_test_algo ); p = pubkeys; break; case 35: if( !ciphers ) ciphers = build_list(_("Cipher: "), 'S', gcry_cipher_algo_name, openpgp_cipher_test_algo ); p = ciphers; break; case 36: if( !digests ) digests = build_list(_("Hash: "), 'H', gcry_md_algo_name, openpgp_md_test_algo ); p = digests; break; case 37: if( !zips ) zips = build_list(_("Compression: "),'Z', compress_algo_to_string, check_compress_algo); p = zips; break; default: p = NULL; } return p; } static char * build_list( const char *text, char letter, const char * (*mapf)(int), int (*chkf)(int) ) { int i; const char *s; size_t n=strlen(text)+2; char *list, *p, *line=NULL; if (maybe_setuid) gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ for(i=0; i <= 110; i++ ) if( !chkf(i) && (s=mapf(i)) ) n += strlen(s) + 7 + 2; list = xmalloc( 21 + n ); *list = 0; for(p=NULL, i=0; i <= 110; i++ ) { if( !chkf(i) && (s=mapf(i)) ) { if( !p ) { p = stpcpy( list, text ); line=p; } else p = stpcpy( p, ", "); if(strlen(line)>60) { int spaces=strlen(text); list=xrealloc(list,n+spaces+1); /* realloc could move the block, so find the end again */ p=list; while(*p) p++; p=stpcpy(p, "\n"); line=p; for(;spaces;spaces--) p=stpcpy(p, " "); } p = stpcpy(p, s ); if(opt.verbose && letter) { char num[8]; sprintf(num," (%c%d)",letter,i); p = stpcpy(p,num); } } } if( p ) p = stpcpy(p, "\n" ); return list; } static void i18n_init(void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file (PACKAGE_GT, "Software\\GNU\\GnuPG"); #else #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); #endif #endif } static void wrong_args( const char *text) { fputs(_("usage: gpg [options] "),stderr); fputs(text,stderr); putc('\n',stderr); g10_exit(2); } static char * make_username( const char *string ) { char *p; if( utf8_strings ) p = xstrdup(string); else p = native_to_utf8( string ); return p; } /* Setup the debugging. With a LEVEL of NULL only the active debug flags are propagated to the subsystems. With LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. */ static void set_debug (const char *level) { if (!level) ; else if (!strcmp (level, "none")) opt.debug = 0; else if (!strcmp (level, "basic")) opt.debug = DBG_MEMSTAT_VALUE; else if (!strcmp (level, "advanced")) opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE; else if (!strcmp (level, "expert")) opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE |DBG_CACHE_VALUE|DBG_FILTER_VALUE|DBG_PACKET_VALUE); else if (!strcmp (level, "guru")) opt.debug = ~0; else { log_error (_("invalid debug-level `%s' given\n"), level); g10_exit (2); } if (opt.debug & DBG_MEMORY_VALUE ) memory_debug_mode = 1; if (opt.debug & DBG_MEMSTAT_VALUE ) memory_stat_debug_mode = 1; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CIPHER_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); if (opt.debug & DBG_IOBUF_VALUE ) iobuf_debug_mode = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } /* We need the home directory also in some other directories, so make sure that both variables are always in sync. */ static void set_homedir (const char *dir) { if (!dir) dir = ""; opt.homedir = dir; } /* We set the screen dimensions for UI purposes. Do not allow screens smaller than 80x24 for the sake of simplicity. */ static void set_screen_dimensions(void) { #ifndef HAVE_W32_SYSTEM char *str; str=getenv("COLUMNS"); if(str) opt.screen_columns=atoi(str); str=getenv("LINES"); if(str) opt.screen_lines=atoi(str); #endif if(opt.screen_columns<80 || opt.screen_columns>255) opt.screen_columns=80; if(opt.screen_lines<24 || opt.screen_lines>255) opt.screen_lines=24; } /* Helper to open a file FNAME either for reading or writing to be used with --status-file etc functions. Not generally useful but it avoids the riscos specific functions and well some Windows people might like it too. Prints an error message and returns -1 on error. On success the file descriptor is returned. */ static int open_info_file (const char *fname, int for_write) { #ifdef __riscos__ return riscos_fdopenfile (fname, for_write); #elif defined (ENABLE_SELINUX_HACKS) /* We can't allow these even when testing for a secured filename because files to be secured might not yet been secured. This is similar to the option file but in that case it is unlikely that sensitive information may be retrieved by means of error messages. */ return -1; #else int fd; /* if (is_secured_filename (fname)) */ /* { */ /* fd = -1; */ /* errno = EPERM; */ /* } */ /* else */ /* { */ do { if (for_write) fd = open (fname, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); else fd = open (fname, O_RDONLY | MY_O_BINARY); } while (fd == -1 && errno == EINTR); /* } */ if ( fd == -1) log_error ( for_write? _("can't create `%s': %s\n") : _("can't open `%s': %s\n"), fname, strerror(errno)); return fd; #endif } static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ) { enum cmd_and_opt_values cmd = *ret_cmd; if( !cmd || cmd == new_cmd ) cmd = new_cmd; else if( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if( cmd == aSign && new_cmd == aSym ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aSign ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aEncr ) cmd = aEncrSym; else if( cmd == aEncr && new_cmd == aSym ) cmd = aEncrSym; else if( cmd == aKMode && new_cmd == aSym ) cmd = aKModeC; else if (cmd == aSignEncr && new_cmd == aSym) cmd = aSignEncrSym; else if (cmd == aSignSym && new_cmd == aEncr) cmd = aSignEncrSym; else if (cmd == aEncrSym && new_cmd == aSign) cmd = aSignEncrSym; else if( ( cmd == aSign && new_cmd == aClearsign ) || ( cmd == aClearsign && new_cmd == aSign ) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); g10_exit(2); } *ret_cmd = cmd; } static void add_group(char *string) { char *name,*value; struct groupitem *item; /* Break off the group name */ name=strsep(&string,"="); if(string==NULL) { log_error(_("no = sign found in group definition `%s'\n"),name); return; } trim_trailing_ws(name,strlen(name)); /* Does this group already exist? */ for(item=opt.grouplist;item;item=item->next) if(strcasecmp(item->name,name)==0) break; if(!item) { item=xmalloc(sizeof(struct groupitem)); item->name=name; item->next=opt.grouplist; item->values=NULL; opt.grouplist=item; } /* Break apart the values */ while ((value= strsep(&string," \t"))) { if (*value) add_to_strlist2(&item->values,value,utf8_strings); } } static void rm_group(char *name) { struct groupitem *item,*last=NULL; trim_trailing_ws(name,strlen(name)); for(item=opt.grouplist;item;last=item,item=item->next) { if(strcasecmp(item->name,name)==0) { if(last) last->next=item->next; else opt.grouplist=item->next; free_strlist(item->values); xfree(item); break; } } } /* We need to check three things. 0) The homedir. It must be x00, a directory, and owned by the user. 1) The options/gpg.conf file. Okay unless it or its containing directory is group or other writable or not owned by us. Disable exec in this case. 2) Extensions. Same as #1. Returns true if the item is unsafe. */ static int check_permissions(const char *path,int item) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) static int homedir_cache=-1; char *tmppath,*dir; struct stat statbuf,dirbuf; int homedir=0,ret=0,checkonly=0; int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0; if(opt.no_perm_warn) return 0; assert(item==0 || item==1 || item==2); /* extensions may attach a path */ if(item==2 && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(GNUPG_LIBDIR,path,NULL); } else tmppath=xstrdup(path); /* If the item is located in the homedir, but isn't the homedir, don't continue if we already checked the homedir itself. This is to avoid user confusion with an extra options file warning which could be rectified if the homedir itself had proper permissions. */ if(item!=0 && homedir_cache>-1 && ascii_strncasecmp(opt.homedir,tmppath,strlen(opt.homedir))==0) { ret=homedir_cache; goto end; } /* It's okay if the file or directory doesn't exist */ if(stat(tmppath,&statbuf)!=0) { ret=0; goto end; } /* Now check the enclosing directory. Theoretically, we could walk this test up to the root directory /, but for the sake of sanity, I'm stopping at one level down. */ dir=make_dirname(tmppath); if(stat(dir,&dirbuf)!=0 || !S_ISDIR(dirbuf.st_mode)) { /* Weird error */ ret=1; goto end; } xfree(dir); /* Assume failure */ ret=1; if(item==0) { /* The homedir must be x00, a directory, and owned by the user. */ if(S_ISDIR(statbuf.st_mode)) { if(statbuf.st_uid==getuid()) { if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=1; } else own=1; homedir_cache=ret; } } else if(item==1 || item==2) { /* The options or extension file. Okay unless it or its containing directory is group or other writable or not owned by us or root. */ if(S_ISREG(statbuf.st_mode)) { if(statbuf.st_uid==getuid() || statbuf.st_uid==0) { if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0) { /* it's not writable, so make sure the enclosing directory is also not writable */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0) ret=0; else enc_dir_perm=1; } else enc_dir_own=1; } else { /* it's writable, so the enclosing directory had better not let people get to it. */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=enc_dir_perm=1; /* unclear which one to fix! */ } else enc_dir_own=1; } } else own=1; } } else BUG(); if(!checkonly) { if(own) { if(item==0) log_info(_("WARNING: unsafe ownership on" " homedir `%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe ownership on" " configuration file `%s'\n"),tmppath); else log_info(_("WARNING: unsafe ownership on" " extension `%s'\n"),tmppath); } if(perm) { if(item==0) log_info(_("WARNING: unsafe permissions on" " homedir `%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe permissions on" " configuration file `%s'\n"),tmppath); else log_info(_("WARNING: unsafe permissions on" " extension `%s'\n"),tmppath); } if(enc_dir_own) { if(item==0) log_info(_("WARNING: unsafe enclosing directory ownership on" " homedir `%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory ownership on" " configuration file `%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory ownership on" " extension `%s'\n"),tmppath); } if(enc_dir_perm) { if(item==0) log_info(_("WARNING: unsafe enclosing directory permissions on" " homedir `%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory permissions on" " configuration file `%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory permissions on" " extension `%s'\n"),tmppath); } } end: xfree(tmppath); if(homedir) homedir_cache=ret; return ret; #endif /* HAVE_STAT && !HAVE_DOSISH_SYSTEM */ return 0; } static void print_algo_numbers(int (*checker)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else printf(";"); printf("%d",i); } } } /* In the future, we can do all sorts of interesting configuration output here. For now, just give "group" as the Enigmail folks need it, and pubkey, cipher, hash, and compress as they may be useful for frontends. */ static void list_config(char *items) { int show_all=(items==NULL); char *name=NULL; if(!opt.with_colons) return; while(show_all || (name=strsep(&items," "))) { int any=0; if(show_all || ascii_strcasecmp(name,"group")==0) { struct groupitem *iter; for(iter=opt.grouplist;iter;iter=iter->next) { STRLIST sl; printf("cfg:group:"); print_string(stdout,iter->name,strlen(iter->name),':'); printf(":"); for(sl=iter->values;sl;sl=sl->next) { print_sanitized_string2 (stdout, sl->d, ':',';'); if(sl->next) printf(";"); } printf("\n"); } any=1; } if(show_all || ascii_strcasecmp(name,"version")==0) { printf("cfg:version:"); print_string(stdout,VERSION,strlen(VERSION),':'); printf("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkey")==0) { printf("cfg:pubkey:"); print_algo_numbers (openpgp_pk_test_algo); printf("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"cipher")==0) { printf("cfg:cipher:"); print_algo_numbers(openpgp_cipher_test_algo); printf("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"digest")==0 || ascii_strcasecmp(name,"hash")==0) { printf("cfg:digest:"); print_algo_numbers(openpgp_md_test_algo); printf("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"compress")==0) { printf("cfg:compress:"); print_algo_numbers(check_compress_algo); printf("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"ccid-reader-id")==0) { #if defined(ENABLE_CARD_SUPPORT) && defined(HAVE_LIBUSB) char *p, *p2, *list = ccid_get_reader_list (); for (p=list; p && (p2 = strchr (p, '\n')); p = p2+1) { *p2 = 0; printf("cfg:ccid-reader-id:%s\n", p); } free (list); #endif any=1; } if(show_all) break; if(!any) log_error(_("unknown configuration item `%s'\n"),name); } } /* List options and default values in the GPG Conf format. This is a new tool distributed with gnupg 1.9.x but we also want some limited support in older gpg versions. The output is the name of the configuration file and a list of options available for editing by gpgconf. */ static void gpgconf_list (const char *configfile) { /* The following definitions are taken from gnupg/tools/gpgconf-comp.c. */ #define GC_OPT_FLAG_NONE 0UL #define GC_OPT_FLAG_DEFAULT (1UL << 4) printf ("gpgconf-gpg.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT,configfile?configfile:"/dev/null"); printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE); printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE); printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE); printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE); } static int parse_subpacket_list(char *list) { char *tok; byte subpackets[128],i; int count=0; if(!list) { /* No arguments means all subpackets */ memset(subpackets+1,1,sizeof(subpackets)-1); count=127; } else { memset(subpackets,0,sizeof(subpackets)); /* Merge with earlier copy */ if(opt.show_subpackets) { byte *in; for(in=opt.show_subpackets;*in;in++) { if(*in>127 || *in<1) BUG(); if(!subpackets[*in]) count++; subpackets[*in]=1; } } while((tok=strsep(&list," ,"))) { if(!*tok) continue; i=atoi(tok); if(i>127 || i<1) return 0; if(!subpackets[i]) count++; subpackets[i]=1; } } xfree(opt.show_subpackets); opt.show_subpackets=xmalloc(count+1); opt.show_subpackets[count--]=0; for(i=1;i<128 && count>=0;i++) if(subpackets[i]) opt.show_subpackets[count--]=i; return 1; } static int parse_list_options(char *str) { char *subpackets=""; /* something that isn't NULL */ struct parse_options lopts[]= { {"show-photos",LIST_SHOW_PHOTOS,NULL, N_("display photo IDs during key listings")}, {"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature listings")}, {"show-notations",LIST_SHOW_NOTATIONS,NULL, N_("show all notations during signature listings")}, {"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature listings")}, {"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature listings")}, {"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature listings")}, {"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during key listings")}, {"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in key listings")}, {"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL, N_("show revoked and expired subkeys in key listings")}, {"show-keyring",LIST_SHOW_KEYRING,NULL, N_("show the keyring name in key listings")}, {"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL, N_("show expiration dates during signature listings")}, {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, NULL}, {NULL,0,NULL,NULL} }; /* C99 allows for non-constant initializers, but we'd like to compile everywhere, so fill in the show-sig-subpackets argument here. Note that if the parse_options array changes, we'll have to change the subscript here. */ lopts[12].value=&subpackets; if(parse_options(str,&opt.list_options,lopts,1)) { if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS) { /* Unset so users can pass multiple lists in. */ opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS; if(!parse_subpacket_list(subpackets)) return 0; } else if(subpackets==NULL && opt.show_subpackets) { /* User did 'no-show-subpackets' */ xfree(opt.show_subpackets); opt.show_subpackets=NULL; } return 1; } else return 0; } /* Collapses argc/argv into a single string that must be freed */ static char * collapse_args(int argc,char *argv[]) { char *str=NULL; int i,first=1,len=0; for(i=0;iflags=2; break; case oShowKeyring: deprecated_warning(configname,configlineno,"--show-keyring", "--list-options ","show-keyring"); opt.list_options|=LIST_SHOW_KEYRING; break; case oDebug: opt.debug |= pargs.r.ret_ulong; break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oStatusFD: set_status_fd( iobuf_translate_file_handle (pargs.r.ret_int, 1) ); break; case oStatusFile: set_status_fd ( open_info_file (pargs.r.ret_str, 1) ); break; case oAttributeFD: set_attrib_fd(iobuf_translate_file_handle (pargs.r.ret_int, 1)); break; case oAttributeFile: set_attrib_fd ( open_info_file (pargs.r.ret_str, 1) ); break; case oLoggerFD: log_set_fd (iobuf_translate_file_handle (pargs.r.ret_int, 1)); break; case oLoggerFile: logfile = pargs.r.ret_str; break; case oWithFingerprint: opt.with_fingerprint = 1; with_fpr=1; /*fall thru*/ case oFingerprint: opt.fingerprint++; break; case oSecretKeyring: append_to_strlist( &sec_nrings, pargs.r.ret_str); 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 oNoArmor: opt.no_armor=1; opt.armor=0; break; case oNoDefKeyring: default_keyring = 0; break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_sigs=0; break; /* Disabled for now: case oQuickRandom: quick_random_gen(1); break;*/ case oEmitVersion: opt.no_version=0; break; case oNoEmitVersion: opt.no_version=1; break; case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break; case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break; case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break; case oTrustDBName: trustdb_name = pargs.r.ret_str; break; case oDefaultKey: opt.def_secret_key = pargs.r.ret_str; break; case oDefRecipient: if( *pargs.r.ret_str ) opt.def_recipient = make_username(pargs.r.ret_str); break; case oDefRecipientSelf: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; case oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */ case oHomedir: break; case oNoBatch: opt.batch = 0; break; case oWithKeyData: opt.with_key_data=1; /* fall thru */ case oWithColons: opt.with_colons=':'; break; case oSkipVerify: opt.skip_verify=1; break; case oCompressKeys: opt.compress_keys = 1; break; case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break; /* There are many programs (like mutt) that call gpg with --always-trust so keep this option around for a long time. */ case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break; case oTrustModel: parse_trust_model(pargs.r.ret_str); break; case oForceOwnertrust: log_info(_("NOTE: %s is not for normal use!\n"), "--force-ownertrust"); opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str); if(opt.force_ownertrust==-1) { log_error("invalid ownertrust `%s'\n",pargs.r.ret_str); opt.force_ownertrust=0; } break; case oLoadExtension: #ifndef __riscos__ #if defined(USE_DYNAMIC_LINKING) || defined(_WIN32) if(check_permissions(pargs.r.ret_str,2)) log_info(_("cipher extension `%s' not loaded due to" " unsafe permissions\n"),pargs.r.ret_str); else register_cipher_extension(orig_argc? *orig_argv:NULL, pargs.r.ret_str); #endif #else /* __riscos__ */ riscos_not_implemented("load-extension"); #endif /* __riscos__ */ break; case oRFC1991: opt.compliance = CO_RFC1991; opt.force_v4_certs = 0; opt.escape_from = 1; break; case oOpenPGP: case oRFC2440: /* TODO: When 2440bis becomes a RFC, set new values for oOpenPGP. */ opt.rfc2440_text=1; opt.compliance = CO_RFC2440; opt.allow_non_selfsigned_uid = 1; opt.allow_freeform_uid = 1; opt.pgp2_workarounds = 0; opt.escape_from = 0; opt.force_v3_sigs = 0; opt.compress_keys = 0; /* not mandated, but we do it */ opt.compress_sigs = 0; /* ditto. */ opt.not_dash_escaped = 0; opt.def_cipher_algo = 0; opt.def_digest_algo = 0; opt.cert_digest_algo = 0; opt.compress_algo = -1; opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_digest_algo = DIGEST_ALGO_SHA1; opt.s2k_cipher_algo = CIPHER_ALGO_3DES; break; case oPGP2: opt.compliance = CO_PGP2; break; case oPGP6: opt.compliance = CO_PGP6; break; case oPGP7: opt.compliance = CO_PGP7; break; case oPGP8: opt.compliance = CO_PGP8; break; case oGnuPG: opt.compliance = CO_GNUPG; break; case oCompressSigs: opt.compress_sigs = 1; break; case oRFC2440Text: opt.rfc2440_text=1; break; case oNoRFC2440Text: opt.rfc2440_text=0; break; case oSetFilename: if(utf8_strings) opt.set_filename = pargs.r.ret_str; else opt.set_filename = native_to_utf8(pargs.r.ret_str); break; case oForYourEyesOnly: eyes_only = 1; break; case oNoForYourEyesOnly: eyes_only = 0; break; case oSetPolicyURL: add_policy_url(pargs.r.ret_str,0); add_policy_url(pargs.r.ret_str,1); break; case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break; case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break; case oShowPolicyURL: deprecated_warning(configname,configlineno,"--show-policy-url", "--list-options ","show-policy-urls"); deprecated_warning(configname,configlineno,"--show-policy-url", "--verify-options ","show-policy-urls"); opt.list_options|=LIST_SHOW_POLICY_URLS; opt.verify_options|=VERIFY_SHOW_POLICY_URLS; break; case oNoShowPolicyURL: deprecated_warning(configname,configlineno,"--no-show-policy-url", "--list-options ","no-show-policy-urls"); deprecated_warning(configname,configlineno,"--no-show-policy-url", "--verify-options ","no-show-policy-urls"); opt.list_options&=~LIST_SHOW_POLICY_URLS; opt.verify_options&=~VERIFY_SHOW_POLICY_URLS; break; case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break; case oUseEmbeddedFilename: opt.flags.use_embedded_filename=1; break; case oNoUseEmbeddedFilename: opt.flags.use_embedded_filename=0; break; case oComment: if(pargs.r.ret_str[0]) append_to_strlist(&opt.comments,pargs.r.ret_str); break; case oDefaultComment: deprecated_warning(configname,configlineno, "--default-comment","--no-comments",""); /* fall through */ case oNoComments: free_strlist(opt.comments); opt.comments=NULL; break; case oThrowKeyids: opt.throw_keyid = 1; break; case oNoThrowKeyids: opt.throw_keyid = 0; break; case oShowPhotos: deprecated_warning(configname,configlineno,"--show-photos", "--list-options ","show-photos"); deprecated_warning(configname,configlineno,"--show-photos", "--verify-options ","show-photos"); opt.list_options|=LIST_SHOW_PHOTOS; opt.verify_options|=VERIFY_SHOW_PHOTOS; break; case oNoShowPhotos: deprecated_warning(configname,configlineno,"--no-show-photos", "--list-options ","no-show-photos"); deprecated_warning(configname,configlineno,"--no-show-photos", "--verify-options ","no-show-photos"); opt.list_options&=~LIST_SHOW_PHOTOS; opt.verify_options&=~VERIFY_SHOW_PHOTOS; break; case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break; case oForceV3Sigs: opt.force_v3_sigs = 1; break; case oNoForceV3Sigs: opt.force_v3_sigs = 0; break; case oForceV4Certs: opt.force_v4_certs = 1; break; case oNoForceV4Certs: opt.force_v4_certs = 0; break; case oForceMDC: opt.force_mdc = 1; break; case oNoForceMDC: opt.force_mdc = 0; break; case oDisableMDC: opt.disable_mdc = 1; break; case oNoDisableMDC: opt.disable_mdc = 0; break; case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break; case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break; case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break; case oSimpleSKChecksum: opt.simple_sk_checksum = 1; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptTo: /* store the recipient in the second list */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = 1; break; case oHiddenEncryptTo: /* store the recipient in the second list */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = 1|2; break; case oRecipient: /* store the recipient */ add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); any_explicit_recipient = 1; break; case oHiddenRecipient: /* store the recipient with a flag */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = 2; any_explicit_recipient = 1; break; case oTextmodeShort: opt.textmode = 2; break; case oTextmode: opt.textmode=1; break; case oNoTextmode: opt.textmode=0; break; case oExpert: opt.expert = 1; break; case oNoExpert: opt.expert = 0; break; case oDefSigExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("`%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_sig_expire=pargs.r.ret_str; } break; case oAskSigExpire: opt.ask_sig_expire = 1; break; case oNoAskSigExpire: opt.ask_sig_expire = 0; break; case oDefCertExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("`%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_cert_expire=pargs.r.ret_str; } break; case oAskCertExpire: opt.ask_cert_expire = 1; break; case oNoAskCertExpire: opt.ask_cert_expire = 0; break; case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break; case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break; case oAskCertLevel: opt.ask_cert_level = 1; break; case oNoAskCertLevel: opt.ask_cert_level = 0; break; case oLocalUser: /* store the local users */ add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings ); break; case oCompress: /* this is the -z command line option */ opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int; break; case oCompressLevel: opt.compress_level = pargs.r.ret_int; break; case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break; case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break; case oPasswd: set_passphrase_from_string(pargs.r.ret_str); break; case oPasswdFD: pwfd = iobuf_translate_file_handle (pargs.r.ret_int, 0); opt.use_agent = 0; break; case oPasswdFile: pwfd = open_info_file (pargs.r.ret_str, 0); break; case oCommandFD: opt.command_fd = iobuf_translate_file_handle (pargs.r.ret_int, 0); break; case oCommandFile: opt.command_fd = open_info_file (pargs.r.ret_str, 0); break; case oCipherAlgo: def_cipher_string = xstrdup(pargs.r.ret_str); break; case oDigestAlgo: def_digest_string = xstrdup(pargs.r.ret_str); break; case oCompressAlgo: /* If it is all digits, stick a Z in front of it for later. This is for backwards compatibility with versions that took the compress algorithm number. */ { char *pt=pargs.r.ret_str; while(*pt) { if (!isascii (*pt) || !isdigit (*pt)) break; pt++; } if(*pt=='\0') { compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2); strcpy(compress_algo_string,"Z"); strcat(compress_algo_string,pargs.r.ret_str); } else compress_algo_string = xstrdup(pargs.r.ret_str); } break; case oCertDigestAlgo: cert_digest_string = xstrdup(pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oRequireSecmem: require_secmem=1; break; case oNoRequireSecmem: require_secmem=0; break; case oNoPermissionWarn: opt.no_perm_warn=1; break; case oNoMDCWarn: opt.no_mdc_warn=1; break; case oDisplayCharset: if( set_native_charset( pargs.r.ret_str ) ) log_error(_("`%s' is not a valid character set\n"), pargs.r.ret_str); break; case oNotDashEscaped: opt.not_dash_escaped = 1; break; case oEscapeFrom: opt.escape_from = 1; break; case oNoEscapeFrom: opt.escape_from = 0; break; case oLockOnce: opt.lock_once = 1; break; case oLockNever: disable_dotlock (); break; case oLockMultiple: #ifndef __riscos__ opt.lock_once = 0; #else /* __riscos__ */ riscos_not_implemented("lock-multiple"); #endif /* __riscos__ */ break; case oKeyServer: { struct keyserver_spec *keyserver; keyserver=parse_keyserver_uri(pargs.r.ret_str,0, configname,configlineno); if(!keyserver) log_error(_("could not parse keyserver URL\n")); else { keyserver->next=opt.keyserver; opt.keyserver=keyserver; } } break; case oKeyServerOptions: if(!parse_keyserver_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid keyserver options\n"), configname,configlineno); else log_error(_("invalid keyserver options\n")); } break; case oImportOptions: if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1)) { if(configname) log_error(_("%s:%d: invalid import options\n"), configname,configlineno); else log_error(_("invalid import options\n")); } break; case oExportOptions: if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1)) { if(configname) log_error(_("%s:%d: invalid export options\n"), configname,configlineno); else log_error(_("invalid export options\n")); } break; case oListOptions: if(!parse_list_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid list options\n"), configname,configlineno); else log_error(_("invalid list options\n")); } break; case oVerifyOptions: { struct parse_options vopts[]= { {"show-photos",VERIFY_SHOW_PHOTOS,NULL, N_("display photo IDs during signature verification")}, {"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature verification")}, {"show-notations",VERIFY_SHOW_NOTATIONS,NULL, N_("show all notations during signature verification")}, {"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature verification")}, {"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature verification")}, {"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature verification")}, {"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during signature verification")}, {"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in signature verification")}, {"pka-lookups",VERIFY_PKA_LOOKUPS,NULL, N_("validate signatures with PKA data")}, {"pka-trust-increase",VERIFY_PKA_TRUST_INCREASE,NULL, N_("elevate the trust of signatures with valid PKA data")}, {NULL,0,NULL,NULL} }; if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1)) { if(configname) log_error(_("%s:%d: invalid verify options\n"), configname,configlineno); else log_error(_("invalid verify options\n")); } } break; case oTempDir: opt.temp_dir=pargs.r.ret_str; break; case oExecPath: if(set_exec_path(pargs.r.ret_str)) log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str); else opt.exec_path_set=1; break; case oSetNotation: add_notation_data( pargs.r.ret_str, 0 ); add_notation_data( pargs.r.ret_str, 1 ); break; case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break; case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break; case oShowNotation: deprecated_warning(configname,configlineno,"--show-notation", "--list-options ","show-notations"); deprecated_warning(configname,configlineno,"--show-notation", "--verify-options ","show-notations"); opt.list_options|=LIST_SHOW_NOTATIONS; opt.verify_options|=VERIFY_SHOW_NOTATIONS; break; case oNoShowNotation: deprecated_warning(configname,configlineno,"--no-show-notation", "--list-options ","no-show-notations"); deprecated_warning(configname,configlineno,"--no-show-notation", "--verify-options ","no-show-notations"); opt.list_options&=~LIST_SHOW_NOTATIONS; opt.verify_options&=~VERIFY_SHOW_NOTATIONS; break; case oUtf8Strings: utf8_strings = 1; break; case oNoUtf8Strings: utf8_strings = 0; break; case oDisableCipherAlgo: { int algo = string_to_cipher_algo (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oNoSigCache: opt.no_sig_cache = 1; break; case oNoSigCreateCheck: opt.no_sig_create_check = 1; break; case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break; case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break; case oAllowFreeformUID: opt.allow_freeform_uid = 1; break; case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break; case oNoLiteral: opt.no_literal = 1; break; case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break; case oHonorHttpProxy: add_to_strlist(&opt.keyserver_options.other,"http-proxy"); deprecated_warning(configname,configlineno, "--honor-http-proxy", "--keyserver-options ","http-proxy"); break; case oFastListMode: opt.fast_list_mode = 1; break; case oFixedListMode: opt.fixed_list_mode = 1; break; case oListOnly: opt.list_only=1; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oIgnoreValidFrom: opt.ignore_valid_from = 1; break; case oIgnoreCrcError: opt.ignore_crc_error = 1; break; case oIgnoreMDCError: opt.ignore_mdc_error = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oAutoKeyRetrieve: case oNoAutoKeyRetrieve: if(pargs.r_opt==oAutoKeyRetrieve) opt.keyserver_options.options|=KEYSERVER_AUTO_KEY_RETRIEVE; else opt.keyserver_options.options&=~KEYSERVER_AUTO_KEY_RETRIEVE; deprecated_warning(configname,configlineno, pargs.r_opt==oAutoKeyRetrieve?"--auto-key-retrieve": "--no-auto-key-retrieve","--keyserver-options ", pargs.r_opt==oAutoKeyRetrieve?"auto-key-retrieve": "no-auto-key-retrieve"); break; case oShowSessionKey: opt.show_session_key = 1; break; case oOverrideSessionKey: opt.override_session_key = pargs.r.ret_str; break; case oMergeOnly: deprecated_warning(configname,configlineno,"--merge-only", "--import-options ","merge-only"); opt.import_options|=IMPORT_MERGE_ONLY; break; case oAllowSecretKeyImport: /* obsolete */ break; case oTryAllSecrets: opt.try_all_secrets = 1; break; case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break; case oEnableSpecialFilenames: iobuf_enable_special_filenames (1); break; case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break; case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break; case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break; case oPreservePermissions: opt.preserve_permissions=1; break; case oDefaultPreferenceList: opt.def_preference_list = pargs.r.ret_str; break; case oPersonalCipherPreferences: pers_cipher_list=pargs.r.ret_str; break; case oPersonalDigestPreferences: pers_digest_list=pargs.r.ret_str; break; case oPersonalCompressPreferences: pers_compress_list=pargs.r.ret_str; break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oDisplay: opt.display = pargs.r.ret_str; break; case oTTYname: opt.ttyname = pargs.r.ret_str; break; case oTTYtype: opt.ttytype = pargs.r.ret_str; break; case oLCctype: opt.lc_ctype = pargs.r.ret_str; break; case oLCmessages: opt.lc_messages = pargs.r.ret_str; break; case oGroup: add_group(pargs.r.ret_str); break; case oUnGroup: rm_group(pargs.r.ret_str); break; case oNoGroups: while(opt.grouplist) { struct groupitem *iter=opt.grouplist; free_strlist(iter->values); opt.grouplist=opt.grouplist->next; xfree(iter); } break; case oStrict: case oNoStrict: /* Not used */ break; case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break; case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break; case oEnableProgressFilter: opt.enable_progress_filter = 1; break; case oMultifile: multifile=1; break; case oKeyidFormat: if(ascii_strcasecmp(pargs.r.ret_str,"short")==0) opt.keyid_format=KF_SHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0) opt.keyid_format=KF_LONG; else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0) opt.keyid_format=KF_0xSHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0) opt.keyid_format=KF_0xLONG; else log_error("unknown keyid-format `%s'\n",pargs.r.ret_str); break; case oExitOnStatusWriteError: opt.exit_on_status_write_error = 1; break; case oLimitCardInsertTries: opt.limit_card_insert_tries = pargs.r.ret_int; break; case oRequireCrossCert: opt.flags.require_cross_cert=1; break; case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break; case oAutoKeyLocate: if(!parse_auto_key_locate(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid auto-key-locate list\n"), configname,configlineno); else log_error(_("invalid auto-key-locate list\n")); } break; case oNoAutoKeyLocate: release_akl(); break; case oAllowMultisigVerification: opt.allow_multisig_verification = 1; break; case oEnableDSA2: opt.flags.dsa2=1; break; case oDisableDSA2: opt.flags.dsa2=0; break; + case oDebugAllowRun: allow_run = 1; break; + case oNoop: break; default : pargs.err = configfp? 1:2; break; } } if( configfp ) { fclose( configfp ); configfp = NULL; /* Remember the first config file name. */ if (!save_configname) save_configname = configname; else xfree(configname); configname = NULL; goto next_pass; } xfree( configname ); configname = NULL; if( log_get_errorcount(0) ) g10_exit(2); /* The command --gpgconf-list is pretty simple and may be called directly after the option parsing. */ if (cmd == aGPGConfList) { gpgconf_list (save_configname); g10_exit (0); } xfree (save_configname); 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 if( !opt.batch ) { const char *s; if((s=strusage(20))) log_info("%s\n",s); if((s=strusage(21))) log_info("%s\n",s); if((s=strusage(22))) log_info("%s\n",s); } #endif + if (!allow_run) + log_fatal ("This version of gpg is not ready for use, use gpg 1.4.x\n"); + /* FIXME: We should use logging to a file only in server mode; however we have not yet implemetyed that. Thus we try to get away with --batch as indication for logging to file required. */ if (logfile && opt.batch) { log_set_file (logfile); log_set_prefix (NULL, 1|2|4); } if (opt.verbose > 2) log_info ("using character set `%s'\n", get_native_charset ()); if( may_coredump && !opt.quiet ) log_info(_("WARNING: program may create a core file!\n")); if (eyes_only) { if (opt.set_filename) log_info(_("WARNING: %s overrides %s\n"), "--for-your-eyes-only","--set-filename"); opt.set_filename="_CONSOLE"; } if (opt.no_literal) { log_info(_("NOTE: %s is not for normal use!\n"), "--no-literal"); if (opt.textmode) log_error(_("%s not allowed with %s!\n"), "--textmode", "--no-literal" ); if (opt.set_filename) log_error(_("%s makes no sense with %s!\n"), eyes_only?"--for-your-eyes-only":"--set-filename", "--no-literal" ); } if (opt.set_filesize) log_info(_("NOTE: %s is not for normal use!\n"), "--set-filesize"); if( opt.batch ) tty_batchmode( 1 ); gcry_control (GCRYCTL_RESUME_SECMEM_WARN); if(require_secmem && !got_secmem) { log_info(_("will not run with insecure memory due to %s\n"), "--require-secmem"); g10_exit(2); } set_debug (debug_level); /* Do these after the switch(), so they can override settings. */ if(PGP2) { int unusable=0; if(cmd==aSign && !detached_sig) { log_info(_("you can only make detached or clear signatures " "while in --pgp2 mode\n")); unusable=1; } else if(cmd==aSignEncr || cmd==aSignSym) { log_info(_("you can't sign and encrypt at the " "same time while in --pgp2 mode\n")); unusable=1; } else if(argc==0 && (cmd==aSign || cmd==aEncr || cmd==aSym)) { log_info(_("you must use files (and not a pipe) when " "working with --pgp2 enabled.\n")); unusable=1; } else if(cmd==aEncr || cmd==aSym) { /* Everything else should work without IDEA (except using a secret key encrypted with IDEA and setting an IDEA preference, but those have their own error messages). */ if (openpgp_cipher_test_algo(CIPHER_ALGO_IDEA)) { log_info(_("encrypting a message in --pgp2 mode requires " "the IDEA cipher\n")); idea_cipher_warn(1); unusable=1; } else if(cmd==aSym) { /* This only sets IDEA for symmetric encryption since it is set via select_algo_from_prefs for pk encryption. */ xfree(def_cipher_string); def_cipher_string = xstrdup("idea"); } /* PGP2 can't handle the output from the textmode filter, so we disable it for anything that could create a literal packet (only encryption and symmetric encryption, since we disable signing above). */ if(!unusable) opt.textmode=0; } if(unusable) compliance_failure(); else { opt.force_v4_certs = 0; opt.escape_from = 1; opt.force_v3_sigs = 1; opt.pgp2_workarounds = 1; opt.ask_sig_expire = 0; opt.ask_cert_expire = 0; xfree(def_digest_string); def_digest_string = xstrdup("md5"); xfree(s2k_digest_string); s2k_digest_string = xstrdup("md5"); opt.compress_algo = COMPRESS_ALGO_ZIP; } } else if(PGP6) { opt.escape_from=1; opt.force_v3_sigs=1; opt.ask_sig_expire=0; } else if(PGP7) { opt.escape_from=1; opt.force_v3_sigs=1; opt.ask_sig_expire=0; } else if(PGP8) { opt.escape_from=1; } if( def_cipher_string ) { opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string); if(opt.def_cipher_algo==0 && (ascii_strcasecmp(def_cipher_string,"idea")==0 || ascii_strcasecmp(def_cipher_string,"s1")==0)) idea_cipher_warn(1); xfree(def_cipher_string); def_cipher_string = NULL; if ( openpgp_cipher_test_algo (opt.def_cipher_algo) ) log_error(_("selected cipher algorithm is invalid\n")); } if( def_digest_string ) { opt.def_digest_algo = string_to_digest_algo (def_digest_string); xfree(def_digest_string); def_digest_string = NULL; if ( openpgp_md_test_algo (opt.def_digest_algo) ) log_error(_("selected digest algorithm is invalid\n")); } if( compress_algo_string ) { opt.compress_algo = string_to_compress_algo(compress_algo_string); xfree(compress_algo_string); compress_algo_string = NULL; if( check_compress_algo(opt.compress_algo) ) log_error(_("selected compression algorithm is invalid\n")); } if( cert_digest_string ) { opt.cert_digest_algo = string_to_digest_algo (cert_digest_string); xfree(cert_digest_string); cert_digest_string = NULL; if (openpgp_md_test_algo(opt.cert_digest_algo)) log_error(_("selected certification digest algorithm is invalid\n")); } if( s2k_cipher_string ) { opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string); xfree(s2k_cipher_string); s2k_cipher_string = NULL; if (openpgp_cipher_test_algo (opt.s2k_cipher_algo)) log_error(_("selected cipher algorithm is invalid\n")); } if( s2k_digest_string ) { opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string); xfree(s2k_digest_string); s2k_digest_string = NULL; if (openpgp_md_test_algo(opt.s2k_digest_algo)) log_error(_("selected digest algorithm is invalid\n")); } if( opt.completes_needed < 1 ) log_error(_("completes-needed must be greater than 0\n")); if( opt.marginals_needed < 2 ) log_error(_("marginals-needed must be greater than 1\n")); if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 ) log_error(_("max-cert-depth must be in the range from 1 to 255\n")); if(opt.def_cert_level<0 || opt.def_cert_level>3) log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n")); if( opt.min_cert_level < 1 || opt.min_cert_level > 3 ) log_error(_("invalid min-cert-level; must be 1, 2, or 3\n")); switch( opt.s2k_mode ) { case 0: log_info(_("NOTE: simple S2K mode (0) is strongly discouraged\n")); break; case 1: case 3: break; default: log_error(_("invalid S2K mode; must be 0, 1 or 3\n")); } /* This isn't actually needed, but does serve to error out if the string is invalid. */ if(opt.def_preference_list && keygen_set_std_prefs(opt.def_preference_list,0)) log_error(_("invalid default preferences\n")); /* We provide defaults for the personal digest list. This is SHA-1. */ if(!pers_digest_list) pers_digest_list="h2"; if(pers_cipher_list && keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM)) log_error(_("invalid personal cipher preferences\n")); if(pers_digest_list && keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH)) log_error(_("invalid personal digest preferences\n")); if(pers_compress_list && keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) log_error(_("invalid personal compress preferences\n")); /* We don't support all possible commands with multifile yet */ if(multifile) { char *cmdname; switch(cmd) { case aSign: cmdname="--sign"; break; case aClearsign: cmdname="--clearsign"; break; case aDetachedSign: cmdname="--detach-sign"; break; case aSym: cmdname="--symmetric"; break; case aEncrSym: cmdname="--symmetric --encrypt"; break; case aStore: cmdname="--store"; break; default: cmdname=NULL; break; } if(cmdname) log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile"); } if( log_get_errorcount(0) ) g10_exit(2); if(opt.compress_level==0) opt.compress_algo=COMPRESS_ALGO_NONE; /* Check our chosen algorithms against the list of legal algorithms. */ if(!GNUPG) { const char *badalg=NULL; preftype_t badtype=PREFTYPE_NONE; if(opt.def_cipher_algo && !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL)) { badalg = gcry_cipher_algo_name (opt.def_cipher_algo); badtype = PREFTYPE_SYM; } else if(opt.def_digest_algo && !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.def_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.cert_digest_algo && !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.cert_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.compress_algo!=-1 && !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL)) { badalg = compress_algo_to_string(opt.compress_algo); badtype = PREFTYPE_ZIP; } if(badalg) { switch(badtype) { case PREFTYPE_SYM: log_info(_("you may not use cipher algorithm `%s'" " while in %s mode\n"), badalg,compliance_option_string()); break; case PREFTYPE_HASH: log_info(_("you may not use digest algorithm `%s'" " while in %s mode\n"), badalg,compliance_option_string()); break; case PREFTYPE_ZIP: log_info(_("you may not use compression algorithm `%s'" " while in %s mode\n"), badalg,compliance_option_string()); break; default: BUG(); } compliance_failure(); } } /* Set the random seed file. */ if( use_random_seed ) { char *p = make_filename(opt.homedir, "random_seed", NULL ); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); if (!access (p, F_OK)) register_secured_file (p); xfree(p); } if( !cmd && opt.fingerprint && !with_fpr ) { set_cmd( &cmd, aListKeys); } if( cmd == aKMode || cmd == aKModeC ) { /* kludge to be compatible to pgp */ if( cmd == aKModeC ) { opt.fingerprint = 1; cmd = aKMode; } opt.list_sigs = 0; if( opt.verbose > 2 ) opt.check_sigs++; if( opt.verbose > 1 ) opt.list_sigs++; opt.verbose = opt.verbose > 1; } /* kludge to let -sat generate a clear text signature */ if( opt.textmode == 2 && !detached_sig && opt.armor && cmd == aSign ) cmd = aClearsign; if( opt.verbose > 1 ) set_packet_list_mode(1); /* Add the keyrings, but not for some special commands and not in case of "-kvv userid keyring". Also avoid adding the secret keyring for a couple of commands to avoid unneeded access in case the secrings are stored on a floppy. We always need to add the keyrings if we are running under SELinux, this is so that the rings are added to the list of secured files. */ if( ALWAYS_ADD_KEYRINGS || (cmd != aDeArmor && cmd != aEnArmor && !(cmd == aKMode && argc == 2 )) ) { if (ALWAYS_ADD_KEYRINGS || (cmd != aCheckKeys && cmd != aListSigs && cmd != aListKeys && cmd != aVerify && cmd != aSym)) { if (!sec_nrings || default_keyring) /* add default secret rings */ keydb_add_resource ("secring" EXTSEP_S "gpg", 4, 1); for (sl = sec_nrings; sl; sl = sl->next) keydb_add_resource ( sl->d, 0, 1 ); } if( !nrings || default_keyring ) /* add default ring */ keydb_add_resource ("pubring" EXTSEP_S "gpg", 4, 0); for(sl = nrings; sl; sl = sl->next ) keydb_add_resource ( sl->d, sl->flags, 0 ); } FREE_STRLIST(nrings); FREE_STRLIST(sec_nrings); if( pwfd != -1 ) /* read the passphrase now. */ read_passphrase_from_fd( pwfd ); fname = argc? *argv : NULL; if(fname && utf8_strings) opt.flags.utf8_filename=1; switch( cmd ) { case aPrimegen: case aPrintMD: case aPrintMDs: case aGenRandom: case aDeArmor: case aEnArmor: case aFixTrustDB: break; case aExportOwnerTrust: rc = setup_trustdb( 0, trustdb_name ); break; case aListTrustDB: rc = setup_trustdb( argc? 1:0, trustdb_name ); break; default: rc = setup_trustdb(1, trustdb_name ); break; } if( rc ) log_error(_("failed to initialize the TrustDB: %s\n"), g10_errstr(rc)); switch (cmd) { case aStore: case aSym: case aSign: case aSignSym: case aClearsign: if (!opt.quiet && any_explicit_recipient) log_info (_("WARNING: recipients (-r) given " "without using public key encryption\n")); break; default: break; } switch( cmd ) { case aStore: /* only store the file */ if( argc > 1 ) wrong_args(_("--store [filename]")); if( (rc = encode_store(fname)) ) log_error ("storing `%s' failed: %s\n", print_fname_stdin(fname),g10_errstr(rc) ); break; case aSym: /* encrypt the given file only with the symmetric cipher */ if( argc > 1 ) wrong_args(_("--symmetric [filename]")); if( (rc = encode_symmetric(fname)) ) log_error (_("symmetric encryption of `%s' failed: %s\n"), print_fname_stdin(fname),g10_errstr(rc) ); break; case aEncr: /* encrypt the given file */ if(multifile) encode_crypt_files(argc, argv, remusr); else { if( argc > 1 ) wrong_args(_("--encrypt [filename]")); if( (rc = encode_crypt(fname,remusr,0)) ) log_error("%s: encryption failed: %s\n", print_fname_stdin(fname), g10_errstr(rc) ); } break; case aEncrSym: /* This works with PGP 8 in the sense that it acts just like a symmetric message. It doesn't work at all with 2 or 6. It might work with 7, but alas, I don't have a copy to test with right now. */ if( argc > 1 ) wrong_args(_("--symmetric --encrypt [filename]")); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --encrypt" " with --s2k-mode 0\n")); else if(PGP2 || PGP6 || PGP7 || RFC1991) log_error(_("you cannot use --symmetric --encrypt" " while in %s mode\n"),compliance_option_string()); else { if( (rc = encode_crypt(fname,remusr,1)) ) log_error("%s: encryption failed: %s\n", print_fname_stdin(fname), g10_errstr(rc) ); } break; case aSign: /* sign the given file */ sl = NULL; if( detached_sig ) { /* sign all files */ for( ; argc; argc--, argv++ ) add_to_strlist( &sl, *argv ); } else { if( argc > 1 ) wrong_args(_("--sign [filename]")); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } } if( (rc = sign_file( sl, detached_sig, locusr, 0, NULL, NULL)) ) log_error("signing failed: %s\n", g10_errstr(rc) ); free_strlist(sl); break; case aSignEncr: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args(_("--sign --encrypt [filename]")); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if( (rc = sign_file(sl, detached_sig, locusr, 1, remusr, NULL)) ) log_error("%s: sign+encrypt failed: %s\n", print_fname_stdin(fname), g10_errstr(rc) ); free_strlist(sl); break; case aSignEncrSym: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args(_("--symmetric --sign --encrypt [filename]")); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --sign --encrypt" " with --s2k-mode 0\n")); else if(PGP2 || PGP6 || PGP7 || RFC1991) log_error(_("you cannot use --symmetric --sign --encrypt" " while in %s mode\n"),compliance_option_string()); else { if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if( (rc = sign_file(sl, detached_sig, locusr, 2, remusr, NULL)) ) log_error("%s: symmetric+sign+encrypt failed: %s\n", print_fname_stdin(fname), g10_errstr(rc) ); free_strlist(sl); } break; case aSignSym: /* sign and conventionally encrypt the given file */ if (argc > 1) wrong_args(_("--sign --symmetric [filename]")); rc = sign_symencrypt_file (fname, locusr); if (rc) log_error("%s: sign+symmetric failed: %s\n", print_fname_stdin(fname), g10_errstr(rc) ); break; case aClearsign: /* make a clearsig */ if( argc > 1 ) wrong_args(_("--clearsign [filename]")); if( (rc = clearsign_file(fname, locusr, NULL)) ) log_error("%s: clearsign failed: %s\n", print_fname_stdin(fname), g10_errstr(rc) ); break; case aVerify: if(multifile) { if( (rc = verify_files( argc, argv ) )) log_error("verify files failed: %s\n", g10_errstr(rc) ); } else { if( (rc = verify_signatures( argc, argv ) )) log_error("verify signatures failed: %s\n", g10_errstr(rc) ); } break; case aDecrypt: if(multifile) decrypt_messages(argc, argv); else { if( argc > 1 ) wrong_args(_("--decrypt [filename]")); if( (rc = decrypt_message( fname ) )) log_error("decrypt_message failed: %s\n", g10_errstr(rc) ); } break; case aSignKey: if( argc != 1 ) wrong_args(_("--sign-key user-id")); /* fall through */ case aLSignKey: if( argc != 1 ) wrong_args(_("--lsign-key user-id")); /* fall through */ sl=NULL; if(cmd==aSignKey) append_to_strlist(&sl,"sign"); else if(cmd==aLSignKey) append_to_strlist(&sl,"lsign"); else BUG(); append_to_strlist( &sl, "save" ); username = make_username( fname ); keyedit_menu(fname, locusr, sl, 0, 0 ); xfree(username); free_strlist(sl); break; case aEditKey: /* Edit a key signature */ if( !argc ) wrong_args(_("--edit-key user-id [commands]")); username = make_username( fname ); if( argc > 1 ) { sl = NULL; for( argc--, argv++ ; argc; argc--, argv++ ) append_to_strlist( &sl, *argv ); keyedit_menu( username, locusr, sl, 0, 1 ); free_strlist(sl); } else keyedit_menu(username, locusr, NULL, 0, 1 ); xfree(username); break; case aDeleteKeys: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: sl = NULL; /* I'm adding these in reverse order as add_to_strlist2 reverses them again, and it's easier to understand in the proper order :) */ for( ; argc; argc-- ) add_to_strlist2( &sl, argv[argc-1], utf8_strings ); delete_keys(sl,cmd==aDeleteSecretKeys,cmd==aDeleteSecretAndPublicKeys); free_strlist(sl); break; case aCheckKeys: opt.check_sigs = 1; case aListSigs: opt.list_sigs = 1; case aListKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list( sl ); free_strlist(sl); break; case aListSecretKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); secret_key_list( sl ); free_strlist(sl); break; case aKMode: /* list keyring -- NOTE: This will be removed soon */ if( argc < 2 ) { /* -kv [userid] */ sl = NULL; if (argc && **argv) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list( sl ); free_strlist(sl); } else if( argc == 2 ) { /* -kv userid keyring */ if( access( argv[1], R_OK ) ) { log_error(_("can't open `%s': %s\n"), print_fname_stdin(argv[1]), strerror(errno)); } else { /* add keyring (default keyrings are not registered in this * special case */ keydb_add_resource( argv[1], 0, 0 ); sl = NULL; if (**argv) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list( sl ); free_strlist(sl); } } else wrong_args(_("-k[v][v][v][c] [user-id] [keyring]") ); break; case aKeygen: /* generate a key */ if( opt.batch ) { if( argc > 1 ) wrong_args("--gen-key [parameterfile]"); generate_keypair( argc? *argv : NULL, NULL, NULL ); } else { if( argc ) wrong_args("--gen-key"); generate_keypair(NULL, NULL, NULL); } break; case aFastImport: opt.import_options |= IMPORT_FAST; case aImport: import_keys( argc? argv:NULL, argc, NULL, opt.import_options ); break; /* TODO: There are a number of command that use this same "make strlist, call function, report error, free strlist" pattern. Join them together here and avoid all that duplicated code. */ case aExport: case aSendKeys: case aRecvKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); if( cmd == aSendKeys ) rc=keyserver_export( sl ); else if( cmd == aRecvKeys ) rc=keyserver_import( sl ); else rc=export_pubkeys( sl, opt.export_options ); if(rc) { if(cmd==aSendKeys) log_error(_("keyserver send failed: %s\n"),g10_errstr(rc)); else if(cmd==aRecvKeys) log_error(_("keyserver receive failed: %s\n"),g10_errstr(rc)); else log_error(_("key export failed: %s\n"),g10_errstr(rc)); } free_strlist(sl); break; case aSearchKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc=keyserver_search( sl ); if(rc) log_error(_("keyserver search failed: %s\n"),g10_errstr(rc)); free_strlist(sl); break; case aRefreshKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc=keyserver_refresh(sl); if(rc) log_error(_("keyserver refresh failed: %s\n"),g10_errstr(rc)); free_strlist(sl); break; case aFetchKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc=keyserver_fetch(sl); if(rc) log_error("key fetch failed: %s\n",g10_errstr(rc)); free_strlist(sl); break; case aExportSecret: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); export_seckeys( sl ); free_strlist(sl); break; case aExportSecretSub: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); export_secsubkeys( sl ); free_strlist(sl); break; case aGenRevoke: if( argc != 1 ) wrong_args("--gen-revoke user-id"); username = make_username(*argv); gen_revoke( username ); xfree( username ); break; case aDesigRevoke: if( argc != 1 ) wrong_args("--desig-revoke user-id"); username = make_username(*argv); gen_desig_revoke( username, locusr ); xfree( username ); break; case aDeArmor: if( argc > 1 ) wrong_args("--dearmor [file]"); rc = dearmor_file( argc? *argv: NULL ); if( rc ) log_error(_("dearmoring failed: %s\n"), g10_errstr(rc)); break; case aEnArmor: if( argc > 1 ) wrong_args("--enarmor [file]"); rc = enarmor_file( argc? *argv: NULL ); if( rc ) log_error(_("enarmoring failed: %s\n"), g10_errstr(rc)); break; case aPrimegen: #if 0 /*FIXME*/ { int mode = argc < 2 ? 0 : atoi(*argv); if( mode == 1 && argc == 2 ) { mpi_print( stdout, generate_public_prime( atoi(argv[1]) ), 1); } else if( mode == 2 && argc == 3 ) { mpi_print( stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), NULL,NULL ), 1); } else if( mode == 3 && argc == 3 ) { MPI *factors; mpi_print( stdout, generate_elg_prime( 1, atoi(argv[1]), atoi(argv[2]), NULL,&factors ), 1); putchar('\n'); mpi_print( stdout, factors[0], 1 ); /* print q */ } else if( mode == 4 && argc == 3 ) { MPI g = mpi_alloc(1); mpi_print( stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), g, NULL ), 1); putchar('\n'); mpi_print( stdout, g, 1 ); mpi_free(g); } else wrong_args("--gen-prime mode bits [qbits] "); putchar('\n'); } #endif wrong_args("--gen-prime not yet supported "); break; case aGenRandom: { int level = argc ? atoi(*argv):0; int count = argc > 1 ? atoi(argv[1]): 0; int endless = !count; if( argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0 ) wrong_args("--gen-random 0|1|2 [count]"); while( endless || count ) { byte *p; /* Wee need a multiple of 3, so that in case of armored output we get a correct string. No linefolding is done, as it is best to levae this to other tools */ size_t n = !endless && count < 99? count : 99; p = gcry_random_bytes (n, level); #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(stdout), O_BINARY ); #endif if (opt.armor) { char *tmp = make_radix64_string (p, n); fputs (tmp, stdout); xfree (tmp); if (n%3 == 1) putchar ('='); if (n%3) putchar ('='); } else { fwrite( p, n, 1, stdout ); } xfree(p); if( !endless ) count -= n; } if (opt.armor) putchar ('\n'); } break; case aPrintMD: if( argc < 1) wrong_args("--print-md algo [files]"); { int all_algos = (**argv=='*' && !(*argv)[1]); int algo = all_algos? 0 : gcry_md_map_name (*argv); if( !algo && !all_algos ) log_error(_("invalid hash algorithm `%s'\n"), *argv ); else { argc--; argv++; if( !argc ) print_mds(NULL, algo); else { for(; argc; argc--, argv++ ) print_mds(*argv, algo); } } } break; case aPrintMDs: /* old option */ if( !argc ) print_mds(NULL,0); else { for(; argc; argc--, argv++ ) print_mds(*argv,0); } break; case aListTrustDB: if( !argc ) list_trustdb(NULL); else { for( ; argc; argc--, argv++ ) list_trustdb( *argv ); } break; case aUpdateTrustDB: if( argc ) wrong_args("--update-trustdb"); update_trustdb(); break; case aCheckTrustDB: /* Old versions allowed for arguments - ignore them */ check_trustdb(); break; case aFixTrustDB: log_error("this command is not yet implemented.\n"); log_error("A workaround is to use \"--export-ownertrust\", remove\n"); log_error("the trustdb file and do an \"--import-ownertrust\".\n" ); break; case aListTrustPath: if( !argc ) wrong_args("--list-trust-path "); for( ; argc; argc--, argv++ ) { username = make_username( *argv ); list_trust_path( username ); xfree(username); } break; case aExportOwnerTrust: if( argc ) wrong_args("--export-ownertrust"); export_ownertrust(); break; case aImportOwnerTrust: if( argc > 1 ) wrong_args("--import-ownertrust [file]"); import_ownertrust( argc? *argv:NULL ); break; case aRebuildKeydbCaches: if (argc) wrong_args ("--rebuild-keydb-caches"); keydb_rebuild_caches (1); break; #ifdef ENABLE_CARD_SUPPORT case aCardStatus: if (argc) wrong_args ("--card-status"); card_status (stdout, NULL, 0); break; case aCardEdit: if (argc) { sl = NULL; for (argc--, argv++ ; argc; argc--, argv++) append_to_strlist (&sl, *argv); card_edit (sl); free_strlist (sl); } else card_edit (NULL); break; case aChangePIN: if (!argc) change_pin (0,1); else if (argc == 1) change_pin (atoi (*argv),1); else wrong_args ("--change-pin [no]"); break; #endif /* ENABLE_CARD_SUPPORT*/ case aListConfig: { char *str=collapse_args(argc,argv); list_config(str); xfree(str); } break; case aListPackets: opt.list_packets=2; default: if( argc > 1 ) wrong_args(_("[filename]")); /* Issue some output for the unix newbie */ if( !fname && !opt.outfile && isatty( fileno(stdin) ) && isatty( fileno(stdout) ) && isatty( fileno(stderr) ) ) log_info(_("Go ahead and type your message ...\n")); a = iobuf_open(fname); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; errno = EPERM; } if( !a ) log_error(_("can't open `%s'\n"), print_fname_stdin(fname)); else { if( !opt.no_armor ) { if( use_armor_filter( a ) ) { memset( &afx, 0, sizeof afx); iobuf_push_filter( a, armor_filter, &afx ); } } if( cmd == aListPackets ) { set_packet_list_mode(1); opt.list_packets=1; } rc = proc_packets(NULL, a ); if( rc ) log_error("processing message failed: %s\n", g10_errstr(rc) ); iobuf_close(a); } break; } /* cleanup */ FREE_STRLIST(remusr); FREE_STRLIST(locusr); g10_exit(0); return 8; /*NEVER REACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void g10_exit( int rc ) { #ifdef ENABLE_CARD_SUPPORT card_close (); #endif gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); 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 ); emergency_cleanup (); rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; exit (rc); } /* Pretty-print hex hashes. This assumes at least an 80-character display, but there are a few other similar assumptions in the display code. */ static void print_hex( gcry_md_hd_t md, int algo, const char *fname ) { int i,n,count,indent=0; const byte *p; if(fname) indent=printf("%s: ",fname); if(indent>40) { printf("\n"); indent=0; } if(algo==DIGEST_ALGO_RMD160) indent+=printf("RMD160 = "); else if(algo>0) indent+=printf("%6s = ", gcry_md_algo_name (algo)); else algo=abs(algo); count=indent; p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); count += printf ("%02X",*p++); for(i=1;i79) { printf("\n%*s",indent," "); count=indent; } else count+=printf(" "); if(!(i%8)) count+=printf(" "); } else if (n==20) { if(!(i%2)) { if(count+4>79) { printf("\n%*s",indent," "); count=indent; } else count+=printf(" "); } if(!(i%10)) count+=printf(" "); } else { if(!(i%4)) { if(count+8>79) { printf("\n%*s",indent," "); count=indent; } else count+=printf(" "); } } count+=printf("%02X",*p); } printf("\n"); } static void print_hashline( gcry_md_hd_t md, int algo, const char *fname ) { int i, n; const byte *p; if ( fname ) { for (p = fname; *p; p++ ) { if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' ) printf("%%%02X", *p ); else putchar( *p ); } } putchar(':'); printf("%d:", algo ); p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); for(i=0; i < n ; i++, p++ ) printf("%02X", *p ); putchar(':'); putchar('\n'); } static void print_mds( const char *fname, int algo ) { FILE *fp; char buf[1024]; size_t n; gcry_md_hd_t md; if( !fname ) { fp = stdin; #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(fp) , O_BINARY ); #endif } else { fp = fopen( fname, "rb" ); if (fp && is_secured_file (fileno (fp))) { fclose (fp); fp = NULL; errno = EPERM; } } if( !fp ) { log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); return; } gcry_md_open (&md, 0, 0); if( algo ) gcry_md_enable (md, algo); else { gcry_md_enable (md, GCRY_MD_MD5); gcry_md_enable (md, GCRY_MD_SHA1); gcry_md_enable (md, GCRY_MD_RMD160); #ifdef USE_SHA256 gcry_md_enable (md, DIGEST_ALGO_SHA224); gcry_md_enable (md, GCRY_MD_SHA256); #endif #ifdef USE_SHA512 gcry_md_enable (md, GCRY_MD_SHA384); gcry_md_enable (md, GCRY_MD_SHA512); #endif } while( (n=fread( buf, 1, DIM(buf), fp )) ) gcry_md_write (md, buf, n); if( ferror(fp) ) log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); else { gcry_md_final (md); if ( opt.with_colons ) { if ( algo ) print_hashline( md, algo, fname ); else { print_hashline( md, GCRY_MD_MD5, fname ); print_hashline( md, GCRY_MD_SHA1, fname ); print_hashline( md, GCRY_MD_RMD160, fname ); #ifdef USE_SHA256 if (!gcry_md_test_algo (DIGEST_ALGO_SHA224) print_hashline (md, DIGEST_ALGO_SHA224, fname); print_hashline( md, GCRY_MD_SHA256, fname ); #endif #ifdef USE_SHA512 print_hashline( md, GCRY_MD_SHA384, fname ); print_hashline( md, GCRY_MD_SHA512, fname ); #endif } } else { if( algo ) print_hex(md,-algo,fname); else { print_hex( md, GCRY_MD_MD5, fname ); print_hex( md, GCRY_MD_SHA1, fname ); print_hex( md, GCRY_MD_RMD160, fname ); #ifdef USE_SHA256 if (!gcry_md_test_algo (DIGEST_ALGO_SHA224) print_hex (md, DIGEST_ALGO_SHA224, fname); print_hex( md, GCRY_MD_SHA256, fname ); #endif #ifdef USE_SHA512 print_hex( md, GCRY_MD_SHA384, fname ); print_hex( md, GCRY_MD_SHA512, fname ); #endif } } } gcry_md_close(md); if( fp != stdin ) fclose(fp); } /**************** * Check the supplied name,value string and add it to the notation * data to be used for signatures. which==0 for sig notations, and 1 * for cert notations. */ static void add_notation_data( const char *string, int which ) { struct notation *notation; notation=string_to_notation(string,utf8_strings); if(notation) { if(which) { notation->next=opt.cert_notations; opt.cert_notations=notation; } else { notation->next=opt.sig_notations; opt.sig_notations=notation; } } } static void add_policy_url( const char *string, int which ) { unsigned int i,critical=0; STRLIST sl; if(*string=='!') { string++; critical=1; } for(i=0;iflags |= 1; } static void add_keyserver_url( const char *string, int which ) { unsigned int i,critical=0; STRLIST sl; if(*string=='!') { string++; critical=1; } for(i=0;iflags |= 1; } diff --git a/g10/gpg.h b/g10/gpg.h index 8ef46fdca..100a8e349 100644 --- a/g10/gpg.h +++ b/g10/gpg.h @@ -1,101 +1,102 @@ /* gpg.h - top level include file for gpg etc. * Copyright (C) 2003, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_G10_GPG_H #define GNUPG_G10_GPG_H /* Note, that this file should be the first one after the system header files. This is required to set the error source to the correct value and may be of advantage if we ever have to do special things. */ #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPG #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include /* Number of bits we accept when reading or writing MPIs. */ #define MAX_EXTERN_MPI_BITS 16384 /* The maximum length of a binary fingerprints. */ #define MAX_FINGERPRINT_LEN 20 /* Forward declarations. */ typedef struct kbnode_struct *KBNODE; typedef struct keydb_search_desc KEYDB_SEARCH_DESC; /* Simple wrappers. */ #define g10_errstr(a) gpg_strerror ((a)) /* Mapping of the old erro codes to the gpg-error ones. Fixme: This is just a temporary solution: We need to do all these gpg_error() calls in the code. */ #define G10ERR_BAD_KEY GPG_ERR_BAD_KEY #define G10ERR_BAD_PASS GPG_ERR_BAD_PASS #define G10ERR_BAD_PUBKEY GPG_ERR_BAD_PUBKEY #define G10ERR_BAD_SIGN GPG_ERR_BAD_SIGNATURE #define G10ERR_BAD_URI GPG_ERR_BAD_URI #define G10ERR_CHECKSUM GPG_ERR_CHECKSUM #define G10ERR_CIPHER_ALGO GPG_ERR_CIPHER_ALGO #define G10ERR_CLOSE_FILE GPG_ERR_CLOSE_FILE #define G10ERR_COMPR_ALGO GPG_ERR_COMPR_ALGO #define G10ERR_CREATE_FILE GPG_ERR_CREATE_FILE #define G10ERR_DIGEST_ALGO GPG_ERR_DIGEST_ALGO #define G10ERR_FILE_EXISTS GPG_ERR_EEXIST #define G10ERR_GENERAL GPG_ERR_GENERAL #define G10ERR_INV_ARG GPG_ERR_INV_ARG #define G10ERR_INV_KEYRING GPG_ERR_INV_KEYRING #define G10ERR_INV_USER_ID GPG_ERR_INV_USER_ID #define G10ERR_INVALID_ARMOR GPG_ERR_INV_ARMOR #define G10ERR_INVALID_PACKET GPG_ERR_INV_PACKET #define G10ERR_KEYRING_OPEN GPG_ERR_KEYRING_OPEN #define G10ERR_KEYSERVER GPG_ERR_KEYSERVER #define G10ERR_NO_DATA GPG_ERR_NO_DATA #define G10ERR_NO_PUBKEY GPG_ERR_NO_PUBKEY #define G10ERR_NO_SECKEY GPG_ERR_NO_SECKEY #define G10ERR_NO_USER_ID GPG_ERR_NO_USER_ID #define G10ERR_NOT_PROCESSED GPG_ERR_NOT_PROCESSED #define G10ERR_OPEN_FILE GPG_ERR_OPEN_FILE #define G10ERR_PASSPHRASE GPG_ERR_PASSPHRASE #define G10ERR_PUBKEY_ALGO GPG_ERR_PUBKEY_ALGO #define G10ERR_READ_FILE GPG_ERR_READ_FILE #define G10ERR_RENAME_FILE GPG_ERR_RENAME_FILE #define G10ERR_RESOURCE_LIMIT GPG_ERR_RESOURCE_LIMIT #define G10ERR_SIG_CLASS GPG_ERR_SIG_CLASS #define G10ERR_TIME_CONFLICT GPG_ERR_TIME_CONFLICT #define G10ERR_TRUSTDB GPG_ERR_TRUSTDB #define G10ERR_UNEXPECTED GPG_ERR_UNEXPECTED #define G10ERR_UNKNOWN_PACKET GPG_ERR_UNKNOWN_PACKET #define G10ERR_UNSUPPORTED GPG_ERR_UNSUPPORTED #define G10ERR_UNU_PUBKEY GPG_ERR_UNUSABLE_PUBKEY #define G10ERR_UNU_SECKEY GPG_ERR_UNUSABLE_SECKEY #define G10ERR_WRONG_SECKEY GPG_ERR_WRONG_SECKEY #endif /*GNUPG_G10_GPG_H*/ diff --git a/g10/pkglue.c b/g10/pkglue.c index f062d8366..3f9669d27 100644 --- a/g10/pkglue.c +++ b/g10/pkglue.c @@ -1,342 +1,343 @@ /* pkglue.c - public key operations glue code * Copyright (C) 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "gpg.h" #include "util.h" #include "pkglue.h" static gcry_mpi_t mpi_from_sexp (gcry_sexp_t sexp, const char * item) { gcry_sexp_t list; gcry_mpi_t data; list = gcry_sexp_find_token (sexp, item, 0); assert (list); data = gcry_sexp_nth_mpi (list, 1, 0); assert (data); gcry_sexp_release (list); return data; } /**************** * Emulate our old PK interface here - sometime in the future we might * change the internal design to directly fit to libgcrypt. */ int pk_sign (int algo, gcry_mpi_t * data, gcry_mpi_t hash, gcry_mpi_t * skey) { gcry_sexp_t s_sig, s_hash, s_skey; int rc; /* make a sexp from skey */ if (algo == GCRY_PK_DSA) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3], skey[4]); } else if (algo == GCRY_PK_RSA) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", skey[0], skey[1], skey[2], skey[3], skey[4], skey[5]); } else if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(elg(p%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3]); } else return GPG_ERR_PUBKEY_ALGO; if (rc) BUG (); /* put hash into a S-Exp s_hash */ if (gcry_sexp_build (&s_hash, NULL, "%m", hash)) BUG (); rc = gcry_pk_sign (&s_sig, s_hash, s_skey); gcry_sexp_release (s_hash); gcry_sexp_release (s_skey); if (rc) ; else if (algo == GCRY_PK_RSA) data[0] = mpi_from_sexp (s_sig, "s"); else { data[0] = mpi_from_sexp (s_sig, "r"); data[1] = mpi_from_sexp (s_sig, "s"); } gcry_sexp_release (s_sig); return rc; } /**************** * Emulate our old PK interface here - sometime in the future we might * change the internal design to directly fit to libgcrypt. */ int pk_verify (int algo, gcry_mpi_t hash, gcry_mpi_t * data, gcry_mpi_t * pkey) { gcry_sexp_t s_sig, s_hash, s_pkey; int rc; /* make a sexp from pkey */ if (algo == GCRY_PK_DSA) { rc = gcry_sexp_build (&s_pkey, NULL, "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))", pkey[0], pkey[1], pkey[2], pkey[3]); } else if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { rc = gcry_sexp_build (&s_pkey, NULL, "(public-key(elg(p%m)(g%m)(y%m)))", pkey[0], pkey[1], pkey[2]); } else if (algo == GCRY_PK_RSA) { rc = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]); } else return GPG_ERR_PUBKEY_ALGO; if (rc) BUG (); /* put hash into a S-Exp s_hash */ if (gcry_sexp_build (&s_hash, NULL, "%m", hash)) BUG (); /* put data into a S-Exp s_sig */ if (algo == GCRY_PK_DSA) { if (!data[0] || !data[1]) rc = gpg_error (GPG_ERR_BAD_MPI); else rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(dsa(r%m)(s%m)))", data[0], data[1]); } else if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { if (!data[0] || !data[1]) rc = gpg_error (GPG_ERR_BAD_MPI); else rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(elg(r%m)(s%m)))", data[0], data[1]); } else if (algo == GCRY_PK_RSA) { if (!data[0]) rc = gpg_error (GPG_ERR_BAD_MPI); else rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", data[0]); } else BUG (); if (rc) BUG (); rc = gcry_pk_verify (s_sig, s_hash, s_pkey); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return rc; } /**************** * Emulate our old PK interface here - sometime in the future we might * change the internal design to directly fit to libgcrypt. */ int pk_encrypt (int algo, gcry_mpi_t * resarr, gcry_mpi_t data, gcry_mpi_t * pkey) { gcry_sexp_t s_ciph, s_data, s_pkey; int rc; /* make a sexp from pkey */ if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { rc = gcry_sexp_build (&s_pkey, NULL, "(public-key(elg(p%m)(g%m)(y%m)))", pkey[0], pkey[1], pkey[2]); } else if (algo == GCRY_PK_RSA) { rc = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]); } else return GPG_ERR_PUBKEY_ALGO; if (rc) BUG (); /* put the data into a simple list */ if (gcry_sexp_build (&s_data, NULL, "%m", data)) BUG (); /* pass it to libgcrypt */ rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); gcry_sexp_release (s_data); gcry_sexp_release (s_pkey); if (rc) ; else { /* add better error handling or make gnupg use S-Exp directly */ resarr[0] = mpi_from_sexp (s_ciph, "a"); if (algo != GCRY_PK_RSA) resarr[1] = mpi_from_sexp (s_ciph, "b"); } gcry_sexp_release (s_ciph); return rc; } /**************** * Emulate our old PK interface here - sometime in the future we might * change the internal design to directly fit to libgcrypt. */ int pk_decrypt (int algo, gcry_mpi_t * result, gcry_mpi_t * data, gcry_mpi_t * skey) { gcry_sexp_t s_skey, s_data, s_plain; int rc; *result = NULL; /* make a sexp from skey */ if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(elg(p%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3]); } else if (algo == GCRY_PK_RSA) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", skey[0], skey[1], skey[2], skey[3], skey[4], skey[5]); } else return GPG_ERR_PUBKEY_ALGO; if (rc) BUG (); /* put data into a S-Exp s_data */ if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { if (!data[0] || !data[1]) rc = gpg_error (GPG_ERR_BAD_MPI); else rc = gcry_sexp_build (&s_data, NULL, "(enc-val(elg(a%m)(b%m)))", data[0], data[1]); } else if (algo == GCRY_PK_RSA) { if (!data[0]) rc = gpg_error (GPG_ERR_BAD_MPI); else rc = gcry_sexp_build (&s_data, NULL, "(enc-val(rsa(a%m)))", data[0]); } else BUG (); if (rc) BUG (); rc = gcry_pk_decrypt (&s_plain, s_data, s_skey); gcry_sexp_release (s_skey); gcry_sexp_release (s_data); if (rc) return rc; *result = gcry_sexp_nth_mpi (s_plain, 0, 0); gcry_sexp_release (s_plain); if (!*result) return -1; /* oops */ return 0; } /* Check whether SKEY is a suitable secret key. */ int pk_check_secret_key (int algo, gcry_mpi_t *skey) { gcry_sexp_t s_skey; int rc; if (algo == GCRY_PK_DSA) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3], skey[4]); } else if (algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(elg(p%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3]); } else if (algo == GCRY_PK_RSA) { rc = gcry_sexp_build (&s_skey, NULL, "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", skey[0], skey[1], skey[2], skey[3], skey[4], skey[5]); } else return GPG_ERR_PUBKEY_ALGO; if (!rc) { rc = gcry_pk_testkey (s_skey); gcry_sexp_release (s_skey); } return rc; } diff --git a/g10/pkglue.h b/g10/pkglue.h index 43b82785b..866960bfd 100644 --- a/g10/pkglue.h +++ b/g10/pkglue.h @@ -1,35 +1,36 @@ /* pkglue.h - public key operations definitions * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_G10_PKGLUE_H #define GNUPG_G10_PKGLUE_H int pk_sign (int algo, gcry_mpi_t *data, gcry_mpi_t hash, gcry_mpi_t *skey); int pk_verify (int algo, gcry_mpi_t hash, gcry_mpi_t *data, gcry_mpi_t *pkey); int pk_encrypt (int algo, gcry_mpi_t *resarr, gcry_mpi_t data, gcry_mpi_t *pkey); int pk_decrypt (int algo, gcry_mpi_t *result, gcry_mpi_t *data, gcry_mpi_t *skey); int pk_check_secret_key (int algo, gcry_mpi_t *skey); #endif /*GNUPG_G10_PKGLUE_H*/ diff --git a/include/_regex.h b/include/_regex.h index fac441dc6..ddd002484 100644 --- a/include/_regex.h +++ b/include/_regex.h @@ -1,574 +1,574 @@ /* Definitions for data structures and routines for the regular expression library. Copyright (C) 1985,1989-93,1995-98,2000,2001,2002 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307 USA. */ + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. */ #ifndef _REGEX_H #define _REGEX_H 1 /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #endif /* POSIX says that must be included (by the caller) before . */ #if !defined _POSIX_C_SOURCE && !defined _POSIX_SOURCE && defined VMS /* VMS doesn't have `size_t' in , even though POSIX says it should be there. */ # include #endif /* The following two types have to be signed and unsigned integer type wide enough to hold a value of a pointer. For most ANSI compilers ptrdiff_t and size_t should be likely OK. Still size of these two types is 2 for Microsoft C. Ugh... */ typedef long int s_reg_t; typedef unsigned long int active_reg_t; /* The following bits are used to determine the regexp syntax we recognize. The set/not-set meanings are chosen so that Emacs syntax remains the value 0. The bits are given in alphabetical order, and the definitions shifted by one from the previous bit; thus, when we add or remove a bit, only one other definition need change. */ typedef unsigned long int reg_syntax_t; /* If this bit is not set, then \ inside a bracket expression is literal. If set, then such a \ quotes the following character. */ #define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) /* If this bit is not set, then + and ? are operators, and \+ and \? are literals. If set, then \+ and \? are operators and + and ? are literals. */ #define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) /* If this bit is set, then character classes are supported. They are: [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. If not set, then character classes are not supported. */ #define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) /* If this bit is set, then ^ and $ are always anchors (outside bracket expressions, of course). If this bit is not set, then it depends: ^ is an anchor if it is at the beginning of a regular expression or after an open-group or an alternation operator; $ is an anchor if it is at the end of a regular expression, or before a close-group or an alternation operator. This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because POSIX draft 11.2 says that * etc. in leading positions is undefined. We already implemented a previous draft which made those constructs invalid, though, so we haven't changed the code back. */ #define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) /* If this bit is set, then special characters are always special regardless of where they are in the pattern. If this bit is not set, then special characters are special only in some contexts; otherwise they are ordinary. Specifically, * + ? and intervals are only special when not after the beginning, open-group, or alternation operator. */ #define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) /* If this bit is set, then *, +, ?, and { cannot be first in an re or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) /* If this bit is set, then . matches newline. If not set, then it doesn't. */ #define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) /* If this bit is set, then . doesn't match NUL. If not set, then it does. */ #define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) /* If this bit is set, nonmatching lists [^...] do not match newline. If not set, they do. */ #define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) /* If this bit is set, either \{...\} or {...} defines an interval, depending on RE_NO_BK_BRACES. If not set, \{, \}, {, and } are literals. */ #define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) /* If this bit is set, +, ? and | aren't recognized as operators. If not set, they are. */ #define RE_LIMITED_OPS (RE_INTERVALS << 1) /* If this bit is set, newline is an alternation operator. If not set, newline is literal. */ #define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) /* If this bit is set, then `{...}' defines an interval, and \{ and \} are literals. If not set, then `\{...\}' defines an interval. */ #define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) /* If this bit is set, (...) defines a group, and \( and \) are literals. If not set, \(...\) defines a group, and ( and ) are literals. */ #define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) /* If this bit is set, then \ matches . If not set, then \ is a back-reference. */ #define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) /* If this bit is set, then | is an alternation operator, and \| is literal. If not set, then \| is an alternation operator, and | is literal. */ #define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) /* If this bit is set, then an ending range point collating higher than the starting range point, as in [z-a], is invalid. If not set, then when ending range point collates higher than the starting range point, the range is ignored. */ #define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) /* If this bit is set, then an unmatched ) is ordinary. If not set, then an unmatched ) is invalid. */ #define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) /* If this bit is set, succeed as soon as we match the whole pattern, without further backtracking. */ #define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) /* If this bit is set, do not process the GNU regex operators. If not set, then the GNU regex operators are recognized. */ #define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) /* If this bit is set, turn on internal regex debugging. If not set, and debugging was on, turn it off. This only works if regex.c is compiled -DDEBUG. We define this bit always, so that all that's needed to turn on debugging is to recompile regex.c; the calling code can always have this bit set, and it won't affect anything in the normal case. */ #define RE_DEBUG (RE_NO_GNU_OPS << 1) /* If this bit is set, a syntactically invalid interval is treated as a string of ordinary characters. For example, the ERE 'a{1' is treated as 'a\{1'. */ #define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) /* This global variable defines the particular regexp syntax to use (for some interfaces). When a regexp is compiled, the syntax used is stored in the pattern buffer, so changing this does not affect already-compiled regexps. */ extern reg_syntax_t re_syntax_options; /* Define combinations of the above bits for the standard possibilities. (The [[[ comments delimit what gets put into the Texinfo file, so don't delete them!) */ /* [[[begin syntaxes]]] */ #define RE_SYNTAX_EMACS 0 #define RE_SYNTAX_AWK \ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) #define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS \ | RE_CONTEXT_INVALID_OPS )) #define RE_SYNTAX_POSIX_AWK \ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INTERVALS | RE_NO_GNU_OPS) #define RE_SYNTAX_GREP \ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ | RE_NEWLINE_ALT) #define RE_SYNTAX_EGREP \ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ | RE_NO_BK_VBAR) #define RE_SYNTAX_POSIX_EGREP \ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ | RE_INVALID_INTERVAL_ORD) /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ #define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC #define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC /* Syntax bits common to both basic and extended POSIX regex syntax. */ #define _RE_SYNTAX_POSIX_COMMON \ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ | RE_INTERVALS | RE_NO_EMPTY_RANGES) #define RE_SYNTAX_POSIX_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM) /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this isn't minimal, since other operators, such as \`, aren't disabled. */ #define RE_SYNTAX_POSIX_MINIMAL_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) #define RE_SYNTAX_POSIX_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is removed and RE_NO_BK_REFS is added. */ #define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) /* [[[end syntaxes]]] */ /* Maximum number of duplicates an interval can allow. Some systems (erroneously) define this in other header files, but we want our value, so remove any previous define. */ #ifdef RE_DUP_MAX # undef RE_DUP_MAX #endif /* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ #define RE_DUP_MAX (0x7fff) /* POSIX `cflags' bits (i.e., information for `regcomp'). */ /* If this bit is set, then use extended regular expression syntax. If not set, then use basic regular expression syntax. */ #define REG_EXTENDED 1 /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define REG_ICASE (REG_EXTENDED << 1) /* If this bit is set, then anchors do not match at newline characters in the string. If not set, then anchors do match at newlines. */ #define REG_NEWLINE (REG_ICASE << 1) /* If this bit is set, then report only success or fail in regexec. If not set, then returns differ between not matching and errors. */ #define REG_NOSUB (REG_NEWLINE << 1) /* POSIX `eflags' bits (i.e., information for regexec). */ /* If this bit is set, then the beginning-of-line operator doesn't match the beginning of the string (presumably because it's not the beginning of a line). If not set, then the beginning-of-line operator does match the beginning of the string. */ #define REG_NOTBOL 1 /* Like REG_NOTBOL, except for the end-of-line. */ #define REG_NOTEOL (1 << 1) /* If any error codes are removed, changed, or added, update the `re_error_msg' table in regex.c. */ typedef enum { #ifdef _XOPEN_SOURCE REG_ENOSYS = -1, /* This will never happen for this implementation. */ #endif REG_NOERROR = 0, /* Success. */ REG_NOMATCH, /* Didn't find a match (for regexec). */ /* POSIX regcomp return error codes. (In the order listed in the standard.) */ REG_BADPAT, /* Invalid pattern. */ REG_ECOLLATE, /* Not implemented. */ REG_ECTYPE, /* Invalid character class name. */ REG_EESCAPE, /* Trailing backslash. */ REG_ESUBREG, /* Invalid back reference. */ REG_EBRACK, /* Unmatched left bracket. */ REG_EPAREN, /* Parenthesis imbalance. */ REG_EBRACE, /* Unmatched \{. */ REG_BADBR, /* Invalid contents of \{\}. */ REG_ERANGE, /* Invalid range end. */ REG_ESPACE, /* Ran out of memory. */ REG_BADRPT, /* No preceding re for repetition op. */ /* Error codes we've added. */ REG_EEND, /* Premature end. */ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields `buffer', `allocated', `fastmap', `translate', and `no_sub' can be set. After the pattern has been compiled, the `re_nsub' field is available. All other fields are private to the regex routines. */ #ifndef RE_TRANSLATE_TYPE # define RE_TRANSLATE_TYPE char * #endif struct re_pattern_buffer { /* [[[begin pattern_buffer]]] */ /* Space that holds the compiled pattern. It is declared as `unsigned char *' because its elements are sometimes used as array indexes. */ unsigned char *buffer; /* Number of bytes to which `buffer' points. */ unsigned long int allocated; /* Number of bytes actually used in `buffer'. */ unsigned long int used; /* Syntax setting with which the pattern was compiled. */ reg_syntax_t syntax; /* Pointer to a fastmap, if any, otherwise zero. re_search uses the fastmap, if there is one, to skip over impossible starting points for matches. */ char *fastmap; /* Either a translate table to apply to all characters before comparing them, or zero for no translation. The translation is applied to a pattern when it is compiled and to a string when it is matched. */ RE_TRANSLATE_TYPE translate; /* Number of subexpressions found by the compiler. */ size_t re_nsub; /* Zero if this pattern cannot match the empty string, one else. Well, in truth it's used only in `re_search_2', to see whether or not we should use the fastmap, so we don't set this absolutely perfectly; see `re_compile_fastmap' (the `duplicate' case). */ unsigned can_be_null : 1; /* If REGS_UNALLOCATED, allocate space in the `regs' structure for `max (RE_NREGS, re_nsub + 1)' groups. If REGS_REALLOCATE, reallocate space if necessary. If REGS_FIXED, use what's there. */ #define REGS_UNALLOCATED 0 #define REGS_REALLOCATE 1 #define REGS_FIXED 2 unsigned regs_allocated : 2; /* Set to zero when `regex_compile' compiles a pattern; set to one by `re_compile_fastmap' if it updates the fastmap. */ unsigned fastmap_accurate : 1; /* If set, `re_match_2' does not return information about subexpressions. */ unsigned no_sub : 1; /* If set, a beginning-of-line anchor doesn't match at the beginning of the string. */ unsigned not_bol : 1; /* Similarly for an end-of-line anchor. */ unsigned not_eol : 1; /* If true, an anchor at a newline matches. */ unsigned newline_anchor : 1; /* [[[end pattern_buffer]]] */ }; typedef struct re_pattern_buffer regex_t; /* Type for byte offsets within the string. POSIX mandates this. */ typedef int regoff_t; /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { unsigned num_regs; regoff_t *start; regoff_t *end; }; /* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, `re_match_2' returns information about at least this many registers the first time a `regs' structure is passed. */ #ifndef RE_NREGS # define RE_NREGS 30 #endif /* POSIX specification for registers. Aside from the different names than `re_registers', POSIX uses an array of structures, instead of a structure of arrays. */ typedef struct { regoff_t rm_so; /* Byte offset from string's start to substring's start. */ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ } regmatch_t; /* Declarations for routines. */ /* To avoid duplicating every routine declaration -- once with a prototype (if we are ANSI), and once without (if we aren't) -- we use the following macro to declare argument types. This unfortunately clutters up the declarations a bit, but I think it's worth it. */ #if __STDC__ # define _RE_ARGS(args) args #else /* not __STDC__ */ # define _RE_ARGS(args) () #endif /* not __STDC__ */ /* Sets the current default syntax to SYNTAX, and return the old syntax. You can also simply assign to the `re_syntax_options' variable. */ extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); /* Compile the regular expression PATTERN, with length LENGTH and syntax given by the global `re_syntax_options', into the buffer BUFFER. Return NULL if successful, and an error string if not. */ extern const char *re_compile_pattern _RE_ARGS ((const char *pattern, size_t length, struct re_pattern_buffer *buffer)); /* Compile a fastmap for the compiled pattern in BUFFER; used to accelerate searches. Return 0 if successful and -2 if was an internal error. */ extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); /* Search in the string STRING (with length LENGTH) for the pattern compiled into BUFFER. Start searching at position START, for RANGE characters. Return the starting position of the match, -1 for no match, or -2 for an internal error. Also return register information in REGS (if REGS and BUFFER->no_sub are nonzero). */ extern int re_search _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, int range, struct re_registers *regs)); /* Like `re_search', but search in the concatenation of STRING1 and STRING2. Also, stop searching at index START + STOP. */ extern int re_search_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, int range, struct re_registers *regs, int stop)); /* Like `re_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ extern int re_match _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, struct re_registers *regs)); /* Relates to `re_match' as `re_search_2' relates to `re_search'. */ extern int re_match_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, struct re_registers *regs, int stop)); /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated with malloc, and must each be at least `NUM_REGS * sizeof (regoff_t)' bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ extern void re_set_registers _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, unsigned num_regs, regoff_t *starts, regoff_t *ends)); #if defined _REGEX_RE_COMP || defined _LIBC # ifndef _CRAY /* 4.2 bsd compatibility. */ extern char *re_comp _RE_ARGS ((const char *)); extern int re_exec _RE_ARGS ((const char *)); # endif #endif /* GCC 2.95 and later have "__restrict"; C99 compilers have "restrict", and "configure" may have defined "restrict". */ #ifndef __restrict # if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) # if defined restrict || 199901L <= __STDC_VERSION__ # define __restrict restrict # else # define __restrict # endif # endif #endif /* gcc 3.1 and up support the [restrict] syntax. */ #ifndef __restrict_arr # if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) # define __restrict_arr __restrict # else # define __restrict_arr # endif #endif /* POSIX compatibility. */ extern int regcomp _RE_ARGS ((regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags)); extern int regexec _RE_ARGS ((const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags)); extern size_t regerror _RE_ARGS ((int __errcode, const regex_t *__preg, char *__errbuf, size_t __errbuf_size)); extern void regfree _RE_ARGS ((regex_t *__preg)); #ifdef __cplusplus } #endif /* C++ */ #endif /* regex.h */ /* Local variables: make-backup-files: t version-control: t trim-versions-without-asking: nil End: */ diff --git a/include/errors.h b/include/errors.h index ed437fa99..f3269ce5b 100644 --- a/include/errors.h +++ b/include/errors.h @@ -1,101 +1,102 @@ /* errors.h - error code * Copyright (C) 1998, 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_INCLUDE_ERRORS_H #define GNUPG_INCLUDE_ERRORS_H #if 0 #error Remove this file after replacing all error codes with those #error from libgpg-error. The numerical values are identical, though. #endif #if 0 /* Not used anymore. */ #define G10ERR_GENERAL 1 #define G10ERR_UNKNOWN_PACKET 2 #define G10ERR_UNKNOWN_VERSION 3 /* Unknown version (in packet) */ #define G10ERR_PUBKEY_ALGO 4 /* Unknown pubkey algorithm */ #define G10ERR_DIGEST_ALGO 5 /* Unknown digest algorithm */ #define G10ERR_BAD_PUBKEY 6 /* Bad public key */ #define G10ERR_BAD_SECKEY 7 /* Bad secret key */ #define G10ERR_BAD_SIGN 8 /* Bad signature */ #define G10ERR_NO_PUBKEY 9 /* public key not found */ #define G10ERR_CHECKSUM 10 /* checksum error */ #define G10ERR_BAD_PASS 11 /* Bad passphrase */ #define G10ERR_CIPHER_ALGO 12 /* Unknown cipher algorithm */ #define G10ERR_KEYRING_OPEN 13 #define G10ERR_INVALID_PACKET 14 #define G10ERR_INVALID_ARMOR 15 #define G10ERR_NO_USER_ID 16 #define G10ERR_NO_SECKEY 17 /* secret key not available */ #define G10ERR_WRONG_SECKEY 18 /* wrong seckey used */ #define G10ERR_UNSUPPORTED 19 #define G10ERR_BAD_KEY 20 /* bad (session) key */ #define G10ERR_READ_FILE 21 #define G10ERR_WRITE_FILE 22 #define G10ERR_COMPR_ALGO 23 /* Unknown compress algorithm */ #define G10ERR_OPEN_FILE 24 #define G10ERR_CREATE_FILE 25 #define G10ERR_PASSPHRASE 26 /* invalid passphrase */ #define G10ERR_NI_PUBKEY 27 #define G10ERR_NI_CIPHER 28 #define G10ERR_SIG_CLASS 29 #define G10ERR_BAD_MPI 30 #define G10ERR_RESOURCE_LIMIT 31 #define G10ERR_INV_KEYRING 32 #define G10ERR_TRUSTDB 33 /* a problem with the trustdb */ #define G10ERR_BAD_CERT 34 /* bad certicate */ #define G10ERR_INV_USER_ID 35 #define G10ERR_CLOSE_FILE 36 #define G10ERR_RENAME_FILE 37 #define G10ERR_DELETE_FILE 38 #define G10ERR_UNEXPECTED 39 #define G10ERR_TIME_CONFLICT 40 #define G10ERR_WR_PUBKEY_ALGO 41 /* unusabe pubkey algo */ #define G10ERR_FILE_EXISTS 42 #define G10ERR_WEAK_KEY 43 /* NOTE: hardcoded into the cipher modules */ #define G10ERR_WRONG_KEYLEN 44 /* NOTE: hardcoded into the cipher modules */ #define G10ERR_INV_ARG 45 #define G10ERR_BAD_URI 46 /* syntax error in URI */ #define G10ERR_INVALID_URI 47 /* e.g. unsupported scheme */ #define G10ERR_NETWORK 48 /* general network error */ #define G10ERR_UNKNOWN_HOST 49 #define G10ERR_SELFTEST_FAILED 50 #define G10ERR_NOT_ENCRYPTED 51 #define G10ERR_NOT_PROCESSED 52 #define G10ERR_UNU_PUBKEY 53 #define G10ERR_UNU_SECKEY 54 #define G10ERR_KEYSERVER 55 #endif #ifndef HAVE_STRERROR char *strerror( int n ); #endif #endif /*GNUPG_INCLUDE_ERRORS_H*/ diff --git a/include/memory.h b/include/memory.h index 35719da62..2e2f8fdce 100644 --- a/include/memory.h +++ b/include/memory.h @@ -1,95 +1,96 @@ /* memory.h - memory allocation * Copyright (C) 1998, 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef G10_MEMORY_H #define G10_MEMORY_H #error this file should not be used anymore #ifdef M_DEBUG #ifndef STR #define STR(v) #v #endif #ifndef __riscos__ #define M_DBGINFO(a) __FUNCTION__ "["__FILE__ ":" STR(a) "]" #else /* __riscos__ */ #define M_DBGINFO(a) "["__FILE__ ":" STR(a) "]" #endif /* __riscos__ */ #define m_alloc(n) m_debug_alloc((n), M_DBGINFO( __LINE__ ) ) #define m_alloc_clear(n) m_debug_alloc_clear((n), M_DBGINFO(__LINE__) ) #define m_alloc_secure(n) m_debug_alloc((n), M_DBGINFO(__LINE__) ) #define m_alloc_secure_clear(n) m_debug_alloc_secure_clear((n), M_DBGINFO(__LINE__) ) #define m_realloc(n,m) m_debug_realloc((n),(m), M_DBGINFO(__LINE__) ) #define m_free(n) m_debug_free((n), M_DBGINFO(__LINE__) ) #define m_check(n) m_debug_check((n), M_DBGINFO(__LINE__) ) /*#define m_copy(a) m_debug_copy((a), M_DBGINFO(__LINE__) )*/ #define m_strdup(a) m_debug_strdup((a), M_DBGINFO(__LINE__) ) void *m_debug_alloc( size_t n, const char *info ); void *m_debug_alloc_clear( size_t n, const char *info ); void *m_debug_alloc_secure( size_t n, const char *info ); void *m_debug_alloc_secure_clear( size_t n, const char *info ); void *m_debug_realloc( void *a, size_t n, const char *info ); void m_debug_free( void *p, const char *info ); void m_debug_check( const void *a, const char *info ); /*void *m_debug_copy( const void *a, const char *info );*/ char *m_debug_strdup( const char *a, const char *info ); #else void *m_alloc( size_t n ); void *m_alloc_clear( size_t n ); void *m_alloc_secure( size_t n ); void *m_alloc_secure_clear( size_t n ); void *m_realloc( void *a, size_t n ); void m_free( void *p ); void m_check( const void *a ); /*void *m_copy( const void *a );*/ char *m_strdup( const char * a); #endif size_t m_size( const void *a ); void m_print_stats(const char *prefix); /*-- secmem.c --*/ void secmem_init( size_t npool ); void secmem_term( void ); void *secmem_malloc( size_t size ); void *secmem_realloc( void *a, size_t newsize ); void secmem_free( void *a ); int m_is_secure( const void *p ); void secmem_dump_stats(void); void secmem_set_flags( unsigned flags ); unsigned secmem_get_flags(void); #define DBG_MEMORY memory_debug_mode #define DBG_MEMSTAT memory_stat_debug_mode #ifndef EXTERN_UNLESS_MAIN_MODULE #if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE) #define EXTERN_UNLESS_MAIN_MODULE extern #else #define EXTERN_UNLESS_MAIN_MODULE #endif #endif EXTERN_UNLESS_MAIN_MODULE int memory_debug_mode; EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #endif /*G10_MEMORY_H*/ diff --git a/include/mpi.h b/include/mpi.h index b732923a2..7402ef6d3 100644 --- a/include/mpi.h +++ b/include/mpi.h @@ -1,203 +1,204 @@ /* mpi.h - Multi Precision Integers * Copyright (C) 1994, 1996, 1998, 1999, * 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * Note: This code is heavily based on the GNU MP Library. * Actually it's the same code with only minor changes in the * way the data is stored; this is to support the abstraction * of an optional secure memory allocation which may be used * to avoid revealing of sensitive data due to paging etc. * The GNU MP Library itself is published under the LGPL; * however I decided to publish this code under the plain GPL. */ #ifndef G10_MPI_H #define G10_MPI_H #error this file should not be used anymore #include #if 0 #include #include #include "iobuf.h" #include "types.h" #include "memory.h" #if BYTES_PER_MPI_LIMB == SIZEOF_UNSIGNED_INT typedef unsigned int mpi_limb_t; typedef signed int mpi_limb_signed_t; #elif BYTES_PER_MPI_LIMB == SIZEOF_UNSIGNED_LONG typedef unsigned long int mpi_limb_t; typedef signed long int mpi_limb_signed_t; #elif BYTES_PER_MPI_LIMB == SIZEOF_UNSIGNED_LONG_LONG typedef unsigned long long int mpi_limb_t; typedef signed long long int mpi_limb_signed_t; #elif BYTES_PER_MPI_LIMB == SIZEOF_UNSIGNED_SHORT typedef unsigned short int mpi_limb_t; typedef signed short int mpi_limb_signed_t; #else #error BYTES_PER_MPI_LIMB does not match any C type #endif #define BITS_PER_MPI_LIMB (8*BYTES_PER_MPI_LIMB) #ifndef EXTERN_UNLESS_MAIN_MODULE #if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE) #define EXTERN_UNLESS_MAIN_MODULE extern #else #define EXTERN_UNLESS_MAIN_MODULE #endif #endif #define DBG_MPI mpi_debug_mode EXTERN_UNLESS_MAIN_MODULE int mpi_debug_mode; struct gcry_mpi { int alloced; /* array size (# of allocated limbs) */ int nlimbs; /* number of valid limbs */ int nbits; /* the real number of valid bits (info only) */ int sign; /* indicates a negative number */ unsigned flags; /* bit 0: array must be allocated in secure memory space */ /* bit 1: not used */ /* bit 2: the limb is a pointer to some m_alloced data */ mpi_limb_t *d; /* array with the limbs */ }; typedef struct gcry_mpi *MPI; #define MPI_NULL NULL #define mpi_get_nlimbs(a) ((a)->nlimbs) #define mpi_is_neg(a) ((a)->sign) /*-- mpiutil.c --*/ #ifdef M_DEBUG #define mpi_alloc(n) mpi_debug_alloc((n), M_DBGINFO( __LINE__ ) ) #define mpi_alloc_secure(n) mpi_debug_alloc_secure((n), M_DBGINFO( __LINE__ ) ) #define mpi_alloc_like(n) mpi_debug_alloc_like((n), M_DBGINFO( __LINE__ ) ) #define mpi_free(a) mpi_debug_free((a), M_DBGINFO(__LINE__) ) #define mpi_resize(a,b) mpi_debug_resize((a),(b), M_DBGINFO(__LINE__) ) #define mpi_copy(a) mpi_debug_copy((a), M_DBGINFO(__LINE__) ) MPI mpi_debug_alloc( unsigned nlimbs, const char *info ); MPI mpi_debug_alloc_secure( unsigned nlimbs, const char *info ); MPI mpi_debug_alloc_like( MPI a, const char *info ); void mpi_debug_free( MPI a, const char *info ); void mpi_debug_resize( MPI a, unsigned nlimbs, const char *info ); MPI mpi_debug_copy( MPI a, const char *info ); #else MPI mpi_alloc( unsigned nlimbs ); MPI mpi_alloc_secure( unsigned nlimbs ); MPI mpi_alloc_like( MPI a ); void mpi_free( MPI a ); void mpi_resize( MPI a, unsigned nlimbs ); MPI mpi_copy( MPI a ); #endif #define mpi_is_opaque(a) ((a) && ((a)->flags&4)) MPI mpi_set_opaque( MPI a, void *p, int len ); void *mpi_get_opaque( MPI a, int *len ); #define mpi_is_secure(a) ((a) && ((a)->flags&1)) void mpi_set_secure( MPI a ); void mpi_clear( MPI a ); void mpi_set( MPI w, MPI u); void mpi_set_ui( MPI w, ulong u); MPI mpi_alloc_set_ui( unsigned long u); void mpi_m_check( MPI a ); void mpi_swap( MPI a, MPI b); /*-- mpicoder.c --*/ int mpi_write( IOBUF out, MPI a ); #ifdef M_DEBUG #define mpi_read(a,b,c) mpi_debug_read((a),(b),(c), M_DBGINFO( __LINE__ ) ) MPI mpi_debug_read(IOBUF inp, unsigned *nread, int secure, const char *info); #else MPI mpi_read(IOBUF inp, unsigned *nread, int secure); #endif MPI mpi_read_from_buffer(byte *buffer, unsigned *ret_nread, int secure); int mpi_fromstr(MPI val, const char *str); int mpi_print( FILE *fp, MPI a, int mode ); void g10_log_mpidump( const char *text, MPI a ); u32 mpi_get_keyid( MPI a, u32 *keyid ); byte *mpi_get_buffer( MPI a, unsigned *nbytes, int *sign ); byte *mpi_get_secure_buffer( MPI a, unsigned *nbytes, int *sign ); void mpi_set_buffer( MPI a, const byte *buffer, unsigned nbytes, int sign ); #define log_mpidump g10_log_mpidump /*-- mpi-add.c --*/ void mpi_add_ui(MPI w, MPI u, ulong v ); void mpi_add(MPI w, MPI u, MPI v); void mpi_addm(MPI w, MPI u, MPI v, MPI m); void mpi_sub_ui(MPI w, MPI u, ulong v ); void mpi_sub( MPI w, MPI u, MPI v); void mpi_subm( MPI w, MPI u, MPI v, MPI m); /*-- mpi-mul.c --*/ void mpi_mul_ui(MPI w, MPI u, ulong v ); void mpi_mul_2exp( MPI w, MPI u, ulong cnt); void mpi_mul( MPI w, MPI u, MPI v); void mpi_mulm( MPI w, MPI u, MPI v, MPI m); /*-- mpi-div.c --*/ ulong mpi_fdiv_r_ui( MPI rem, MPI dividend, ulong divisor ); void mpi_fdiv_r( MPI rem, MPI dividend, MPI divisor ); void mpi_fdiv_q( MPI quot, MPI dividend, MPI divisor ); void mpi_fdiv_qr( MPI quot, MPI rem, MPI dividend, MPI divisor ); void mpi_tdiv_r( MPI rem, MPI num, MPI den); void mpi_tdiv_qr( MPI quot, MPI rem, MPI num, MPI den); void mpi_tdiv_q_2exp( MPI w, MPI u, unsigned count ); int mpi_divisible_ui(MPI dividend, ulong divisor ); /*-- mpi-gcd.c --*/ int mpi_gcd( MPI g, MPI a, MPI b ); /*-- mpi-pow.c --*/ void mpi_pow( MPI w, MPI u, MPI v); void mpi_powm( MPI res, MPI base, MPI exp, MPI mod); /*-- mpi-mpow.c --*/ void mpi_mulpowm( MPI res, MPI *basearray, MPI *exparray, MPI mod); /*-- mpi-cmp.c --*/ int mpi_cmp_ui( MPI u, ulong v ); int mpi_cmp( MPI u, MPI v ); /*-- mpi-scan.c --*/ int mpi_getbyte( MPI a, unsigned idx ); void mpi_putbyte( MPI a, unsigned idx, int value ); unsigned mpi_trailing_zeros( MPI a ); /*-- mpi-bit.c --*/ void mpi_normalize( MPI a ); unsigned mpi_get_nbits( MPI a ); int mpi_test_bit( MPI a, unsigned n ); void mpi_set_bit( MPI a, unsigned n ); void mpi_set_highbit( MPI a, unsigned n ); void mpi_clear_highbit( MPI a, unsigned n ); void mpi_clear_bit( MPI a, unsigned n ); void mpi_rshift( MPI x, MPI a, unsigned n ); /*-- mpi-inv.c --*/ void mpi_invm( MPI x, MPI u, MPI v ); #endif #endif /*G10_MPI_H*/ diff --git a/include/util.h b/include/util.h index c579c152e..1d6d01366 100644 --- a/include/util.h +++ b/include/util.h @@ -1,300 +1,301 @@ /* util.h * Copyright (C) 1998, 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef G10_UTIL_H #define G10_UTIL_H #error this file should not be used anymore #if 0 /* Dont use it anymore */ #if defined (__MINGW32__) || defined (__CYGWIN32__) #include #endif #include "types.h" #include "errors.h" #include "types.h" #include "mpi.h" typedef struct { int *argc; /* pointer to argc (value subject to change) */ char ***argv; /* pointer to argv (value subject to change) */ unsigned flags; /* Global flags (DO NOT CHANGE) */ int err; /* print error about last option */ /* 1 = warning, 2 = abort */ int r_opt; /* return option */ int r_type; /* type of return value (0 = no argument found)*/ union { int ret_int; long ret_long; ulong ret_ulong; char *ret_str; } r; /* Return values */ struct { int idx; int inarg; int stopped; const char *last; void *aliases; const void *cur_alias; } internal; /* DO NOT CHANGE */ } ARGPARSE_ARGS; typedef struct { int short_opt; const char *long_opt; unsigned flags; const char *description; /* optional option description */ } ARGPARSE_OPTS; /*-- logger.c --*/ void log_set_logfile( const char *name, int fd ); FILE *log_stream(void); void g10_log_print_prefix(const char *text); void log_set_name( const char *name ); const char *log_get_name(void); void log_set_pid( int pid ); int log_get_errorcount( int clear ); void log_inc_errorcount(void); int log_set_strict(int val); void g10_log_hexdump( const char *text, const char *buf, size_t len ); #if defined (__riscos__) \ || (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )) void g10_log_bug( const char *fmt, ... ) __attribute__ ((noreturn, format (printf,1,2))); void g10_log_bug0( const char *, int, const char * ) __attribute__ ((noreturn)); void g10_log_fatal( const char *fmt, ... ) __attribute__ ((noreturn, format (printf,1,2))); void g10_log_error( const char *fmt, ... ) __attribute__ ((format (printf,1,2))); void g10_log_info( const char *fmt, ... ) __attribute__ ((format (printf,1,2))); void g10_log_warning( const char *fmt, ... ) __attribute__ ((format (printf,1,2))); void g10_log_debug( const char *fmt, ... ) __attribute__ ((format (printf,1,2))); void g10_log_fatal_f( const char *fname, const char *fmt, ... ) __attribute__ ((noreturn, format (printf,2,3))); void g10_log_error_f( const char *fname, const char *fmt, ... ) __attribute__ ((format (printf,2,3))); void g10_log_info_f( const char *fname, const char *fmt, ... ) __attribute__ ((format (printf,2,3))); void g10_log_debug_f( const char *fname, const char *fmt, ... ) __attribute__ ((format (printf,2,3))); #ifndef __riscos__ #define BUG() g10_log_bug0( __FILE__ , __LINE__, __FUNCTION__ ) #else #define BUG() g10_log_bug0( __FILE__ , __LINE__, __func__ ) #endif #else void g10_log_bug( const char *fmt, ... ); void g10_log_bug0( const char *, int ); void g10_log_fatal( const char *fmt, ... ); void g10_log_error( const char *fmt, ... ); void g10_log_info( const char *fmt, ... ); void g10_log_warning( const char *fmt, ... ); void g10_log_debug( const char *fmt, ... ); void g10_log_fatal_f( const char *fname, const char *fmt, ... ); void g10_log_error_f( const char *fname, const char *fmt, ... ); void g10_log_info_f( const char *fname, const char *fmt, ... ); void g10_log_debug_f( const char *fname, const char *fmt, ... ); #define BUG() g10_log_bug0( __FILE__ , __LINE__ ) #endif #define log_hexdump g10_log_hexdump #define log_bug g10_log_bug #define log_bug0 g10_log_bug0 #define log_fatal g10_log_fatal #define log_error g10_log_error #define log_info g10_log_info #define log_warning g10_log_warning #define log_debug g10_log_debug #define log_fatal_f g10_log_fatal_f #define log_error_f g10_log_error_f #define log_info_f g10_log_info_f #define log_debug_f g10_log_debug_f /*-- argparse.c --*/ int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); int optfile_parse( FILE *fp, const char *filename, unsigned *lineno, ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); void usage( int level ); const char *default_strusage( int level ); /*-- (main program) --*/ const char *strusage( int level ); /*-- dotlock.c --*/ struct dotlock_handle; typedef struct dotlock_handle *DOTLOCK; void disable_dotlock(void); DOTLOCK create_dotlock( const char *file_to_lock ); int make_dotlock( DOTLOCK h, long timeout ); int release_dotlock( DOTLOCK h ); void remove_lockfiles (void); /*-- fileutil.c --*/ char * make_basename(const char *filepath, const char *inputpath); char * make_dirname(const char *filepath); char *make_filename( const char *first_part, ... ); int compare_filenames( const char *a, const char *b ); const char *print_fname_stdin( const char *s ); const char *print_fname_stdout( const char *s ); int is_file_compressed(const char *s, int *r_status); /*-- miscutil.c --*/ u32 make_timestamp(void); u32 scan_isodatestr( const char *string ); u32 add_days_to_timestamp( u32 stamp, u16 days ); const char *strtimevalue( u32 stamp ); const char *strtimestamp( u32 stamp ); /* GMT */ const char *asctimestamp( u32 stamp ); /* localized */ void print_string( FILE *fp, const byte *p, size_t n, int delim ); void print_utf8_string( FILE *fp, const byte *p, size_t n ); void print_utf8_string2( FILE *fp, const byte *p, size_t n, int delim); char *make_printable_string( const byte *p, size_t n, int delim ); int answer_is_yes_no_default( const char *s, int def_answer ); int answer_is_yes( const char *s ); int answer_is_yes_no_quit( const char *s ); /*-- strgutil.c --*/ #include "../jnlib/strlist.h" const char *memistr( const char *buf, size_t buflen, const char *sub ); const char *ascii_memistr( const char *buf, size_t buflen, const char *sub ); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); unsigned int trim_trailing_chars( byte *line, unsigned int len, const char *trimchars); unsigned int trim_trailing_ws( byte *line, unsigned len ); unsigned int check_trailing_chars( const byte *line, unsigned int len, const char *trimchars ); unsigned int check_trailing_ws( const byte *line, unsigned int len ); int string_count_chr( const char *string, int c ); int set_native_charset( const char *newset ); const char* get_native_charset(void); char *native_to_utf8( const char *string ); char *utf8_to_native( const char *string, size_t length, int delim); int check_utf8_string( const char *string ); int ascii_isupper (int c); int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp( const char *a, const char *b, size_t n); int ascii_memcasecmp( const char *a, const char *b, size_t n); #ifndef HAVE_STPCPY char *stpcpy(char *a,const char *b); #endif #ifndef HAVE_STRLWR char *strlwr(char *a); #endif #ifndef HAVE_STRSEP char *strsep (char **stringp, const char *delim); #endif #ifndef HAVE_STRCASECMP int strcasecmp( const char *, const char *b); #endif #ifndef HAVE_STRNCASECMP int strncasecmp (const char *, const char *b, size_t n); #endif #ifndef HAVE_STRTOUL #define strtoul(a,b,c) ((unsigned long)strtol((a),(b),(c))) #endif #ifndef HAVE_MEMMOVE #define memmove(d, s, n) bcopy((s), (d), (n)) #endif #if defined (__MINGW32__) /*-- w32reg.c --*/ char *read_w32_registry_string( const char *root, const char *dir, const char *name ); int write_w32_registry_string(const char *root, const char *dir, const char *name, const char *value); /*-- strgutil.c --*/ int vasprintf ( char **result, const char *format, va_list args); #endif /**** other missing stuff ****/ #ifndef HAVE_ATEXIT /* For SunOS */ #define atexit(a) (on_exit((a),0)) #endif #ifndef HAVE_RAISE #define raise(a) kill(getpid(), (a)) #endif /******** some macros ************/ #ifndef STR #define STR(v) #v #endif #define STR2(v) STR(v) #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) #define wipememory2(_ptr,_set,_len) do { volatile char *_vptr=(volatile char *)(_ptr); size_t _vlen=(_len); while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } } while(0) #define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) /******* RISC OS stuff ***********/ #ifdef __riscos__ /* needed for strcasecmp() */ #include /* needed for filename munging */ #include /* needed for image file system feature */ #include void riscos_global_defaults(void); #define RISCOS_GLOBAL_STATICS(a) const char *__dynamic_da_name = (a); int riscos_load_module(const char *name, const char * const path[], int fatal); int riscos_get_filetype_from_string(const char *string, int len); int riscos_get_filetype(const char *filename); void riscos_set_filetype_by_number(const char *filename, int type); void riscos_set_filetype_by_mimetype(const char *filename, const char *mimetype); pid_t riscos_getpid(void); int riscos_kill(pid_t pid, int sig); int riscos_access(const char *path, int amode); int riscos_getchar(void); char *riscos_make_basename(const char *filepath, const char *inputpath); int riscos_check_regexp(const char *exp, const char *string, int debug); int riscos_fdopenfile(const char *filename, const int allow_write); void riscos_close_fds(void); int riscos_renamefile(const char *old, const char *new); char *riscos_gstrans(const char *old); void riscos_not_implemented(const char *feature); #ifdef DEBUG void riscos_dump_fdlist(void); void riscos_list_openfiles(void); #endif #ifndef __RISCOS__C__ #define getpid riscos_getpid #define kill(a,b) riscos_kill((a),(b)) #define access(a,b) riscos_access((a),(b)) #endif /* !__RISCOS__C__ */ #endif /* __riscos__ */ #endif #endif /*G10_UTIL_H*/ diff --git a/jnlib/Makefile.am b/jnlib/Makefile.am index 69eac4bf7..5fd48495c 100644 --- a/jnlib/Makefile.am +++ b/jnlib/Makefile.am @@ -1,45 +1,46 @@ # Copyright (C) 1999, 2000, 2001, 2004 Feee Software Soundation, 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in EXTRA_DIST = README AM_CPPFLAGS = -I$(top_srcdir)/intl # We need libgcrypt because libjnlib-config includes gcrypt.h AM_CFLAGS = $(LIBGCRYPT_CFLAGS) noinst_LIBRARIES = libjnlib.a #libjnlib_a_LDFLAGS = libjnlib_a_SOURCES = \ libjnlib-config.h \ stringhelp.c stringhelp.h \ strlist.c strlist.h \ utf8conv.c utf8conv.h \ argparse.c argparse.h \ logging.c logging.h \ dotlock.c dotlock.h \ types.h mischelp.h \ w32-pth.c w32-pth.h \ w32-afunix.c w32-afunix.h # xmalloc.c xmalloc.h diff --git a/jnlib/argparse.c b/jnlib/argparse.c index 980d1186c..15a7c546e 100644 --- a/jnlib/argparse.c +++ b/jnlib/argparse.c @@ -1,999 +1,1001 @@ /* [argparse.c wk 17.06.97] Argument Parser for option handling * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. * - * This file is part of GnuPG. + * 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 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. + * 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 + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "libjnlib-config.h" #include "mischelp.h" #include "stringhelp.h" #include "logging.h" #include "argparse.h" /********************************* * @Summary arg_parse * #include * * typedef struct { * char *argc; pointer to argc (value subject to change) * char ***argv; pointer to argv (value subject to change) * unsigned flags; Global flags (DO NOT CHANGE) * int err; print error about last option * 1 = warning, 2 = abort * int r_opt; return option * int r_type; type of return value (0 = no argument found) * union { * int ret_int; * long ret_long * ulong ret_ulong; * char *ret_str; * } r; Return values * struct { * int idx; * const char *last; * void *aliases; * } internal; DO NOT CHANGE * } ARGPARSE_ARGS; * * typedef struct { * int short_opt; * const char *long_opt; * unsigned flags; * } ARGPARSE_OPTS; * * int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts ); * * @Description * This is my replacement for getopt(). See the example for a typical usage. * Global flags are: * Bit 0 : Do not remove options form argv * Bit 1 : Do not stop at last option but return other args * with r_opt set to -1. * Bit 2 : Assume options and real args are mixed. * Bit 3 : Do not use -- to stop option processing. * Bit 4 : Do not skip the first arg. * Bit 5 : allow usage of long option with only one dash * Bit 6 : ignore --version * all other bits must be set to zero, this value is modified by the * function, so assume this is write only. * Local flags (for each option): * Bit 2-0 : 0 = does not take an argument * 1 = takes int argument * 2 = takes string argument * 3 = takes long argument * 4 = takes ulong argument * Bit 3 : argument is optional (r_type will the be set to 0) * Bit 4 : allow 0x etc. prefixed values. * Bit 7 : this is a command and not an option * You stop the option processing by setting opts to NULL, the function will * then return 0. * @Return Value * Returns the args.r_opt or 0 if ready * r_opt may be -2/-7 to indicate an unknown option/command. * @See Also * ArgExpand * @Notes * You do not need to process the options 'h', '--help' or '--version' * because this function includes standard help processing; but if you * specify '-h', '--help' or '--version' you have to do it yourself. * The option '--' stops argument processing; if bit 1 is set the function * continues to return normal arguments. * To process float args or unsigned args you must use a string args and do * the conversion yourself. * @Example * * ARGPARSE_OPTS opts[] = { * { 'v', "verbose", 0 }, * { 'd', "debug", 0 }, * { 'o', "output", 2 }, * { 'c', "cross-ref", 2|8 }, * { 'm', "my-option", 1|8 }, * { 500, "have-no-short-option-for-this-long-option", 0 }, * {0} }; * ARGPARSE_ARGS pargs = { &argc, &argv, 0 } * * while( ArgParse( &pargs, &opts) ) { * switch( pargs.r_opt ) { * case 'v': opt.verbose++; break; * case 'd': opt.debug++; break; * case 'o': opt.outfile = pargs.r.ret_str; break; * case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; * case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; * case 500: opt.a_long_one++; break * default : pargs.err = 1; break; -- force warning output -- * } * } * if( argc > 1 ) * log_fatal( "Too many args"); * */ typedef struct alias_def_s *ALIAS_DEF; struct alias_def_s { ALIAS_DEF next; char *name; /* malloced buffer with name, \0, value */ const char *value; /* ptr into name */ }; static const char *(*strusage_handler)( int ) = NULL; static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s); static void show_help(ARGPARSE_OPTS *opts, unsigned flags); static void show_version(void); static void initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) { if( !(arg->flags & (1<<15)) ) { /* initialize this instance */ arg->internal.idx = 0; arg->internal.last = NULL; arg->internal.inarg = 0; arg->internal.stopped = 0; arg->internal.aliases = NULL; arg->internal.cur_alias = NULL; arg->err = 0; arg->flags |= 1<<15; /* mark initialized */ if( *arg->argc < 0 ) jnlib_log_bug("Invalid argument for ArgParse\n"); } if( arg->err ) { /* last option was erroneous */ const char *s; if( filename ) { if( arg->r_opt == -6 ) s = "argument not expected\n"; else if( arg->r_opt == -5 ) s = "read error\n"; else if( arg->r_opt == -4 ) s = "keyword too long\n"; else if( arg->r_opt == -3 ) s = "missing argument\n"; else if( arg->r_opt == -7 ) s = "invalid command\n"; else if( arg->r_opt == -10 ) s = "invalid alias definition\n"; else s = "invalid option\n"; jnlib_log_error("%s:%u: %s\n", filename, *lineno, s); } else { s = arg->internal.last? arg->internal.last:"[??]"; if( arg->r_opt == -3 ) jnlib_log_error ("Missing argument for option \"%.50s\"\n", s); else if( arg->r_opt == -6 ) jnlib_log_error ("Option \"%.50s\" does not expect an argument\n", s ); else if( arg->r_opt == -7 ) jnlib_log_error ("Invalid command \"%.50s\"\n", s); else if( arg->r_opt == -8 ) jnlib_log_error ("Option \"%.50s\" is ambiguous\n", s); else if( arg->r_opt == -9 ) jnlib_log_error ("Command \"%.50s\" is ambiguous\n",s ); else jnlib_log_error ("Invalid option \"%.50s\"\n", s); } if( arg->err != 1 ) exit(2); arg->err = 0; } /* clearout the return value union */ arg->r.ret_str = NULL; arg->r.ret_long= 0; } static void store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) { /* TODO: replace this dummy function with a rea one * and fix the probelms IRIX has with (ALIAS_DEV)arg.. * used as lvalue */ #if 0 ALIAS_DEF a = jnlib_xmalloc( sizeof *a ); a->name = name; a->value = value; a->next = (ALIAS_DEF)arg->internal.aliases; (ALIAS_DEF)arg->internal.aliases = a; #endif } /**************** * Get options from a file. * Lines starting with '#' are comment lines. * Syntax is simply a keyword and the argument. * Valid keywords are all keywords from the long_opt list without * the leading dashes. The special keywords "help", "warranty" and "version" * are not valid here. * The special keyword "alias" may be used to store alias definitions, * which are later expanded like long options. * Caller must free returned strings. * If called with FP set to NULL command line args are parse instead. * * Q: Should we allow the syntax * keyword = value * and accept for boolean options a value of 1/0, yes/no or true/false? * Note: Abbreviation of options is here not allowed. */ int optfile_parse( FILE *fp, const char *filename, unsigned *lineno, ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) { int state, i, c; int idx=0; char keyword[100]; char *buffer = NULL; size_t buflen = 0; int inverse=0; int in_alias=0; if( !fp ) /* same as arg_parse() in this case */ return arg_parse( arg, opts ); initialize( arg, filename, lineno ); /* find the next keyword */ state = i = 0; for(;;) { c=getc(fp); if( c == '\n' || c== EOF ) { if( c != EOF ) ++*lineno; if( state == -1 ) break; else if( state == 2 ) { keyword[i] = 0; for(i=0; opts[i].short_opt; i++ ) if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) break; idx = i; arg->r_opt = opts[idx].short_opt; if( inverse ) /* this does not have an effect, hmmm */ arg->r_opt = -arg->r_opt; if( !opts[idx].short_opt ) /* unknown command/option */ arg->r_opt = (opts[idx].flags & 256)? -7:-2; else if( !(opts[idx].flags & 7) ) /* does not take an arg */ arg->r_type = 0; /* okay */ else if( (opts[idx].flags & 8) ) /* argument is optional */ arg->r_type = 0; /* okay */ else /* required argument */ arg->r_opt = -3; /* error */ break; } else if( state == 3 ) { /* no argument found */ if( in_alias ) arg->r_opt = -3; /* error */ else if( !(opts[idx].flags & 7) ) /* does not take an arg */ arg->r_type = 0; /* okay */ else if( (opts[idx].flags & 8) ) /* no optional argument */ arg->r_type = 0; /* okay */ else /* no required argument */ arg->r_opt = -3; /* error */ break; } else if( state == 4 ) { /* have an argument */ if( in_alias ) { if( !buffer ) arg->r_opt = -6; else { char *p; buffer[i] = 0; p = strpbrk( buffer, " \t" ); if( p ) { *p++ = 0; trim_spaces( p ); } if( !p || !*p ) { jnlib_free( buffer ); arg->r_opt = -10; } else { store_alias( arg, buffer, p ); } } } else if( !(opts[idx].flags & 7) ) /* does not take an arg */ arg->r_opt = -6; /* error */ else { char *p; if( !buffer ) { keyword[i] = 0; buffer = jnlib_xstrdup(keyword); } else buffer[i] = 0; trim_spaces( buffer ); p = buffer; if( *p == '"' ) { /* remove quotes */ p++; if( *p && p[strlen(p)-1] == '"' ) p[strlen(p)-1] = 0; } if( !set_opt_arg(arg, opts[idx].flags, p) ) jnlib_free(buffer); } break; } else if( c == EOF ) { if( ferror(fp) ) arg->r_opt = -5; /* read error */ else arg->r_opt = 0; /* eof */ break; } state = 0; i = 0; } else if( state == -1 ) ; /* skip */ else if( !state && isspace(c) ) ; /* skip leading white space */ else if( !state && c == '#' ) state = 1; /* start of a comment */ else if( state == 1 ) ; /* skip comments */ else if( state == 2 && isspace(c) ) { keyword[i] = 0; for(i=0; opts[i].short_opt; i++ ) if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) break; idx = i; arg->r_opt = opts[idx].short_opt; if( !opts[idx].short_opt ) { if( !strcmp( keyword, "alias" ) ) { in_alias = 1; state = 3; } else { arg->r_opt = (opts[idx].flags & 256)? -7:-2; state = -1; /* skip rest of line and leave */ } } else state = 3; } else if( state == 3 ) { /* skip leading spaces of the argument */ if( !isspace(c) ) { i = 0; keyword[i++] = c; state = 4; } } else if( state == 4 ) { /* collect the argument */ if( buffer ) { if( i < buflen-1 ) buffer[i++] = c; else { buflen += 50; buffer = jnlib_xrealloc(buffer, buflen); buffer[i++] = c; } } else if( i < DIM(keyword)-1 ) keyword[i++] = c; else { buflen = DIM(keyword)+50; buffer = jnlib_xmalloc(buflen); memcpy(buffer, keyword, i); buffer[i++] = c; } } else if( i >= DIM(keyword)-1 ) { arg->r_opt = -4; /* keyword to long */ state = -1; /* skip rest of line and leave */ } else { keyword[i++] = c; state = 2; } } return arg->r_opt; } static int find_long_option( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts, const char *keyword ) { int i; size_t n; /* Would be better if we can do a binary search, but it is not possible to reorder our option table because we would mess up our help strings - What we can do is: Build a nice option lookup table wehn this function is first invoked */ if( !*keyword ) return -1; for(i=0; opts[i].short_opt; i++ ) if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) return i; #if 0 { ALIAS_DEF a; /* see whether it is an alias */ for( a = args->internal.aliases; a; a = a->next ) { if( !strcmp( a->name, keyword) ) { /* todo: must parse the alias here */ args->internal.cur_alias = a; return -3; /* alias available */ } } } #endif /* not found, see whether it is an abbreviation */ /* aliases may not be abbreviated */ n = strlen( keyword ); for(i=0; opts[i].short_opt; i++ ) { if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) { int j; for(j=i+1; opts[j].short_opt; j++ ) { if( opts[j].long_opt && !strncmp( opts[j].long_opt, keyword, n ) ) return -2; /* abbreviation is ambiguous */ } return i; } } return -1; } int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) { int idx; int argc; char **argv; char *s, *s2; int i; initialize( arg, NULL, NULL ); argc = *arg->argc; argv = *arg->argv; idx = arg->internal.idx; if( !idx && argc && !(arg->flags & (1<<4)) ) { /* skip the first entry */ argc--; argv++; idx++; } next_one: if( !argc ) { /* no more args */ arg->r_opt = 0; goto leave; /* ready */ } s = *argv; arg->internal.last = s; if( arg->internal.stopped && (arg->flags & (1<<1)) ) { arg->r_opt = -1; /* not an option but a argument */ arg->r_type = 2; arg->r.ret_str = s; argc--; argv++; idx++; /* set to next one */ } else if( arg->internal.stopped ) { /* ready */ arg->r_opt = 0; goto leave; } else if( *s == '-' && s[1] == '-' ) { /* long option */ char *argpos; arg->internal.inarg = 0; if( !s[2] && !(arg->flags & (1<<3)) ) { /* stop option processing */ arg->internal.stopped = 1; argc--; argv++; idx++; goto next_one; } argpos = strchr( s+2, '=' ); if( argpos ) *argpos = 0; i = find_long_option( arg, opts, s+2 ); if( argpos ) *argpos = '='; if( i < 0 && !strcmp( "help", s+2) ) show_help(opts, arg->flags); else if( i < 0 && !strcmp( "version", s+2) ) { if( !(arg->flags & (1<<6)) ) { show_version(); exit(0); } } else if( i < 0 && !strcmp( "warranty", s+2) ) { puts( strusage(16) ); exit(0); } else if( i < 0 && !strcmp( "dump-options", s+2) ) { for(i=0; opts[i].short_opt; i++ ) { if( opts[i].long_opt ) printf( "--%s\n", opts[i].long_opt ); } fputs("--dump-options\n--help\n--version\n--warranty\n", stdout ); exit(0); } if( i == -2 ) /* ambiguous option */ arg->r_opt = -8; else if( i == -1 ) { arg->r_opt = -2; arg->r.ret_str = s+2; } else arg->r_opt = opts[i].short_opt; if( i < 0 ) ; else if( (opts[i].flags & 7) ) { if( argpos ) { s2 = argpos+1; if( !*s2 ) s2 = NULL; } else s2 = argv[1]; if( !s2 && (opts[i].flags & 8) ) { /* no argument but it is okay*/ arg->r_type = 0; /* because it is optional */ } else if( !s2 ) { arg->r_opt = -3; /* missing argument */ } else if( !argpos && *s2 == '-' && (opts[i].flags & 8) ) { /* the argument is optional and the next seems to be * an option. We do not check this possible option * but assume no argument */ arg->r_type = 0; } else { set_opt_arg(arg, opts[i].flags, s2); if( !argpos ) { argc--; argv++; idx++; /* skip one */ } } } else { /* does not take an argument */ if( argpos ) arg->r_type = -6; /* argument not expected */ else arg->r_type = 0; } argc--; argv++; idx++; /* set to next one */ } else if( (*s == '-' && s[1]) || arg->internal.inarg ) { /* short option */ int dash_kludge = 0; i = 0; if( !arg->internal.inarg ) { arg->internal.inarg++; if( arg->flags & (1<<5) ) { for(i=0; opts[i].short_opt; i++ ) if( opts[i].long_opt && !strcmp( opts[i].long_opt, s+1)) { dash_kludge=1; break; } } } s += arg->internal.inarg; if( !dash_kludge ) { for(i=0; opts[i].short_opt; i++ ) if( opts[i].short_opt == *s ) break; } if( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) ) show_help(opts, arg->flags); arg->r_opt = opts[i].short_opt; if( !opts[i].short_opt ) { arg->r_opt = (opts[i].flags & 256)? -7:-2; arg->internal.inarg++; /* point to the next arg */ arg->r.ret_str = s; } else if( (opts[i].flags & 7) ) { if( s[1] && !dash_kludge ) { s2 = s+1; set_opt_arg(arg, opts[i].flags, s2); } else { s2 = argv[1]; if( !s2 && (opts[i].flags & 8) ) { /* no argument but it is okay*/ arg->r_type = 0; /* because it is optional */ } else if( !s2 ) { arg->r_opt = -3; /* missing argument */ } else if( *s2 == '-' && s2[1] && (opts[i].flags & 8) ) { /* the argument is optional and the next seems to be * an option. We do not check this possible option * but assume no argument */ arg->r_type = 0; } else { set_opt_arg(arg, opts[i].flags, s2); argc--; argv++; idx++; /* skip one */ } } s = "x"; /* so that !s[1] yields false */ } else { /* does not take an argument */ arg->r_type = 0; arg->internal.inarg++; /* point to the next arg */ } if( !s[1] || dash_kludge ) { /* no more concatenated short options */ arg->internal.inarg = 0; argc--; argv++; idx++; } } else if( arg->flags & (1<<2) ) { arg->r_opt = -1; /* not an option but a argument */ arg->r_type = 2; arg->r.ret_str = s; argc--; argv++; idx++; /* set to next one */ } else { arg->internal.stopped = 1; /* stop option processing */ goto next_one; } leave: *arg->argc = argc; *arg->argv = argv; arg->internal.idx = idx; return arg->r_opt; } static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s) { int base = (flags & 16)? 0 : 10; switch( arg->r_type = (flags & 7) ) { case 1: /* takes int argument */ arg->r.ret_int = (int)strtol(s,NULL,base); return 0; case 3: /* takes long argument */ arg->r.ret_long= strtol(s,NULL,base); return 0; case 4: /* takes ulong argument */ arg->r.ret_ulong= strtoul(s,NULL,base); return 0; case 2: /* takes string argument */ default: arg->r.ret_str = s; return 1; } } static size_t long_opt_strlen( ARGPARSE_OPTS *o ) { size_t n = strlen(o->long_opt); if( o->description && *o->description == '|' ) { const char *s; s=o->description+1; if( *s != '=' ) n++; for(; *s && *s != '|'; s++ ) n++; } return n; } /**************** * Print formatted help. The description string has some special * meanings: * - A description string which is "@" suppresses help output for * this option * - a description,ine which starts with a '@' and is followed by * any other characters is printed as is; this may be used for examples * ans such. * - A description which starts with a '|' outputs the string between this * bar and the next one as arguments of the long option. */ static void show_help( ARGPARSE_OPTS *opts, unsigned flags ) { const char *s; show_version(); putchar('\n'); s = strusage(41); puts(s); if( opts[0].description ) { /* auto format the option description */ int i,j, indent; /* get max. length of long options */ for(i=indent=0; opts[i].short_opt; i++ ) { if( opts[i].long_opt ) if( !opts[i].description || *opts[i].description != '@' ) if( (j=long_opt_strlen(opts+i)) > indent && j < 35 ) indent = j; } /* example: " -v, --verbose Viele Sachen ausgeben" */ indent += 10; if( *opts[0].description != '@' ) puts("Options:"); for(i=0; opts[i].short_opt; i++ ) { s = _( opts[i].description ); if( s && *s== '@' && !s[1] ) /* hide this line */ continue; if( s && *s == '@' ) { /* unindented comment only line */ for(s++; *s; s++ ) { if( *s == '\n' ) { if( s[1] ) putchar('\n'); } else putchar(*s); } putchar('\n'); continue; } j = 3; if( opts[i].short_opt < 256 ) { printf(" -%c", opts[i].short_opt ); if( !opts[i].long_opt ) { if(s && *s == '|' ) { putchar(' '); j++; for(s++ ; *s && *s != '|'; s++, j++ ) putchar(*s); if( *s ) s++; } } } else fputs(" ", stdout); if( opts[i].long_opt ) { j += printf("%c --%s", opts[i].short_opt < 256?',':' ', opts[i].long_opt ); if(s && *s == '|' ) { if( *++s != '=' ) { putchar(' '); j++; } for( ; *s && *s != '|'; s++, j++ ) putchar(*s); if( *s ) s++; } fputs(" ", stdout); j += 3; } for(;j < indent; j++ ) putchar(' '); if( s ) { if( *s && j > indent ) { putchar('\n'); for(j=0;j < indent; j++ ) putchar(' '); } for(; *s; s++ ) { if( *s == '\n' ) { if( s[1] ) { putchar('\n'); for(j=0;j < indent; j++ ) putchar(' '); } } else putchar(*s); } } putchar('\n'); } if( flags & 32 ) puts("\n(A single dash may be used instead of the double ones)"); } if( (s=strusage(19)) ) { /* bug reports to ... */ putchar('\n'); fputs(s, stdout); } fflush(stdout); exit(0); } static void show_version() { const char *s; int i; /* version line */ fputs(strusage(11), stdout); if( (s=strusage(12)) ) printf(" (%s)", s ); printf(" %s\n", strusage(13) ); /* additional version lines */ for(i=20; i < 30; i++ ) if( (s=strusage(i)) ) printf("%s\n", s ); /* copyright string */ if( (s=strusage(14)) ) printf("%s\n", s ); /* copying conditions */ if( (s=strusage(15)) ) fputs(s, stdout); /* thanks */ if( (s=strusage(18)) ) fputs(s, stdout); /* additional program info */ for(i=30; i < 40; i++ ) if( (s=strusage(i)) ) fputs (s, stdout); fflush(stdout); } void usage( int level ) { if( !level ) { fprintf(stderr,"%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); fflush(stderr); } else if( level == 1 ) { fputs(strusage(40),stderr); exit(2); } else if( level == 2 ) { puts(strusage(41)); exit(0); } } /* Level * 0: Copyright String auf stderr ausgeben * 1: Kurzusage auf stderr ausgeben und beenden * 2: Langusage auf stdout ausgeben und beenden * 11: name of program * 12: optional name of package which includes this program. * 13: version string * 14: copyright string * 15: Short copying conditions (with LFs) * 16: Long copying conditions (with LFs) * 17: Optional printable OS name * 18: Optional thanks list (with LFs) * 19: Bug report info *20..29: Additional lib version strings. *30..39: Additional program info (with LFs) * 40: short usage note (with LF) * 41: long usage note (with LF) */ const char * strusage( int level ) { const char *p = strusage_handler? strusage_handler(level) : NULL; if( p ) return p; switch( level ) { case 11: p = "foo"; break; case 13: p = "0.0"; break; - case 14: p = "Copyright (C) 2005 Free Software Foundation, Inc."; break; + case 14: p = "Copyright (C) 2006 Free Software Foundation, Inc."; break; case 15: p = "This program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions. See the file COPYING for details.\n"; break; case 16: p = "This is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation; either version 2 of the License, or\n" "(at your option) any later version.\n\n" "It is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" -"Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n"; +"Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,\n" +"USA.\n"; break; case 40: /* short and long usage */ case 41: p = ""; break; } return p; } void set_strusage( const char *(*f)( int ) ) { strusage_handler = f; } #ifdef TEST static struct { int verbose; int debug; char *outfile; char *crf; int myopt; int echo; int a_long_one; }opt; int main(int argc, char **argv) { ARGPARSE_OPTS opts[] = { { 'v', "verbose", 0 , "Laut sein"}, { 'e', "echo" , 0 , "Zeile ausgeben, damit wir sehen, was wir einegegeben haben"}, { 'd', "debug", 0 , "Debug\nfalls mal etasws\nSchief geht"}, { 'o', "output", 2 }, { 'c', "cross-ref", 2|8, "cross-reference erzeugen\n" }, { 'm', "my-option", 1|8 }, { 500, "a-long-option", 0 }, {0} }; ARGPARSE_ARGS pargs = { &argc, &argv, 2|4|32 }; int i; while( ArgParse( &pargs, opts) ) { switch( pargs.r_opt ) { case -1 : printf( "arg=`%s'\n", pargs.r.ret_str); break; case 'v': opt.verbose++; break; case 'e': opt.echo++; break; case 'd': opt.debug++; break; case 'o': opt.outfile = pargs.r.ret_str; break; case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; case 500: opt.a_long_one++; break; default : pargs.err = 1; break; /* force warning output */ } } for(i=0; i < argc; i++ ) printf("%3d -> (%s)\n", i, argv[i] ); puts("Options:"); if( opt.verbose ) printf(" verbose=%d\n", opt.verbose ); if( opt.debug ) printf(" debug=%d\n", opt.debug ); if( opt.outfile ) printf(" outfile=`%s'\n", opt.outfile ); if( opt.crf ) printf(" crffile=`%s'\n", opt.crf ); if( opt.myopt ) printf(" myopt=%d\n", opt.myopt ); if( opt.a_long_one ) printf(" a-long-one=%d\n", opt.a_long_one ); if( opt.echo ) printf(" echo=%d\n", opt.echo ); return 0; } #endif /**** bottom of file ****/ diff --git a/jnlib/argparse.h b/jnlib/argparse.h index e8922faa4..531622ea5 100644 --- a/jnlib/argparse.h +++ b/jnlib/argparse.h @@ -1,67 +1,68 @@ /* argparse.h * Copyright (C) 1998,1999,2000,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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_ARGPARSE_H #define LIBJNLIB_ARGPARSE_H #include #include "types.h" typedef struct { int *argc; /* pointer to argc (value subject to change) */ char ***argv; /* pointer to argv (value subject to change) */ unsigned flags; /* Global flags (DO NOT CHANGE) */ int err; /* print error about last option */ /* 1 = warning, 2 = abort */ int r_opt; /* return option */ int r_type; /* type of return value (0 = no argument found)*/ union { int ret_int; long ret_long; unsigned long ret_ulong; char *ret_str; } r; /* Return values */ struct { int idx; int inarg; int stopped; const char *last; void *aliases; const void *cur_alias; } internal; /* DO NOT CHANGE */ } ARGPARSE_ARGS; typedef struct { int short_opt; const char *long_opt; unsigned flags; const char *description; /* optional option description */ } ARGPARSE_OPTS; int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); int optfile_parse( FILE *fp, const char *filename, unsigned *lineno, ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); void usage( int level ); const char *strusage( int level ); void set_strusage( const char *(*f)( int ) ); #endif /*LIBJNLIB_ARGPARSE_H*/ diff --git a/jnlib/dotlock.c b/jnlib/dotlock.c index b7f892717..89edb7b91 100644 --- a/jnlib/dotlock.c +++ b/jnlib/dotlock.c @@ -1,477 +1,478 @@ /* dotlock.c - dotfile locking * Copyright (C) 1998, 2000, 2001, 2003, 2004, * 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #ifndef HAVE_DOSISH_SYSTEM #include #endif #include #include #include #include #include #include "libjnlib-config.h" #include "dotlock.h" #if !defined(DIRSEP_C) && !defined(EXTSEP_C) \ && !defined(DIRSEP_S) && !defined(EXTSEP_S) #ifdef HAVE_DOSISH_SYSTEM #define DIRSEP_C '\\' #define EXTSEP_C '.' #define DIRSEP_S "\\" #define EXTSEP_S "." #else #define DIRSEP_C '/' #define EXTSEP_C '.' #define DIRSEP_S "/" #define EXTSEP_S "." #endif #endif struct dotlock_handle { struct dotlock_handle *next; char *tname; /* name of lockfile template */ char *lockname; /* name of the real lockfile */ int locked; /* lock status */ int disable; /* locking */ }; static volatile DOTLOCK all_lockfiles; static int never_lock; static int read_lockfile( const char *name ); void disable_dotlock(void) { never_lock = 1; } /**************** * Create a lockfile with the given name and return an object of * type DOTLOCK which may be used later to actually do the lock. * A cleanup routine gets installed to cleanup left over locks * or other files used together with the lockmechanism. * Althoug the function is called dotlock, this does not necessarily * mean that real lockfiles are used - the function may decide to * use fcntl locking. Calling the function with NULL only install * the atexit handler and maybe used to assure that the cleanup * is called after all other atexit handlers. * * Notes: This function creates a lock file in the same directory * as file_to_lock with the name "file_to_lock.lock" * A temporary file ".#lk..pid[.threadid] is used. * This function does nothing for Windoze. */ DOTLOCK create_dotlock( const char *file_to_lock ) { static int initialized; DOTLOCK h; int fd = -1; char pidstr[16]; #ifndef HAVE_DOSISH_SYSTEM struct utsname utsbuf; #endif const char *nodename; const char *dirpart; int dirpartlen; if( !initialized ) { atexit( dotlock_remove_lockfiles ); initialized = 1; } if( !file_to_lock ) return NULL; h = jnlib_xcalloc( 1, sizeof *h ); if( never_lock ) { h->disable = 1; #ifdef _REENTRANT /* fixme: aquire mutex on all_lockfiles */ #endif h->next = all_lockfiles; all_lockfiles = h; return h; } #ifndef HAVE_DOSISH_SYSTEM sprintf( pidstr, "%10d\n", (int)getpid() ); /* fixme: add the hostname to the second line (FQDN or IP addr?) */ /* create a temporary file */ if( uname( &utsbuf ) ) nodename = "unknown"; else nodename = utsbuf.nodename; #ifdef __riscos__ { char *iter = (char *) nodename; for (; iter[0]; iter++) if (iter[0] == '.') iter[0] = '/'; } #endif /* __riscos__ */ if( !(dirpart = strrchr( file_to_lock, DIRSEP_C )) ) { dirpart = EXTSEP_S; dirpartlen = 1; } else { dirpartlen = dirpart - file_to_lock; dirpart = file_to_lock; } #ifdef _REENTRANT /* fixme: aquire mutex on all_lockfiles */ #endif h->next = all_lockfiles; all_lockfiles = h; h->tname = jnlib_xmalloc( dirpartlen + 6+30+ strlen(nodename) + 11 ); #ifndef __riscos__ sprintf( h->tname, "%.*s/.#lk%p.%s.%d", dirpartlen, dirpart, h, nodename, (int)getpid() ); #else /* __riscos__ */ sprintf( h->tname, "%.*s.lk%p/%s/%d", dirpartlen, dirpart, h, nodename, (int)getpid() ); #endif /* __riscos__ */ do { errno = 0; fd = open( h->tname, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR ); } while( fd == -1 && errno == EINTR ); if( fd == -1 ) { all_lockfiles = h->next; log_error( "failed to create temporary file `%s': %s\n", h->tname, strerror(errno)); jnlib_free(h->tname); jnlib_free(h); return NULL; } if( write(fd, pidstr, 11 ) != 11 ) { all_lockfiles = h->next; #ifdef _REENTRANT /* release mutex */ #endif log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) ); close(fd); unlink(h->tname); jnlib_free(h->tname); jnlib_free(h); return NULL; } if( close(fd) ) { all_lockfiles = h->next; #ifdef _REENTRANT /* release mutex */ #endif log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) ); close(fd); unlink(h->tname); jnlib_free(h->tname); jnlib_free(h); return NULL; } # ifdef _REENTRANT /* release mutex */ # endif #endif /* !HAVE_DOSISH_SYSTEM */ h->lockname = jnlib_xmalloc( strlen(file_to_lock) + 6 ); strcpy(stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock"); return h; } void destroy_dotlock ( DOTLOCK h ) { #if !defined (HAVE_DOSISH_SYSTEM) if ( h ) { DOTLOCK hprev, htmp; /* First remove the handle from our global list of all locks. */ for (hprev=NULL, htmp=all_lockfiles; htmp; hprev=htmp, htmp=htmp->next) if (htmp == h) { if (hprev) hprev->next = htmp->next; else all_lockfiles = htmp->next; h->next = NULL; break; } /* Second destroy the lock. */ if (!h->disable) { if (h->locked && h->lockname) unlink (h->lockname); if (h->tname) unlink (h->tname); jnlib_free (h->tname); jnlib_free (h->lockname); } jnlib_free(h); } #endif } static int maybe_deadlock( DOTLOCK h ) { DOTLOCK r; for( r=all_lockfiles; r; r = r->next ) { if( r != h && r->locked ) return 1; } return 0; } /**************** * Do a lock on H. A TIMEOUT of 0 returns immediately, * -1 waits forever (hopefully not), other * values are timeouts in milliseconds. * Returns: 0 on success */ int make_dotlock( DOTLOCK h, long timeout ) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else int pid; const char *maybe_dead=""; int backoff=0; if( h->disable ) { return 0; } if( h->locked ) { #ifndef __riscos__ log_debug("oops, `%s' is already locked\n", h->lockname ); #endif /* !__riscos__ */ return 0; } for(;;) { #ifndef __riscos__ if( !link(h->tname, h->lockname) ) { /* fixme: better use stat to check the link count */ h->locked = 1; return 0; /* okay */ } if( errno != EEXIST ) { log_error( "lock not made: link() failed: %s\n", strerror(errno) ); return -1; } #else /* __riscos__ */ if( !renamefile(h->tname, h->lockname) ) { h->locked = 1; return 0; /* okay */ } if( errno != EEXIST ) { log_error( "lock not made: rename() failed: %s\n", strerror(errno) ); return -1; } #endif /* __riscos__ */ if( (pid = read_lockfile(h->lockname)) == -1 ) { if( errno != ENOENT ) { log_info("cannot read lockfile\n"); return -1; } log_info( "lockfile disappeared\n"); continue; } else if( pid == getpid() ) { log_info( "Oops: lock already held by us\n"); h->locked = 1; return 0; /* okay */ } else if( kill(pid, 0) && errno == ESRCH ) { #ifndef __riscos__ maybe_dead = " - probably dead"; #if 0 /* we should not do this without checking the permissions */ /* and the hostname */ log_info( "removing stale lockfile (created by %d)", pid ); #endif #else /* __riscos__ */ /* we are *pretty* sure that the other task is dead and therefore we remove the other lock file */ maybe_dead = " - probably dead - removing lock"; unlink(h->lockname); #endif /* __riscos__ */ } if( timeout == -1 ) { struct timeval tv; log_info( "waiting for lock (held by %d%s) %s...\n", pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":""); /* can't use sleep, cause signals may be blocked */ tv.tv_sec = 1 + backoff; tv.tv_usec = 0; select(0, NULL, NULL, NULL, &tv); if( backoff < 10 ) backoff++ ; } else return -1; } /*not reached */ #endif /* !HAVE_DOSISH_SYSTEM */ } /**************** * release a lock * Returns: 0 := success */ int release_dotlock( DOTLOCK h ) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else int pid; /* To avoid atexit race conditions we first check whether there are any locks left. It might happen that another atexit handler tries to release the lock while the atexit handler of this module already ran and thus H is undefined. */ if(!all_lockfiles) return 0; if( h->disable ) { return 0; } if( !h->locked ) { log_debug("oops, `%s' is not locked\n", h->lockname ); return 0; } pid = read_lockfile( h->lockname ); if( pid == -1 ) { log_error( "release_dotlock: lockfile error\n"); return -1; } if( pid != getpid() ) { log_error( "release_dotlock: not our lock (pid=%d)\n", pid); return -1; } #ifndef __riscos__ if( unlink( h->lockname ) ) { log_error( "release_dotlock: error removing lockfile `%s'", h->lockname); return -1; } #else /* __riscos__ */ if( renamefile(h->lockname, h->tname) ) { log_error( "release_dotlock: error renaming lockfile `%s' to `%s'", h->lockname, h->tname); return -1; } #endif /* __riscos__ */ /* fixme: check that the link count is now 1 */ h->locked = 0; return 0; #endif /* !HAVE_DOSISH_SYSTEM */ } /**************** * Read the lock file and return the pid, returns -1 on error. */ static int read_lockfile( const char *name ) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else int fd, pid; char pidstr[16]; if( (fd = open(name, O_RDONLY)) == -1 ) { int e = errno; log_debug("error opening lockfile `%s': %s\n", name, strerror(errno) ); errno = e; return -1; } if( read(fd, pidstr, 10 ) != 10 ) { /* Read 10 digits w/o newline */ log_debug("error reading lockfile `%s'", name ); close(fd); errno = 0; return -1; } pidstr[10] = 0; /* terminate pid string */ close(fd); pid = atoi(pidstr); #ifndef __riscos__ if( !pid || pid == -1 ) { #else /* __riscos__ */ if( (!pid && riscos_getpid()) || pid == -1 ) { #endif /* __riscos__ */ log_error("invalid pid %d in lockfile `%s'", pid, name ); errno = 0; return -1; } return pid; #endif } void dotlock_remove_lockfiles() { #ifndef HAVE_DOSISH_SYSTEM DOTLOCK h, h2; h = all_lockfiles; all_lockfiles = NULL; while ( h ) { h2 = h->next; destroy_dotlock (h); h = h2; } #endif } diff --git a/jnlib/dotlock.h b/jnlib/dotlock.h index 2cb39008a..1c0f05cb2 100644 --- a/jnlib/dotlock.h +++ b/jnlib/dotlock.h @@ -1,37 +1,38 @@ /* dotlock.h * Copyright (C) 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_DOTLOCK_H #define LIBJNLIB_DOTLOCK_H struct dotlock_handle; typedef struct dotlock_handle *DOTLOCK; void disable_dotlock (void); DOTLOCK create_dotlock(const char *file_to_lock); void destroy_dotlock ( DOTLOCK h ); int make_dotlock (DOTLOCK h, long timeout); int release_dotlock (DOTLOCK h); void dotlock_remove_lockfiles (void); #endif /*LIBJNLIB_DOTLOCK_H*/ diff --git a/jnlib/libjnlib-config.h b/jnlib/libjnlib-config.h index da3991432..ded6e057b 100644 --- a/jnlib/libjnlib-config.h +++ b/jnlib/libjnlib-config.h @@ -1,75 +1,76 @@ /* libjnlib-config.h - local configuration of the jnlib functions * Copyright (C) 2000, 2001, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /**************** * This header is to be included only by the files in this directory * it should not be used by other modules. */ #ifndef LIBJNLIB_CONFIG_H #define LIBJNLIB_CONFIG_H #include /* gcry_malloc & Cie. */ #include "logging.h" /* We require support for utf-8 conversion. */ #define JNLIB_NEED_UTF8CONV 1 #ifdef USE_SIMPLE_GETTEXT int set_gettext_file( const char *filename ); const char *gettext( const char *msgid ); # define _(a) gettext (a) # define N_(a) (a) #else #ifdef HAVE_LOCALE_H # include #endif #ifdef ENABLE_NLS # include # define _(a) gettext (a) # ifdef gettext_noop # define N_(a) gettext_noop (a) # else # define N_(a) (a) # endif #else # define _(a) (a) # define N_(a) (a) #endif #endif /* !USE_SIMPLE_GETTEXT */ #define jnlib_xmalloc(a) gcry_xmalloc( (a) ) #define jnlib_xcalloc(a,b) gcry_xcalloc( (a), (b) ) #define jnlib_xrealloc(a,n) gcry_xrealloc( (a), (n) ) #define jnlib_xstrdup(a) gcry_xstrdup( (a) ) #define jnlib_free(a) gcry_free( (a) ) #define jnlib_log_debug log_debug #define jnlib_log_info log_info #define jnlib_log_error log_error #define jnlib_log_fatal log_fatal #define jnlib_log_bug log_bug #endif /*LIBJNUTIL_CONFIG_H*/ diff --git a/jnlib/logging.c b/jnlib/logging.c index c944006a5..20ba02ccd 100644 --- a/jnlib/logging.c +++ b/jnlib/logging.c @@ -1,614 +1,615 @@ /* logging.c - useful logging functions * Copyright (C) 1998, 1999, 2000, 2001, 2003, * 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*!HAVE_W32_SYSTEM*/ #include #include #include #define JNLIB_NEED_LOG_LOGV 1 #include "libjnlib-config.h" #include "logging.h" #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN) #define USE_FUNWRITER 1 #endif static FILE *logstream; static int log_socket = -1; static char prefix_buffer[80]; static int with_time; static int with_prefix; static int with_pid; static int running_detached; static int force_prefixes; static int missing_lf; static int errorcount; int log_get_errorcount (int clear) { int n = errorcount; if( clear ) errorcount = 0; return n; } void log_inc_errorcount (void) { errorcount++; } /* The follwing 3 functions are used by funopen to write logs to a socket. */ #ifdef USE_FUNWRITER struct fun_cookie_s { int fd; int quiet; int want_socket; int is_socket; char name[1]; }; /* Write NBYTES of BUFFER to file descriptor FD. */ static int writen (int fd, const void *buffer, size_t nbytes) { const char *buf = buffer; size_t nleft = nbytes; int nwritten; while (nleft > 0) { nwritten = write (fd, buf, nleft); if (nwritten < 0 && errno == EINTR) continue; if (nwritten < 0) return -1; nleft -= nwritten; buf = buf + nwritten; } return 0; } static int fun_writer (void *cookie_arg, const char *buffer, size_t size) { struct fun_cookie_s *cookie = cookie_arg; /* Note that we always try to reconnect to the socket but print error messages only the first time an error occured. If RUNNING_DETACHED is set we don't fall back to stderr and even do not print any error messages. This is needed because detached processes often close stderr and by writing to file descriptor 2 we might send the log message to a file not intended for logging (e.g. a pipe or network connection). */ if (cookie->want_socket && cookie->fd == -1) { /* Not yet open or meanwhile closed due to an error. */ cookie->is_socket = 0; cookie->fd = socket (PF_LOCAL, SOCK_STREAM, 0); if (cookie->fd == -1) { if (!cookie->quiet && !running_detached && isatty (fileno (stderr))) fprintf (stderr, "failed to create socket for logging: %s\n", strerror(errno)); } else { struct sockaddr_un addr; size_t addrlen; memset (&addr, 0, sizeof addr); addr.sun_family = PF_LOCAL; strncpy (addr.sun_path, cookie->name, sizeof (addr.sun_path)-1); addr.sun_path[sizeof (addr.sun_path)-1] = 0; addrlen = (offsetof (struct sockaddr_un, sun_path) + strlen (addr.sun_path) + 1); if (connect (cookie->fd, (struct sockaddr *) &addr, addrlen) == -1) { if (!cookie->quiet && !running_detached && isatty (fileno (stderr))) fprintf (stderr, "can't connect to `%s': %s\n", cookie->name, strerror(errno)); close (cookie->fd); cookie->fd = -1; } } if (cookie->fd == -1) { if (!running_detached) { /* Due to all the problems with apps not running detahced but beeing caled with stderr closed or used for a different purposes, it does not make sense to switch to stderr. We tehrefore disable it. */ if (!cookie->quiet) { /* fputs ("switching logging to stderr\n", stderr);*/ cookie->quiet = 1; } cookie->fd = -1; /*fileno (stderr);*/ } } else /* Connection has been established. */ { cookie->quiet = 0; cookie->is_socket = 1; } } log_socket = cookie->fd; if (cookie->fd != -1 && !writen (cookie->fd, buffer, size)) return size; /* Okay. */ if (!running_detached && cookie->fd != -1 && isatty (fileno (stderr))) { if (*cookie->name) fprintf (stderr, "error writing to `%s': %s\n", cookie->name, strerror(errno)); else fprintf (stderr, "error writing to file descriptor %d: %s\n", cookie->fd, strerror(errno)); } if (cookie->is_socket && cookie->fd != -1) { close (cookie->fd); cookie->fd = -1; log_socket = -1; } return size; } static int fun_closer (void *cookie_arg) { struct fun_cookie_s *cookie = cookie_arg; if (cookie->fd != -1) close (cookie->fd); jnlib_free (cookie); log_socket = -1; return 0; } #endif /*USE_FUNWRITER*/ /* Common function to either set the logging to a file or a file descriptor. */ static void set_file_fd (const char *name, int fd) { FILE *fp; int want_socket; #ifdef USE_FUNWRITER struct fun_cookie_s *cookie; #endif if (name && !strcmp (name, "-")) { name = NULL; fd = fileno (stderr); } if (name) { want_socket = (!strncmp (name, "socket://", 9) && name[9]); if (want_socket) name += 9; } else { want_socket = 0; } #ifdef USE_FUNWRITER cookie = jnlib_xmalloc (sizeof *cookie + (name? strlen (name):0)); strcpy (cookie->name, name? name:""); cookie->quiet = 0; cookie->is_socket = 0; cookie->want_socket = want_socket; if (!name) cookie->fd = fd; else if (want_socket) cookie->fd = -1; else { do cookie->fd = open (name, O_WRONLY|O_APPEND|O_CREAT, (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH)); while (cookie->fd == -1 && errno == EINTR); } log_socket = cookie->fd; #ifdef HAVE_FOPENCOOKIE { cookie_io_functions_t io = { NULL }; io.write = fun_writer; io.close = fun_closer; fp = fopencookie (cookie, "w", io); } #else /*!HAVE_FOPENCOOKIE*/ fp = funopen (cookie, NULL, fun_writer, NULL, fun_closer); #endif /*!HAVE_FOPENCOOKIE*/ #else /*!USE_FUNWRITER*/ /* The system does not feature custom streams. Thus fallback to plain stdio. */ if (want_socket) { fprintf (stderr, "system does not support logging to a socket - " "using stderr\n"); fp = stderr; } else if (name) fp = fopen (name, "a"); else if (fd == 1) fp = stdout; else if (fd == 2) fp = stderr; else fp = fdopen (fd, "a"); log_socket = -1; #endif /*!USE_FUNWRITER*/ /* On success close the old logstream right now, so that we are really sure it has been closed. */ if (fp && logstream) { if (logstream != stderr && logstream != stdout) fclose (logstream); logstream = NULL; } if (!fp) { if (name) fprintf (stderr, "failed to open log file `%s': %s\n", name, strerror(errno)); else fprintf (stderr, "failed to fdopen file descriptor %d: %s\n", fd, strerror(errno)); /* We need to make sure that there is a log stream. We use stderr. */ fp = stderr; } else setvbuf (fp, NULL, _IOLBF, 0); if (logstream && logstream != stderr && logstream != stdout) fclose (logstream); logstream = fp; /* We always need to print the prefix and the pid for socket mode, so that the server reading the socket can do something meaningful. */ force_prefixes = want_socket; missing_lf = 0; } /* Set the file to write log to. The special names NULL and "-" may be used to select stderr and names formatted like "socket:///home/foo/mylogs" may be used to write the logging to the socket "/home/foo/mylogs". If the connection to the socket fails or a write error is detected, the function writes to stderr and tries the next time again to connect the socket. */ void log_set_file (const char *name) { set_file_fd (name? name: "-", -1); } void log_set_fd (int fd) { set_file_fd (NULL, fd); } void log_set_prefix (const char *text, unsigned int flags) { if (text) { strncpy (prefix_buffer, text, sizeof (prefix_buffer)-1); prefix_buffer[sizeof (prefix_buffer)-1] = 0; } with_prefix = (flags & JNLIB_LOG_WITH_PREFIX); with_time = (flags & JNLIB_LOG_WITH_TIME); with_pid = (flags & JNLIB_LOG_WITH_PID); running_detached = (flags & JNLIB_LOG_RUN_DETACHED); } const char * log_get_prefix (unsigned int *flags) { if (flags) { *flags = 0; if (with_prefix) *flags |= JNLIB_LOG_WITH_PREFIX; if (with_time) *flags |= JNLIB_LOG_WITH_TIME; if (with_pid) *flags |= JNLIB_LOG_WITH_PID; if (running_detached) *flags |= JNLIB_LOG_RUN_DETACHED; } return prefix_buffer; } /* This function returns true if the file descriptor FD is in use for logging. This is preferable over a test using log_get_fd in that it allows the logging code to use more then one file descriptor. */ int log_test_fd (int fd) { if (logstream) { int tmp = fileno (logstream); if ( tmp != -1 && tmp == fd) return 1; } if (log_socket != -1 && log_socket == fd) return 1; return 0; } int log_get_fd () { return fileno(logstream?logstream:stderr); } FILE * log_get_stream () { /* FIXME: We should not return stderr here but initialize the log stream properly. This might break more things than using stderr, though */ return logstream?logstream:stderr; } static void do_logv (int level, const char *fmt, va_list arg_ptr) { if (!logstream) { log_set_file (NULL); /* Make sure a log stream has been set. */ assert (logstream); } if (missing_lf && level != JNLIB_LOG_CONT) putc('\n', logstream ); missing_lf = 0; if (level != JNLIB_LOG_CONT) { /* Note this does not work for multiple line logging as we would * need to print to a buffer first */ if (with_time && !force_prefixes) { struct tm *tp; time_t atime = time (NULL); tp = localtime (&atime); fprintf (logstream, "%04d-%02d-%02d %02d:%02d:%02d ", 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec ); } if (with_prefix || force_prefixes) fputs (prefix_buffer, logstream); if (with_pid || force_prefixes) fprintf (logstream, "[%u]", (unsigned int)getpid ()); if (!with_time || force_prefixes) putc (':', logstream); /* A leading backspace suppresses the extra space so that we can correctly output, programname, filename and linenumber. */ if (fmt && *fmt == '\b') fmt++; else putc (' ', logstream); } switch (level) { case JNLIB_LOG_BEGIN: break; case JNLIB_LOG_CONT: break; case JNLIB_LOG_INFO: break; case JNLIB_LOG_WARN: break; case JNLIB_LOG_ERROR: break; case JNLIB_LOG_FATAL: fputs("Fatal: ",logstream ); break; case JNLIB_LOG_BUG: fputs("Ohhhh jeeee: ", logstream); break; case JNLIB_LOG_DEBUG: fputs("DBG: ", logstream ); break; default: fprintf(logstream,"[Unknown log level %d]: ", level ); break; } if (fmt) { vfprintf(logstream,fmt,arg_ptr) ; if (*fmt && fmt[strlen(fmt)-1] != '\n') missing_lf = 1; } if (level == JNLIB_LOG_FATAL) exit(2); if (level == JNLIB_LOG_BUG) abort(); } static void do_log( int level, const char *fmt, ... ) { va_list arg_ptr ; va_start( arg_ptr, fmt ) ; do_logv( level, fmt, arg_ptr ); va_end(arg_ptr); } void log_logv (int level, const char *fmt, va_list arg_ptr) { do_logv (level, fmt, arg_ptr); } void log_info( const char *fmt, ... ) { va_list arg_ptr ; va_start( arg_ptr, fmt ) ; do_logv( JNLIB_LOG_INFO, fmt, arg_ptr ); va_end(arg_ptr); } void log_error( const char *fmt, ... ) { va_list arg_ptr ; va_start( arg_ptr, fmt ) ; do_logv( JNLIB_LOG_ERROR, fmt, arg_ptr ); va_end(arg_ptr); /* protect against counter overflow */ if( errorcount < 30000 ) errorcount++; } void log_fatal( const char *fmt, ... ) { va_list arg_ptr ; va_start( arg_ptr, fmt ) ; do_logv( JNLIB_LOG_FATAL, fmt, arg_ptr ); va_end(arg_ptr); abort(); /* never called, but it makes the compiler happy */ } void log_bug( const char *fmt, ... ) { va_list arg_ptr ; va_start( arg_ptr, fmt ) ; do_logv( JNLIB_LOG_BUG, fmt, arg_ptr ); va_end(arg_ptr); abort(); /* never called, but it makes the compiler happy */ } void log_debug( const char *fmt, ... ) { va_list arg_ptr ; va_start( arg_ptr, fmt ) ; do_logv( JNLIB_LOG_DEBUG, fmt, arg_ptr ); va_end(arg_ptr); } void log_printf (const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); do_logv (fmt ? JNLIB_LOG_CONT : JNLIB_LOG_BEGIN, fmt, arg_ptr); va_end (arg_ptr); } /* Print a hexdump of BUFFER. With TEXT of NULL print just the raw dump, with TEXT just an empty string, print a trailing linefeed, otherwise print an entire debug line. */ void log_printhex (const char *text, const void *buffer, size_t length) { if (text && *text) log_debug ("%s ", text); if (length) { const unsigned char *p = buffer; log_printf ("%02X", *p); for (length--, p++; length--; p++) log_printf (" %02X", *p); } if (text) log_printf ("\n"); } #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) void bug_at( const char *file, int line, const char *func ) { do_log( JNLIB_LOG_BUG, ("... this is a bug (%s:%d:%s)\n"), file, line, func ); abort(); /* never called, but it makes the compiler happy */ } #else void bug_at( const char *file, int line ) { do_log( JNLIB_LOG_BUG, _("you found a bug ... (%s:%d)\n"), file, line); abort(); /* never called, but it makes the compiler happy */ } #endif diff --git a/jnlib/logging.h b/jnlib/logging.h index b5c0bd741..3ad43b4ec 100644 --- a/jnlib/logging.h +++ b/jnlib/logging.h @@ -1,83 +1,84 @@ /* logging.h * Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_LOGGING_H #define LIBJNLIB_LOGGING_H #include #include "mischelp.h" /* Flag values for log_set_prefix. */ #define JNLIB_LOG_WITH_PREFIX 1 #define JNLIB_LOG_WITH_TIME 2 #define JNLIB_LOG_WITH_PID 4 #define JNLIB_LOG_RUN_DETACHED 256 int log_get_errorcount (int clear); void log_inc_errorcount (void); void log_set_file( const char *name ); void log_set_fd (int fd); void log_set_prefix (const char *text, unsigned int flags); const char *log_get_prefix (unsigned int *flags); int log_test_fd (int fd); int log_get_fd(void); FILE *log_get_stream (void); #ifdef JNLIB_GCC_M_FUNCTION void bug_at( const char *file, int line, const char *func ) JNLIB_GCC_A_NR; # define BUG() bug_at( __FILE__ , __LINE__, __FUNCTION__ ) #else void bug_at( const char *file, int line ); # define BUG() bug_at( __FILE__ , __LINE__ ) #endif /* To avoid mandatory inclusion of stdarg and other stuff, do it only if explicitly requested to do so. */ #ifdef JNLIB_NEED_LOG_LOGV #include enum jnlib_log_levels { JNLIB_LOG_BEGIN, JNLIB_LOG_CONT, JNLIB_LOG_INFO, JNLIB_LOG_WARN, JNLIB_LOG_ERROR, JNLIB_LOG_FATAL, JNLIB_LOG_BUG, JNLIB_LOG_DEBUG }; void log_logv (int level, const char *fmt, va_list arg_ptr); #endif /*JNLIB_NEED_LOG_LOGV*/ void log_bug( const char *fmt, ... ) JNLIB_GCC_A_NR_PRINTF(1,2); void log_fatal( const char *fmt, ... ) JNLIB_GCC_A_NR_PRINTF(1,2); void log_error( const char *fmt, ... ) JNLIB_GCC_A_PRINTF(1,2); void log_info( const char *fmt, ... ) JNLIB_GCC_A_PRINTF(1,2); void log_debug( const char *fmt, ... ) JNLIB_GCC_A_PRINTF(1,2); void log_printf( const char *fmt, ... ) JNLIB_GCC_A_PRINTF(1,2); void log_printhex (const char *text, const void *buffer, size_t length); #endif /*LIBJNLIB_LOGGING_H*/ diff --git a/jnlib/mischelp.h b/jnlib/mischelp.h index 54da4cc1f..8e7f9c346 100644 --- a/jnlib/mischelp.h +++ b/jnlib/mischelp.h @@ -1,54 +1,55 @@ /* mischelp.h * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_MISCHELP_H #define LIBJNLIB_MISCHHELP_H #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) # define JNLIB_GCC_M_FUNCTION 1 # define JNLIB_GCC_A_NR __attribute__ ((noreturn)) # define JNLIB_GCC_A_PRINTF( f, a ) __attribute__ ((format (printf,f,a))) # define JNLIB_GCC_A_NR_PRINTF( f, a ) \ __attribute__ ((noreturn, format (printf,f,a))) #else # define JNLIB_GCC_A_NR # define JNLIB_GCC_A_PRINTF( f, a ) # define JNLIB_GCC_A_NR_PRINTF( f, a ) #endif /* To avoid that a compiler optimizes certain memset calls away, these macros may be used instead. */ #define wipememory2(_ptr,_set,_len) do { \ volatile char *_vptr=(volatile char *)(_ptr); \ size_t _vlen=(_len); \ while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ } while(0) #define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) #endif /*LIBJNLIB_MISCHELP_H*/ diff --git a/jnlib/stringhelp.c b/jnlib/stringhelp.c index 27b8a25e8..9df852754 100644 --- a/jnlib/stringhelp.c +++ b/jnlib/stringhelp.c @@ -1,748 +1,749 @@ /* stringhelp.c - standard string helper functions * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, * 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #ifdef HAVE_W32_SYSTEM #include #endif #include "libjnlib-config.h" #include "utf8conv.h" #include "stringhelp.h" /* * Look for the substring SUB in buffer and return a pointer to that * substring in BUFFER or NULL if not found. * Comparison is case-insensitive. */ const char * memistr (const void *buffer, size_t buflen, const char *sub) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buffer; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if ( toupper (*t) == toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && toupper (*t) == toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } const char * ascii_memistr ( const void *buffer, size_t buflen, const char *sub ) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buf; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if (ascii_toupper (*t) == ascii_toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && ascii_toupper (*t) == ascii_toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = (const unsigned char *)buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } /* This function is similar to strncpy(). However it won't copy more than N - 1 characters and makes sure that a '\0' is appended. With N given as 0, nothing will happen. With DEST given as NULL, memory will be allocated using jnlib_xmalloc (i.e. if it runs out of core the function terminates). Returns DES or a pointer to the allocated memory. */ char * mem2str( char *dest , const void *src , size_t n ) { char *d; const char *s; if( n ) { if( !dest ) dest = jnlib_xmalloc( n ) ; d = dest; s = src ; for(n--; n && *s; n-- ) *d++ = *s++; *d = '\0' ; } return dest ; } /**************** * remove leading and trailing white spaces */ char * trim_spaces( char *str ) { char *string, *p, *mark; string = str; /* find first non space character */ for( p=string; *p && isspace( *(byte*)p ) ; p++ ) ; /* move characters */ for( (mark = NULL); (*string = *p); string++, p++ ) if( isspace( *(byte*)p ) ) { if( !mark ) mark = string ; } else mark = NULL ; if( mark ) *mark = '\0' ; /* remove trailing spaces */ return str ; } /**************** * remove trailing white spaces */ char * trim_trailing_spaces( char *string ) { char *p, *mark; for( mark = NULL, p = string; *p; p++ ) { if( isspace( *(byte*)p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) *mark = '\0' ; return string ; } unsigned trim_trailing_chars( byte *line, unsigned len, const char *trimchars ) { byte *p, *mark; unsigned n; for(mark=NULL, p=line, n=0; n < len; n++, p++ ) { if( strchr(trimchars, *p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) { *mark = 0; return mark - line; } return len; } /**************** * remove trailing white spaces and return the length of the buffer */ unsigned trim_trailing_ws( byte *line, unsigned len ) { return trim_trailing_chars( line, len, " \t\r\n" ); } size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ) { const unsigned char *p, *mark; size_t n; for( mark=NULL, p=line, n=0; n < len; n++, p++ ) { if (strchr (trimchars, *p )) { if( !mark ) mark = p; } else mark = NULL; } if (mark) return mark - line; return len; } /* * Return the length of line ignoring trailing white-space. */ size_t length_sans_trailing_ws (const unsigned char *line, size_t len) { return length_sans_trailing_chars (line, len, " \t\r\n"); } /*************** * Extract from a given path the filename component. * */ char * make_basename(const char *filepath, const char *inputpath) { char *p; #ifdef __riscos__ return riscos_make_basename(filepath, inputpath); #endif if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, '\\')) ) if ( !(p=strrchr(filepath, ':')) ) #endif { return jnlib_xstrdup(filepath); } return jnlib_xstrdup(p+1); } /*************** * Extract from a given filename the path prepended to it. * If their isn't a path prepended to the filename, a dot * is returned ('.'). * */ char * make_dirname(const char *filepath) { char *dirname; int dirname_length; char *p; if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, '\\')) ) if ( !(p=strrchr(filepath, ':')) ) #endif { return jnlib_xstrdup("."); } dirname_length = p-filepath; dirname = jnlib_xmalloc(dirname_length+1); strncpy(dirname, filepath, dirname_length); dirname[dirname_length] = 0; return dirname; } /**************** * Construct a filename from the NULL terminated list of parts. * Tilde expansion is done here. */ char * make_filename( const char *first_part, ... ) { va_list arg_ptr ; size_t n; const char *s; char *name, *home, *p; va_start( arg_ptr, first_part ) ; n = strlen(first_part)+1; while( (s=va_arg(arg_ptr, const char *)) ) n += strlen(s) + 1; va_end(arg_ptr); home = NULL; if( *first_part == '~' && first_part[1] == '/' && (home = getenv("HOME")) && *home ) n += strlen(home); name = jnlib_xmalloc(n); p = home ? stpcpy(stpcpy(name,home), first_part+1) : stpcpy(name, first_part); va_start( arg_ptr, first_part ) ; while( (s=va_arg(arg_ptr, const char *)) ) p = stpcpy(stpcpy(p,"/"), s); va_end(arg_ptr); return name; } int compare_filenames( const char *a, const char *b ) { /* ? check whether this is an absolute filename and * resolve symlinks? */ #ifdef HAVE_DRIVE_LETTERS return stricmp(a,b); #else return strcmp(a,b); #endif } /* Convert 2 hex characters at S to a byte value. Return this value or -1 if there is an error. */ int hextobyte (const char *s) { int c; if ( *s >= '0' && *s <= '9' ) c = 16 * (*s - '0'); else if ( *s >= 'A' && *s <= 'F' ) c = 16 * (10 + *s - 'A'); else if ( *s >= 'a' && *s <= 'f' ) c = 16 * (10 + *s - 'a'); else return -1; s++; if ( *s >= '0' && *s <= '9' ) c += *s - '0'; else if ( *s >= 'A' && *s <= 'F' ) c += 10 + *s - 'A'; else if ( *s >= 'a' && *s <= 'f' ) c += 10 + *s - 'a'; else return -1; return c; } /* Print a BUFFER to stream FP while replacing all control characters and the characters DELIM and DELIM2 with standard C escape sequences. Returns the number of characters printed. */ size_t print_sanitized_buffer2 (FILE *fp, const void *buffer, size_t length, int delim, int delim2) { const unsigned char *p = buffer; size_t count = 0; for (; length; length--, p++, count++) { /* Fixme: Check whether *p < 0xa0 is correct for utf8 encoding. */ if (*p < 0x20 || (*p >= 0x7f && *p < 0xa0) || *p == delim || *p == delim2 || ((delim || delim2) && *p=='\\')) { putc ('\\', fp); count++; if (*p == '\n') { putc ('n', fp); count++; } else if (*p == '\r') { putc ('r', fp); count++; } else if (*p == '\f') { putc ('f', fp); count++; } else if (*p == '\v') { putc ('v', fp); count++; } else if (*p == '\b') { putc ('b', fp); count++; } else if (!*p) { putc('0', fp); count++; } else { fprintf (fp, "x%02x", *p); count += 3; } } else { putc (*p, fp); count++; } } return count; } /* Same as print_sanitized_buffer2 but with just one delimiter. */ size_t print_sanitized_buffer (FILE *fp, const void *buffer, size_t length, int delim) { return print_sanitized_buffer2 (fp, buffer, length, delim, 0); } size_t print_sanitized_utf8_buffer (FILE *fp, const void *buffer, size_t length, int delim) { const char *p = buffer; size_t i; /* We can handle plain ascii simpler, so check for it first. */ for (i=0; i < length; i++ ) { if ( (p[i] & 0x80) ) break; } if (i < length) { char *buf = utf8_to_native (p, length, delim); /*(utf8 conversion already does the control character quoting)*/ i = strlen (buf); fputs (buf, fp); jnlib_free (buf); return i; } else return print_sanitized_buffer (fp, p, length, delim); } size_t print_sanitized_string2 (FILE *fp, const char *string, int delim, int delim2) { return string? print_sanitized_buffer2 (fp, string, strlen (string), delim, delim2):0; } size_t print_sanitized_string (FILE *fp, const char *string, int delim) { return string? print_sanitized_buffer (fp, string, strlen (string), delim):0; } size_t print_sanitized_utf8_string (FILE *fp, const char *string, int delim) { return string? print_sanitized_utf8_buffer (fp, string, strlen (string), delim) : 0; } /* Create a string from the buffer P_ARG of length N which is suitable for printing. Caller must release the created string using xfree. */ char * sanitize_buffer (const void *p_arg, size_t n, int delim) { const unsigned char *p = p_arg; size_t save_n, buflen; const unsigned char *save_p; char *buffer, *d; /* First count length. */ for (save_n = n, save_p = p, buflen=1 ; n; n--, p++ ) { if ( *p < 0x20 || *p == 0x7f || *p == delim || (delim && *p=='\\')) { if ( *p=='\n' || *p=='\r' || *p=='\f' || *p=='\v' || *p=='\b' || !*p ) buflen += 2; else buflen += 5; } else buflen++; } p = save_p; n = save_n; /* And now make the string */ d = buffer = jnlib_xmalloc( buflen ); for ( ; n; n--, p++ ) { if (*p < 0x20 || *p == 0x7f || *p == delim || (delim && *p=='\\')) { *d++ = '\\'; if( *p == '\n' ) *d++ = 'n'; else if( *p == '\r' ) *d++ = 'r'; else if( *p == '\f' ) *d++ = 'f'; else if( *p == '\v' ) *d++ = 'v'; else if( *p == '\b' ) *d++ = 'b'; else if( !*p ) *d++ = '0'; else { sprintf(d, "x%02x", *p ); d += 3; } } else *d++ = *p; } *d = 0; return buffer; } /**************************************************** ********** W32 specific functions **************** ****************************************************/ #ifdef HAVE_W32_SYSTEM const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, DIM (strerr)-1, NULL); return strerr; } #endif /*HAVE_W32_SYSTEM*/ /**************************************************** ******** Locale insensitive ctype functions ******** ****************************************************/ /* FIXME: replace them by a table lookup and macros */ int ascii_isupper (int c) { return c >= 'A' && c <= 'Z'; } int ascii_islower (int c) { return c >= 'a' && c <= 'z'; } int ascii_toupper (int c) { if (c >= 'a' && c <= 'z') c &= ~0x20; return c; } int ascii_tolower (int c) { if (c >= 'A' && c <= 'Z') c |= 0x20; return c; } int ascii_strcasecmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b && ascii_toupper(*a) != ascii_toupper(*b)) break; } return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } int ascii_strncasecmp (const char *a, const char *b, size_t n) { const unsigned char *p1 = (const unsigned char *)a; const unsigned char *p2 = (const unsigned char *)b; unsigned char c1, c2; if (p1 == p2 || !n ) return 0; do { c1 = ascii_tolower (*p1); c2 = ascii_tolower (*p2); if ( !--n || c1 == '\0') break; ++p1; ++p2; } while (c1 == c2); return c1 - c2; } int ascii_memcasecmp (const void *a_arg, const void *b_arg, size_t n ) { const char *a = a_arg; const char *b = b_arg; if (a == b) return 0; for ( ; n; n--, a++, b++ ) { if( *a != *b && ascii_toupper (*a) != ascii_toupper (*b) ) return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } return 0; } int ascii_strcmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b ) break; } return *a == *b? 0 : (*(signed char *)a - *(signed char *)b); } void * ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle) { if (!nneedle) return (void*)haystack; /* finding an empty needle is really easy */ if (nneedle <= nhaystack) { const char *a = haystack; const char *b = a + nhaystack - nneedle; for (; a <= b; a++) { if ( !ascii_memcasecmp (a, needle, nneedle) ) return (void *)a; } } return NULL; } /********************************************* ********** missing string functions ********* *********************************************/ #ifndef HAVE_STPCPY char * stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } #endif #ifndef HAVE_STRLWR char * strlwr(char *s) { char *p; for(p=s; *p; p++ ) *p = tolower(*p); return s; } #endif #ifndef HAVE_STRCASECMP int strcasecmp( const char *a, const char *b ) { for( ; *a && *b; a++, b++ ) { if( *a != *b && toupper(*a) != toupper(*b) ) break; } return *(const byte*)a - *(const byte*)b; } #endif /**************** * mingw32/cpd has a memicmp() */ #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ) { for( ; n; n--, a++, b++ ) if( *a != *b && toupper(*(const byte*)a) != toupper(*(const byte*)b) ) return *(const byte *)a - *(const byte*)b; return 0; } #endif diff --git a/jnlib/stringhelp.h b/jnlib/stringhelp.h index 405d6dbc4..b8f4dbec0 100644 --- a/jnlib/stringhelp.h +++ b/jnlib/stringhelp.h @@ -1,100 +1,101 @@ /* stringhelp.h * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_STRINGHELP_H #define LIBJNLIB_STRINGHELP_H #include "types.h" const char *memistr (const void *buf, size_t buflen, const char *sub); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); char *trim_trailing_spaces( char *string ); unsigned int trim_trailing_chars( unsigned char *line, unsigned len, const char *trimchars); unsigned int trim_trailing_ws( unsigned char *line, unsigned len ); size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ); size_t length_sans_trailing_ws (const unsigned char *line, size_t len); char *make_basename(const char *filepath, const char *inputpath); char *make_dirname(const char *filepath); char *make_filename( const char *first_part, ... ); int compare_filenames( const char *a, const char *b ); int hextobyte (const char *s); size_t print_sanitized_buffer (FILE *fp, const void *buffer, size_t length, int delim); size_t print_sanitized_buffer2 (FILE *fp, const void *buffer, size_t length, int delim, int delim2); size_t print_sanitized_utf8_buffer (FILE *fp, const void *buffer, size_t length, int delim); size_t print_sanitized_string (FILE *fp, const char *string, int delim); size_t print_sanitized_string2 (FILE *fp, const char *string, int delim, int delim2); size_t print_sanitized_utf8_string (FILE *fp, const char *string, int delim); char *sanitize_buffer (const void *p, size_t n, int delim); #ifdef HAVE_W32_SYSTEM const char *w32_strerror (int ec); #endif int ascii_isupper (int c); int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp (const char *a, const char *b, size_t n); int ascii_memcasecmp( const void *a, const void *b, size_t n ); const char *ascii_memistr ( const void *buf, size_t buflen, const char *sub); void *ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle); #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ); #endif #ifndef HAVE_STPCPY char *stpcpy(char *a,const char *b); #endif #ifndef HAVE_STRLWR char *strlwr(char *a); #endif #ifndef HAVE_STRTOUL #define strtoul(a,b,c) ((unsigned long)strtol((a),(b),(c))) #endif #ifndef HAVE_MEMMOVE #define memmove(d, s, n) bcopy((s), (d), (n)) #endif #ifndef HAVE_STRICMP #define stricmp(a,b) strcasecmp( (a), (b) ) #endif #ifndef STR #define STR(v) #v #endif #define STR2(v) STR(v) #endif /*LIBJNLIB_STRINGHELP_H*/ diff --git a/jnlib/strlist.c b/jnlib/strlist.c index 52b4d5869..87e121705 100644 --- a/jnlib/strlist.c +++ b/jnlib/strlist.c @@ -1,176 +1,177 @@ /* strlist.c - string helpers * Copyright (C) 1998, 2000, 2001, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "libjnlib-config.h" #include "strlist.h" #ifdef JNLIB_NEED_UTF8CONV #include "utf8conv.h" #endif void free_strlist( strlist_t sl ) { strlist_t sl2; for(; sl; sl = sl2 ) { sl2 = sl->next; jnlib_free(sl); } } strlist_t add_to_strlist( strlist_t *list, const char *string ) { strlist_t sl; sl = jnlib_xmalloc( sizeof *sl + strlen(string)); sl->flags = 0; strcpy(sl->d, string); sl->next = *list; *list = sl; return sl; } /* Same as add_to_strlist() but if is_utf8 is *not* set, a conversion to UTF-8 is done. */ #ifdef JNLIB_NEED_UTF8CONV strlist_t add_to_strlist2( strlist_t *list, const char *string, int is_utf8 ) { strlist_t sl; if (is_utf8) sl = add_to_strlist( list, string ); else { char *p = native_to_utf8( string ); sl = add_to_strlist( list, p ); jnlib_free ( p ); } return sl; } #endif /* JNLIB_NEED_UTF8CONV*/ strlist_t append_to_strlist( strlist_t *list, const char *string ) { strlist_t r, sl; sl = jnlib_xmalloc( sizeof *sl + strlen(string)); sl->flags = 0; strcpy(sl->d, string); sl->next = NULL; if( !*list ) *list = sl; else { for( r = *list; r->next; r = r->next ) ; r->next = sl; } return sl; } #ifdef JNLIB_NEED_UTF8CONV strlist_t append_to_strlist2( strlist_t *list, const char *string, int is_utf8 ) { strlist_t sl; if( is_utf8 ) sl = append_to_strlist( list, string ); else { char *p = native_to_utf8 (string); sl = append_to_strlist( list, p ); jnlib_free( p ); } return sl; } #endif /* JNLIB_NEED_UTF8CONV */ /* Return a copy of LIST. */ strlist_t strlist_copy (strlist_t list) { strlist_t newlist = NULL, sl, *last; last = &newlist; for (; list; list = list->next) { sl = jnlib_xmalloc (sizeof *sl + strlen (list->d)); sl->flags = list->flags; strcpy(sl->d, list->d); sl->next = NULL; *last = sl; last = &sl; } return newlist; } strlist_t strlist_prev( strlist_t head, strlist_t node ) { strlist_t n; for(n=NULL; head && head != node; head = head->next ) n = head; return n; } strlist_t strlist_last( strlist_t node ) { if( node ) for( ; node->next ; node = node->next ) ; return node; } char * strlist_pop (strlist_t *list) { char *str=NULL; strlist_t sl=*list; if(sl) { str=jnlib_xmalloc(strlen(sl->d)+1); strcpy(str,sl->d); *list=sl->next; jnlib_free(sl); } return str; } diff --git a/jnlib/strlist.h b/jnlib/strlist.h index 3c1252a44..ee9f1fa60 100644 --- a/jnlib/strlist.h +++ b/jnlib/strlist.h @@ -1,50 +1,51 @@ /* strlist.h * Copyright (C) 1998, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_STRLIST_H #define LIBJNLIB_STRLIST_H struct string_list { struct string_list *next; unsigned int flags; char d[1]; }; typedef struct string_list *STRLIST; /* Deprecated. */ typedef struct string_list *strlist_t; void free_strlist (strlist_t sl); strlist_t add_to_strlist (strlist_t *list, const char *string); strlist_t add_to_strlist2( strlist_t *list, const char *string, int is_utf8); strlist_t append_to_strlist (strlist_t *list, const char *string); strlist_t append_to_strlist2 (strlist_t *list, const char *string, int is_utf8); strlist_t strlist_copy (strlist_t list); strlist_t strlist_prev (strlist_t head, strlist_t node); strlist_t strlist_last (strlist_t node); char * strlist_pop (strlist_t *list); #define FREE_STRLIST(a) do { free_strlist((a)); (a) = NULL ; } while(0) #endif /*LIBJNLIB_STRLIST_H*/ diff --git a/jnlib/types.h b/jnlib/types.h index 934b7a6ee..89d245943 100644 --- a/jnlib/types.h +++ b/jnlib/types.h @@ -1,105 +1,106 @@ /* types.h * Copyright (C) 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_TYPES_H #define LIBJNLIB_TYPES_H /* The AC_CHECK_SIZEOF() in configure fails for some machines. * we provide some fallback values here */ #if !SIZEOF_UNSIGNED_SHORT #undef SIZEOF_UNSIGNED_SHORT #define SIZEOF_UNSIGNED_SHORT 2 #endif #if !SIZEOF_UNSIGNED_INT #undef SIZEOF_UNSIGNED_INT #define SIZEOF_UNSIGNED_INT 4 #endif #if !SIZEOF_UNSIGNED_LONG #undef SIZEOF_UNSIGNED_LONG #define SIZEOF_UNSIGNED_LONG 4 #endif #include #ifndef HAVE_BYTE_TYPEDEF #undef byte /* maybe there is a macro with this name */ /* Windows typedefs byte in the rpc headers. Avoid warning about double definition. */ #if !(defined(_WIN32) && defined(cbNDRContext)) typedef unsigned char byte; #endif #define HAVE_BYTE_TYPEDEF #endif #ifndef HAVE_USHORT_TYPEDEF #undef ushort /* maybe there is a macro with this name */ typedef unsigned short ushort; #define HAVE_USHORT_TYPEDEF #endif #ifndef HAVE_ULONG_TYPEDEF #undef ulong /* maybe there is a macro with this name */ typedef unsigned long ulong; #define HAVE_ULONG_TYPEDEF #endif #ifndef HAVE_U16_TYPEDEF #undef u16 /* maybe there is a macro with this name */ #if SIZEOF_UNSIGNED_INT == 2 typedef unsigned int u16; #elif SIZEOF_UNSIGNED_SHORT == 2 typedef unsigned short u16; #else #error no typedef for u16 #endif #define HAVE_U16_TYPEDEF #endif #ifndef HAVE_U32_TYPEDEF #undef u32 /* maybe there is a macro with this name */ #if SIZEOF_UNSIGNED_INT == 4 typedef unsigned int u32; #elif SIZEOF_UNSIGNED_LONG == 4 typedef unsigned long u32; #else #error no typedef for u32 #endif #define HAVE_U32_TYPEDEF #endif #ifndef HAVE_U64_TYPEDEF #undef u64 /* maybe there is a macro with this name */ #if SIZEOF_UNSIGNED_INT == 8 typedef unsigned int u64; #define HAVE_U64_TYPEDEF #elif SIZEOF_UNSIGNED_LONG == 8 typedef unsigned long u64; #define HAVE_U64_TYPEDEF #elif __GNUC__ >= 2 || defined(__SUNPRO_C) typedef unsigned long long u64; #define HAVE_U64_TYPEDEF #endif #endif #endif /*LIBJNLIB_TYPES_H*/ diff --git a/jnlib/utf8conv.c b/jnlib/utf8conv.c index 4df8b7b32..9fba1ed4f 100644 --- a/jnlib/utf8conv.c +++ b/jnlib/utf8conv.c @@ -1,451 +1,452 @@ /* utf8conf.c - UTF8 character set conversion * Copyright (C) 1994, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #ifdef HAVE_LANGINFO_CODESET #include #endif #include "libjnlib-config.h" #include "stringhelp.h" #include "utf8conv.h" static ushort koi8_unicode[128] = { 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, 0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, 0x255e, 0x255f, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x00a9, 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a }; static ushort latin2_unicode[128] = { 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, 0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7, 0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B, 0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7, 0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C, 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9 }; static const char *active_charset_name = "iso-8859-1"; static ushort *active_charset = NULL; static int no_translation = 0; int set_native_charset (const char *newset) { if (!newset) #ifdef HAVE_LANGINFO_CODESET newset = nl_langinfo (CODESET); #else newset = "8859-1"; #endif if (strlen (newset) > 3 && !ascii_memcasecmp (newset, "iso", 3)) { newset += 3; if (*newset == '-' || *newset == '_') newset++; } if (!*newset || !ascii_strcasecmp (newset, "8859-1") || !ascii_strcasecmp (newset, "8859-15")) { active_charset_name = "iso-8859-1"; no_translation = 0; active_charset = NULL; } else if (!ascii_strcasecmp (newset, "8859-2")) { active_charset_name = "iso-8859-2"; no_translation = 0; active_charset = latin2_unicode; } else if (!ascii_strcasecmp (newset, "koi8-r")) { active_charset_name = "koi8-r"; no_translation = 0; active_charset = koi8_unicode; } else if (!ascii_strcasecmp (newset, "utf8") || !ascii_strcasecmp (newset, "utf-8")) { active_charset_name = "utf-8"; no_translation = 1; active_charset = NULL; } else return -1; return 0; } const char * get_native_charset () { return active_charset_name; } /**************** * Convert string, which is in native encoding to UTF8 and return the * new allocated UTF8 string. */ char * native_to_utf8 (const char *orig_string) { const unsigned char *string = (const unsigned char *)orig_string; const unsigned char *s; char *buffer; unsigned char *p; size_t length = 0; if (no_translation) { buffer = jnlib_xstrdup (orig_string); } else if (active_charset) { for (s = string; *s; s++) { length++; if (*s & 0x80) length += 2; /* we may need 3 bytes */ } buffer = jnlib_xmalloc (length + 1); for (p = (unsigned char *)buffer, s = string; *s; s++) { if ((*s & 0x80)) { ushort val = active_charset[*s & 0x7f]; if (val < 0x0800) { *p++ = 0xc0 | ((val >> 6) & 0x1f); *p++ = 0x80 | (val & 0x3f); } else { *p++ = 0xe0 | ((val >> 12) & 0x0f); *p++ = 0x80 | ((val >> 6) & 0x3f); *p++ = 0x80 | (val & 0x3f); } } else *p++ = *s; } *p = 0; } else { for (s = string; *s; s++) { length++; if (*s & 0x80) length++; } buffer = jnlib_xmalloc (length + 1); for (p = (unsigned char *)buffer, s = string; *s; s++) { if (*s & 0x80) { *p++ = 0xc0 | ((*s >> 6) & 3); *p++ = 0x80 | (*s & 0x3f); } else *p++ = *s; } *p = 0; } return buffer; } /* Convert string, which is in UTF8 to native encoding. Replace * illegal encodings by some "\xnn" and quote all control * characters. A character with value DELIM will always be quoted, it * must be a vanilla ASCII character. */ char * utf8_to_native (const char *string, size_t length, int delim) { int nleft; int i; unsigned char encbuf[8]; int encidx; const byte *s; size_t n; char *buffer = NULL; char *p = NULL; unsigned long val = 0; size_t slen; int resync = 0; /* 1. pass (p==NULL): count the extended utf-8 characters */ /* 2. pass (p!=NULL): create string */ for (;;) { for (slen = length, nleft = encidx = 0, n = 0, s = (const unsigned char *)string; slen; s++, slen--) { if (resync) { if (!(*s < 128 || (*s >= 0xc0 && *s <= 0xfd))) { /* still invalid */ if (p) { sprintf (p, "\\x%02x", *s); p += 4; } n += 4; continue; } resync = 0; } if (!nleft) { if (!(*s & 0x80)) { /* plain ascii */ if (*s < 0x20 || *s == 0x7f || *s == delim || (delim && *s == '\\')) { n++; if (p) *p++ = '\\'; switch (*s) { case '\n': n++; if (p) *p++ = 'n'; break; case '\r': n++; if (p) *p++ = 'r'; break; case '\f': n++; if (p) *p++ = 'f'; break; case '\v': n++; if (p) *p++ = 'v'; break; case '\b': n++; if (p) *p++ = 'b'; break; case 0: n++; if (p) *p++ = '0'; break; default: n += 3; if (p) { sprintf (p, "x%02x", *s); p += 3; } break; } } else { if (p) *p++ = *s; n++; } } else if ((*s & 0xe0) == 0xc0) { /* 110x xxxx */ val = *s & 0x1f; nleft = 1; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xf0) == 0xe0) { /* 1110 xxxx */ val = *s & 0x0f; nleft = 2; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xf8) == 0xf0) { /* 1111 0xxx */ val = *s & 0x07; nleft = 3; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xfc) == 0xf8) { /* 1111 10xx */ val = *s & 0x03; nleft = 4; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xfe) == 0xfc) { /* 1111 110x */ val = *s & 0x01; nleft = 5; encidx = 0; encbuf[encidx++] = *s; } else { /* invalid encoding: print as \xnn */ if (p) { sprintf (p, "\\x%02x", *s); p += 4; } n += 4; resync = 1; } } else if (*s < 0x80 || *s >= 0xc0) { /* invalid */ if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } sprintf (p, "\\x%02x", *s); p += 4; } n += 4 + 4 * encidx; nleft = 0; encidx = 0; resync = 1; } else { encbuf[encidx++] = *s; val <<= 6; val |= *s & 0x3f; if (!--nleft) { /* ready */ if (no_translation) { if (p) { for (i = 0; i < encidx; i++) *p++ = encbuf[i]; } n += encidx; encidx = 0; } else if (active_charset) { /* table lookup */ for (i = 0; i < 128; i++) { if (active_charset[i] == val) break; } if (i < 128) { /* we can print this one */ if (p) *p++ = i + 128; n++; } else { /* we do not have a translation: print utf8 */ if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } } n += encidx * 4; encidx = 0; } } else { /* native set */ if (val >= 0x80 && val < 256) { n++; /* we can simply print this character */ if (p) *p++ = val; } else { /* we do not have a translation: print utf8 */ if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } } n += encidx * 4; encidx = 0; } } } } } if (!buffer) { /* allocate the buffer after the first pass */ buffer = p = jnlib_xmalloc (n + 1); } else { *p = 0; /* make a string */ return buffer; } } } diff --git a/jnlib/utf8conv.h b/jnlib/utf8conv.h index 6e2ce9944..344c47f92 100644 --- a/jnlib/utf8conv.h +++ b/jnlib/utf8conv.h @@ -1,31 +1,32 @@ /* utf8conf.h * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_UTF8CONF_H #define LIBJNLIB_UTF8CONF_H int set_native_charset (const char *newset); const char *get_native_charset (void); char *native_to_utf8 (const char *string); char *utf8_to_native (const char *string, size_t length, int delim); #endif /*LIBJNLIB_UTF8CONF_H*/ diff --git a/jnlib/w32-afunix.c b/jnlib/w32-afunix.c index c93d389da..84d799f1f 100644 --- a/jnlib/w32-afunix.c +++ b/jnlib/w32-afunix.c @@ -1,116 +1,117 @@ /* w32-afunix.c * Copyright (C) 2004 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef _WIN32 #include #include #include #include "w32-afunix.h" int _w32_close (int fd) { int rc = closesocket (fd); if (rc && WSAGetLastError () == WSAENOTSOCK) rc = close (fd); return rc; } int _w32_sock_new (int domain, int type, int proto) { if (domain == AF_UNIX || domain == AF_LOCAL) domain = AF_INET; return socket (domain, type, proto); } int _w32_sock_connect (int sockfd, struct sockaddr * addr, int addrlen) { struct sockaddr_in myaddr; struct sockaddr_un * unaddr; FILE * fp; int port; unaddr = (struct sockaddr_un *)addr; fp = fopen (unaddr->sun_path, "rb"); if (!fp) return -1; fscanf (fp, "%d", &port); fclose (fp); /* XXX: set errno in this case */ if (port < 0 || port > 65535) return -1; myaddr.sin_family = AF_INET; myaddr.sin_port = port; myaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); /* we need this later. */ unaddr->sun_family = myaddr.sin_family; unaddr->sun_port = myaddr.sin_port; unaddr->sun_addr.s_addr = myaddr.sin_addr.s_addr; return connect (sockfd, (struct sockaddr *)&myaddr, sizeof myaddr); } int _w32_sock_bind (int sockfd, struct sockaddr * addr, int addrlen) { if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX) { struct sockaddr_in myaddr; struct sockaddr_un * unaddr; FILE * fp; int len = sizeof myaddr; int rc; myaddr.sin_port = 0; myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); rc = bind (sockfd, (struct sockaddr *)&myaddr, len); if (rc) return rc; rc = getsockname (sockfd, (struct sockaddr *)&myaddr, &len); if (rc) return rc; unaddr = (struct sockaddr_un *)addr; fp = fopen (unaddr->sun_path, "wb"); if (!fp) return -1; fprintf (fp, "%d", myaddr.sin_port); fclose (fp); /* we need this later. */ unaddr->sun_family = myaddr.sin_family; unaddr->sun_port = myaddr.sin_port; unaddr->sun_addr.s_addr = myaddr.sin_addr.s_addr; return 0; } return bind (sockfd, addr, addrlen); } #endif /*_WIN32*/ diff --git a/jnlib/w32-afunix.h b/jnlib/w32-afunix.h index 367832299..d0eb8cf7e 100644 --- a/jnlib/w32-afunix.h +++ b/jnlib/w32-afunix.h @@ -1,48 +1,49 @@ /* w32-afunix.h * Copyright (C) 2004 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef _WIN32 #ifndef W32AFUNIX_DEFS_H #define W32AFUNIX_DEFS_H #include #include #include #include #define DIRSEP_C '\\' #define AF_LOCAL AF_UNIX /* We need to prefix the structure with a sockaddr_in header so we can use it later for sendto and recvfrom. */ struct sockaddr_un { short sun_family; unsigned short sun_port; struct in_addr sun_addr; char sun_path[108-2-4]; /* Path name. */ }; int _w32_close (int fd); int _w32_sock_new (int domain, int type, int proto); int _w32_sock_bind (int sockfd, struct sockaddr *addr, int addrlen); int _w32_sock_connect (int sockfd, struct sockaddr *addr, int addrlen); #endif /*W32AFUNIX_DEFS_H*/ #endif /*_WIN32*/ diff --git a/jnlib/w32-pth.c b/jnlib/w32-pth.c index 2f041c490..4107c7cb3 100644 --- a/jnlib/w32-pth.c +++ b/jnlib/w32-pth.c @@ -1,1499 +1,1500 @@ /* w32-pth.c - GNU Pth emulation for W32 (MS Windows). * Copyright (c) 1999-2003 Ralf S. Engelschall * Copyright (C) 2004 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * ------------------------------------------------------------------ * This code is based on Ralf Engelschall's GNU Pth, a non-preemptive * thread scheduling library which can be found at * http://www.gnu.org/software/pth/. MS Windows (W32) specific code * written by Timo Schulz, g10 Code. */ #include #ifdef HAVE_W32_SYSTEM #include #include #include #include #include #include "logging.h" /* For log_get_prefix () */ /* We don't want to have any Windows specific code in the header, thus we use a macro which defaults to a compatible type in w32-pth.h. */ #define W32_PTH_HANDLE_INTERNAL HANDLE #include "w32-pth.h" #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #if FALSE != 0 || TRUE != 1 #error TRUE or FALSE defined to wrong values #endif /* States whether this module has been initialized. */ static int pth_initialized; /* Keeps the current debug level. Define marcos to test them. */ static int debug_level; #define DBG_ERROR (debug_level >= 1) #define DBG_INFO (debug_level >= 2) #define DBG_CALLS (debug_level >= 3) /* Variables to support event handling. */ static int pth_signo; static HANDLE pth_signo_ev; /* Mutex to make sure only one thread is running. */ static CRITICAL_SECTION pth_shd; /* Events are store in a double linked event ring. */ struct pth_event_s { struct pth_event_s * next; struct pth_event_s * prev; HANDLE hd; union { struct sigset_s * sig; int fd; struct timeval tv; pth_mutex_t * mx; } u; int * val; int u_type; int flags; }; struct pth_attr_s { unsigned int flags; unsigned int stack_size; char * name; }; /* Object to keep information about a thread. This may eventually be used to implement a scheduler queue. */ struct thread_info_s { void *(*thread)(void *); /* The actual thread fucntion. */ void * arg; /* The argument passed to that fucntion. */ int joinable; /* True if this Thread is joinable. */ HANDLE th; /* Handle of this thread. Used by non-joinable threads to close the handle. */ }; /* Convenience macro to startup the system. */ #define implicit_init() do { if (!pth_initialized) pth_init(); } while (0) /* Prototypes. */ static pth_event_t do_pth_event (unsigned long spec, ...); static unsigned int do_pth_waitpid (unsigned pid, int * status, int options); static int do_pth_wait (pth_event_t ev); static int do_pth_event_status (pth_event_t ev); static void *launch_thread (void * ctx); int pth_init (void) { SECURITY_ATTRIBUTES sa; WSADATA wsadat; const char *s; if (pth_initialized) return TRUE; debug_level = (s=getenv ("DEBUG_PTH"))? atoi (s):0; if (debug_level) fprintf (stderr, "%s: pth_init: called.\n", log_get_prefix (NULL)); if (WSAStartup (0x202, &wsadat)) return FALSE; pth_signo = 0; InitializeCriticalSection (&pth_shd); if (pth_signo_ev) CloseHandle (pth_signo_ev); memset (&sa, 0, sizeof sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof sa; pth_signo_ev = CreateEvent (&sa, TRUE, FALSE, NULL); if (!pth_signo_ev) return FALSE; pth_initialized = 1; EnterCriticalSection (&pth_shd); return TRUE; } int pth_kill (void) { pth_signo = 0; if (pth_signo_ev) { CloseHandle (pth_signo_ev); pth_signo_ev = NULL; } if (pth_initialized) DeleteCriticalSection (&pth_shd); WSACleanup (); pth_initialized = 0; return TRUE; } static char * w32_strerror (char *strerr, size_t strerrsize) { if (strerrsize > 1) FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, (int)GetLastError (), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, strerrsize, NULL); return strerr; } static void enter_pth (const char *function) { /* Fixme: I am not sure whether the same thread my enter a critical section twice. */ if (DBG_CALLS) fprintf (stderr, "%s: enter_pth (%s)\n", log_get_prefix (NULL), function? function:""); LeaveCriticalSection (&pth_shd); } static void leave_pth (const char *function) { EnterCriticalSection (&pth_shd); if (DBG_CALLS) fprintf (stderr, "%s: leave_pth (%s)\n", log_get_prefix (NULL), function? function:""); } long pth_ctrl (unsigned long query, ...) { implicit_init (); switch (query) { case PTH_CTRL_GETAVLOAD: case PTH_CTRL_GETPRIO: case PTH_CTRL_GETNAME: case PTH_CTRL_GETTHREADS_NEW: case PTH_CTRL_GETTHREADS_READY: case PTH_CTRL_GETTHREADS_RUNNING: case PTH_CTRL_GETTHREADS_WAITING: case PTH_CTRL_GETTHREADS_SUSPENDED: case PTH_CTRL_GETTHREADS_DEAD: case PTH_CTRL_GETTHREADS: default: return -1; } return 0; } pth_time_t pth_timeout (long sec, long usec) { pth_time_t tvd; tvd.tv_sec = sec; tvd.tv_usec = usec; return tvd; } int pth_read_ev (int fd, void *buffer, size_t size, pth_event_t ev) { implicit_init (); return 0; } int pth_read (int fd, void * buffer, size_t size) { int n; implicit_init (); enter_pth (__FUNCTION__); n = recv (fd, buffer, size, 0); if (n == -1 && WSAGetLastError () == WSAENOTSOCK) { DWORD nread = 0; n = ReadFile ((HANDLE)fd, buffer, size, &nread, NULL); if (!n) { char strerr[256]; if (DBG_ERROR) fprintf (stderr, "%s: pth_read(%d) failed read from file: %s\n", log_get_prefix (NULL), fd, w32_strerror (strerr, sizeof strerr)); n = -1; } else n = (int)nread; } leave_pth (__FUNCTION__); return n; } int pth_write_ev (int fd, const void *buffer, size_t size, pth_event_t ev) { implicit_init (); return 0; } int pth_write (int fd, const void * buffer, size_t size) { int n; implicit_init (); enter_pth (__FUNCTION__); n = send (fd, buffer, size, 0); if (n == -1 && WSAGetLastError () == WSAENOTSOCK) { DWORD nwrite; char strerr[256]; /* This is no real error because we first need to figure out if we have a handle or a socket. */ n = WriteFile ((HANDLE)fd, buffer, size, &nwrite, NULL); if (!n) { if (DBG_ERROR) fprintf (stderr, "%s: pth_write(%d) failed in write: %s\n", log_get_prefix (NULL), fd, w32_strerror (strerr, sizeof strerr)); n = -1; } else n = (int)nwrite; } leave_pth (__FUNCTION__); return n; } int pth_select (int nfds, fd_set * rfds, fd_set * wfds, fd_set * efds, const struct timeval * timeout) { int n; implicit_init (); enter_pth (__FUNCTION__); n = select (nfds, rfds, wfds, efds, timeout); leave_pth (__FUNCTION__); return n; } int pth_fdmode (int fd, int mode) { unsigned long val; int ret = PTH_FDMODE_BLOCK; implicit_init (); /* Note: we don't do the eter/leave pth here because this is for one a fast fucntion and secondly already called from inside such a block. */ /* XXX: figure out original fd mode */ switch (mode) { case PTH_FDMODE_NONBLOCK: val = 1; if (ioctlsocket (fd, FIONBIO, &val) == SOCKET_ERROR) ret = PTH_FDMODE_ERROR; break; case PTH_FDMODE_BLOCK: val = 0; if (ioctlsocket (fd, FIONBIO, &val) == SOCKET_ERROR) ret = PTH_FDMODE_ERROR; break; } return ret; } int pth_accept (int fd, struct sockaddr *addr, int *addrlen) { int rc; implicit_init (); enter_pth (__FUNCTION__); rc = accept (fd, addr, addrlen); leave_pth (__FUNCTION__); return rc; } int pth_accept_ev (int fd, struct sockaddr *addr, int *addrlen, pth_event_t ev_extra) { pth_key_t ev_key; pth_event_t ev; int rv; int fdmode; implicit_init (); enter_pth (__FUNCTION__); fdmode = pth_fdmode (fd, PTH_FDMODE_NONBLOCK); if (fdmode == PTH_FDMODE_ERROR) { leave_pth (__FUNCTION__); return -1; } ev = NULL; while ((rv = accept (fd, addr, addrlen)) == -1 && (WSAGetLastError () == WSAEINPROGRESS || WSAGetLastError () == WSAEWOULDBLOCK)) { if (!ev) { ev = do_pth_event (PTH_EVENT_FD|PTH_UNTIL_FD_READABLE| PTH_MODE_STATIC, &ev_key, fd); if (!ev) { leave_pth (__FUNCTION__); return -1; } if (ev_extra) pth_event_concat (ev, ev_extra, NULL); } /* Wait until accept has a chance. */ do_pth_wait (ev); if (ev_extra) { pth_event_isolate (ev); if (do_pth_event_status (ev) != PTH_STATUS_OCCURRED) { pth_fdmode (fd, fdmode); leave_pth (__FUNCTION__); return -1; } } } pth_fdmode (fd, fdmode); leave_pth (__FUNCTION__); return rv; } int pth_connect (int fd, struct sockaddr *name, int namelen) { int rc; implicit_init (); enter_pth (__FUNCTION__); rc = connect (fd, name, namelen); leave_pth (__FUNCTION__); return rc; } int pth_mutex_release (pth_mutex_t *mutex) { int rc; implicit_init (); enter_pth (__FUNCTION__); if (!ReleaseMutex (*mutex)) { char strerr[256]; if (DBG_ERROR) fprintf (stderr, "%s: pth_release_mutex %p failed: %s\n", log_get_prefix (NULL), *mutex, w32_strerror (strerr, sizeof strerr)); rc = FALSE; } else rc = TRUE; leave_pth (__FUNCTION__); return rc; } int pth_mutex_acquire (pth_mutex_t *mutex, int tryonly, pth_event_t ev_extra) { int code; int rc; implicit_init (); enter_pth (__FUNCTION__); /* FIXME: ev_extra is not yet supported. */ code = WaitForSingleObject (*mutex, INFINITE); switch (code) { case WAIT_FAILED: { char strerr[256]; if (DBG_ERROR) fprintf (stderr, "%s: pth_mutex_acquire for %p failed: %s\n", log_get_prefix (NULL), *mutex, w32_strerror (strerr, sizeof strerr)); } rc = FALSE; break; case WAIT_OBJECT_0: rc = TRUE; break; default: if (DBG_ERROR) fprintf (stderr, "%s: WaitForSingleObject returned unexpected " "code %d for mutex %p\n", log_get_prefix (NULL), code, *mutex); rc = FALSE; break; } leave_pth (__FUNCTION__); return rc; } int pth_mutex_init (pth_mutex_t *mutex) { SECURITY_ATTRIBUTES sa; implicit_init (); enter_pth (__FUNCTION__); memset (&sa, 0, sizeof sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof sa; *mutex = CreateMutex (&sa, FALSE, NULL); if (!*mutex) { free (*mutex); *mutex = NULL; leave_pth (__FUNCTION__); return FALSE; } leave_pth (__FUNCTION__); return TRUE; } pth_attr_t pth_attr_new (void) { pth_attr_t hd; implicit_init (); hd = calloc (1, sizeof *hd); return hd; } int pth_attr_destroy (pth_attr_t hd) { if (!hd) return -1; implicit_init (); if (hd->name) free (hd->name); free (hd); return TRUE; } int pth_attr_set (pth_attr_t hd, int field, ...) { va_list args; char * str; int val; int rc = TRUE; implicit_init (); va_start (args, field); switch (field) { case PTH_ATTR_JOINABLE: val = va_arg (args, int); if (val) { hd->flags |= PTH_ATTR_JOINABLE; if (DBG_INFO) fprintf (stderr, "%s: pth_attr_set: PTH_ATTR_JOINABLE\n", log_get_prefix (NULL)); } break; case PTH_ATTR_STACK_SIZE: val = va_arg (args, int); if (val) { hd->flags |= PTH_ATTR_STACK_SIZE; hd->stack_size = val; if (DBG_INFO) fprintf (stderr, "%s: pth_attr_set: PTH_ATTR_STACK_SIZE %d\n", log_get_prefix (NULL), val); } break; case PTH_ATTR_NAME: str = va_arg (args, char*); if (hd->name) free (hd->name); if (str) { hd->name = strdup (str); if (!hd->name) return FALSE; hd->flags |= PTH_ATTR_NAME; if (DBG_INFO) fprintf (stderr, "%s: pth_attr_set: PTH_ATTR_NAME %s\n", log_get_prefix (NULL), hd->name); } break; default: rc = FALSE; break; } va_end (args); return rc; } static pth_t do_pth_spawn (pth_attr_t hd, void *(*func)(void *), void *arg) { SECURITY_ATTRIBUTES sa; DWORD tid; HANDLE th; struct thread_info_s *ctx; if (!hd) return NULL; memset (&sa, 0, sizeof sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof sa; ctx = calloc (1, sizeof *ctx); if (!ctx) return NULL; ctx->thread = func; ctx->arg = arg; ctx->joinable = (hd->flags & PTH_ATTR_JOINABLE); /* XXX: we don't use all thread attributes. */ /* Note that we create the thread suspended so that we are able to store the thread's handle in the context structure. We need to do this to be able to close the handle from the launch helper. FIXME: We should no use th W32's Thread handle directly but keep our own thread control structure. CTX may be used for that. */ if (DBG_INFO) fprintf (stderr, "%s: do_pth_spawn creating thread ...\n", log_get_prefix (NULL)); th = CreateThread (&sa, hd->stack_size, (LPTHREAD_START_ROUTINE)launch_thread, ctx, CREATE_SUSPENDED, &tid); ctx->th = th; if (DBG_INFO) fprintf (stderr, "%s: do_pth_spawn created thread %p\n", log_get_prefix (NULL),th); if (!th) free (ctx); else ResumeThread (th); return th; } pth_t pth_spawn (pth_attr_t hd, void *(*func)(void *), void *arg) { HANDLE th; if (!hd) return NULL; implicit_init (); enter_pth (__FUNCTION__); th = do_pth_spawn (hd, func, arg); leave_pth (__FUNCTION__); return th; } pth_t pth_self (void) { return GetCurrentThread (); } int pth_join (pth_t hd, void **value) { return TRUE; } /* friendly */ int pth_cancel (pth_t hd) { if (!hd) return -1; implicit_init (); enter_pth (__FUNCTION__); WaitForSingleObject (hd, 1000); TerminateThread (hd, 0); leave_pth (__FUNCTION__); return TRUE; } /* cruel */ int pth_abort (pth_t hd) { if (!hd) return -1; implicit_init (); enter_pth (__FUNCTION__); TerminateThread (hd, 0); leave_pth (__FUNCTION__); return TRUE; } void pth_exit (void *value) { implicit_init (); enter_pth (__FUNCTION__); pth_kill (); leave_pth (__FUNCTION__); exit ((int)(long)value); } static unsigned int do_pth_waitpid (unsigned pid, int * status, int options) { #if 0 pth_event_t ev; static pth_key_t ev_key = PTH_KEY_INIT; pid_t pid; pth_debug2("pth_waitpid: called from thread \"%s\"", pth_current->name); for (;;) { /* do a non-blocking poll for the pid */ while ( (pid = pth_sc(waitpid)(wpid, status, options|WNOHANG)) < 0 && errno == EINTR) ; /* if pid was found or caller requested a polling return immediately */ if (pid == -1 || pid > 0 || (pid == 0 && (options & WNOHANG))) break; /* else wait a little bit */ ev = pth_event(PTH_EVENT_TIME|PTH_MODE_STATIC, &ev_key, pth_timeout (0,250000)); pth_wait(ev); } pth_debug2("pth_waitpid: leave to thread \"%s\"", pth_current->name); #endif return 0; } unsigned int pth_waitpid (unsigned pid, int * status, int options) { unsigned int n; implicit_init (); enter_pth (__FUNCTION__); n = do_pth_waitpid (pid, status, options); leave_pth (__FUNCTION__); return n; } static BOOL WINAPI sig_handler (DWORD signo) { switch (signo) { case CTRL_C_EVENT: pth_signo = SIGINT; break; case CTRL_BREAK_EVENT: pth_signo = SIGTERM; break; } SetEvent (pth_signo_ev); if (DBG_INFO) fprintf (stderr, "%s: sig_handler=%d\n", log_get_prefix (NULL), pth_signo); return TRUE; } static pth_event_t do_pth_event_body (unsigned long spec, va_list arg) { SECURITY_ATTRIBUTES sa; pth_event_t ev; int rc; if (DBG_INFO) fprintf (stderr, "%s: pth_event spec=%lu\n", log_get_prefix (NULL), spec); ev = calloc (1, sizeof *ev); if (!ev) return NULL; if (spec == 0) ; else if (spec & PTH_EVENT_SIGS) { ev->u.sig = va_arg (arg, struct sigset_s *); ev->u_type = PTH_EVENT_SIGS; ev->val = va_arg (arg, int *); rc = SetConsoleCtrlHandler (sig_handler, TRUE); if (DBG_INFO) fprintf (stderr, "%s: pth_event: sigs rc=%d\n", log_get_prefix (NULL), rc); } else if (spec & PTH_EVENT_FD) { if (spec & PTH_UNTIL_FD_READABLE) ev->flags |= PTH_UNTIL_FD_READABLE; if (spec & PTH_MODE_STATIC) ev->flags |= PTH_MODE_STATIC; ev->u_type = PTH_EVENT_FD; va_arg (arg, pth_key_t); ev->u.fd = va_arg (arg, int); if (DBG_INFO) fprintf (stderr, "%s: pth_event: fd=%d\n", log_get_prefix (NULL), ev->u.fd); } else if (spec & PTH_EVENT_TIME) { pth_time_t t; if (spec & PTH_MODE_STATIC) ev->flags |= PTH_MODE_STATIC; va_arg (arg, pth_key_t); t = va_arg (arg, pth_time_t); ev->u_type = PTH_EVENT_TIME; ev->u.tv.tv_sec = t.tv_sec; ev->u.tv.tv_usec = t.tv_usec; } else if (spec & PTH_EVENT_MUTEX) { va_arg (arg, pth_key_t); ev->u_type = PTH_EVENT_MUTEX; ev->u.mx = va_arg (arg, pth_mutex_t*); } memset (&sa, 0, sizeof sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof sa; ev->hd = CreateEvent (&sa, FALSE, FALSE, NULL); if (!ev->hd) { free (ev); return NULL; } ev->next = ev; ev->prev = ev; return ev; } static pth_event_t do_pth_event (unsigned long spec, ...) { va_list arg; pth_event_t ev; va_start (arg, spec); ev = do_pth_event_body (spec, arg); va_end (arg); return ev; } pth_event_t pth_event (unsigned long spec, ...) { va_list arg; pth_event_t ev; implicit_init (); enter_pth (__FUNCTION__); va_start (arg, spec); ev = do_pth_event_body (spec, arg); va_end (arg); leave_pth (__FUNCTION__); return ev; } static void pth_event_add (pth_event_t root, pth_event_t node) { pth_event_t n; for (n=root; n->next; n = n->next) ; n->next = node; } pth_event_t pth_event_concat (pth_event_t evf, ...) { pth_event_t evn; va_list ap; if (!evf) return NULL; implicit_init (); va_start (ap, evf); while ((evn = va_arg(ap, pth_event_t)) != NULL) pth_event_add (evf, evn); va_end (ap); return evf; } static int wait_for_fd (int fd, int is_read, int nwait) { struct timeval tv; fd_set r; fd_set w; int n; FD_ZERO (&r); FD_ZERO (&w); FD_SET (fd, is_read ? &r : &w); tv.tv_sec = nwait; tv.tv_usec = 0; while (1) { n = select (fd+1, &r, &w, NULL, &tv); if (DBG_INFO) fprintf (stderr, "%s: wait_for_fd=%d fd %d (ec=%d)\n", log_get_prefix (NULL), n, fd,(int)WSAGetLastError ()); if (n == -1) break; if (!n) continue; if (n == 1) { if (is_read && FD_ISSET (fd, &r)) break; else if (FD_ISSET (fd, &w)) break; } } return 0; } static void * launch_thread (void *arg) { struct thread_info_s *c = arg; if (c) { leave_pth (__FUNCTION__); c->thread (c->arg); if (!c->joinable && c->th) { CloseHandle (c->th); c->th = NULL; } /* FIXME: We would badly fail if someone accesses the now deallocated handle. Don't use it directly but setup proper scheduling queues. */ enter_pth (__FUNCTION__); free (c); } ExitThread (0); return NULL; } /* void */ /* sigemptyset (struct sigset_s * ss) */ /* { */ /* if (ss) { */ /* memset (ss->sigs, 0, sizeof ss->sigs); */ /* ss->idx = 0; */ /* } */ /* } */ /* int */ /* sigaddset (struct sigset_s * ss, int signo) */ /* { */ /* if (!ss) */ /* return -1; */ /* if (ss->idx + 1 > 64) */ /* return -1; */ /* ss->sigs[ss->idx] = signo; */ /* ss->idx++; */ /* return 0; */ /* } */ static int sigpresent (struct sigset_s * ss, int signo) { /* int i; */ /* for (i=0; i < ss->idx; i++) { */ /* if (ss->sigs[i] == signo) */ /* return 1; */ /* } */ /* FIXME: See how to implement it. */ return 0; } static int do_pth_event_occurred (pth_event_t ev) { int ret; if (!ev) return 0; ret = 0; switch (ev->u_type) { case 0: if (WaitForSingleObject (ev->hd, 0) == WAIT_OBJECT_0) ret = 1; break; case PTH_EVENT_SIGS: if (sigpresent (ev->u.sig, pth_signo) && WaitForSingleObject (pth_signo_ev, 0) == WAIT_OBJECT_0) { if (DBG_INFO) fprintf (stderr, "%s: pth_event_occurred: sig signaled.\n", log_get_prefix (NULL)); (*ev->val) = pth_signo; ret = 1; } break; case PTH_EVENT_FD: if (WaitForSingleObject (ev->hd, 0) == WAIT_OBJECT_0) ret = 1; break; } return ret; } int pth_event_occurred (pth_event_t ev) { int ret; implicit_init (); enter_pth (__FUNCTION__); ret = do_pth_event_occurred (ev); leave_pth (__FUNCTION__); return ret; } static int do_pth_event_status (pth_event_t ev) { if (!ev) return 0; if (do_pth_event_occurred (ev)) return PTH_STATUS_OCCURRED; return 0; } int pth_event_status (pth_event_t ev) { if (!ev) return 0; if (pth_event_occurred (ev)) return PTH_STATUS_OCCURRED; return 0; } static int do_pth_event_free (pth_event_t ev, int mode) { if (!ev) return FALSE; if (mode == PTH_FREE_ALL) { pth_event_t cur = ev; do { pth_event_t next = cur->next; CloseHandle (cur->hd); cur->hd = NULL; free (cur); cur = next; } while (cur != ev); } else if (mode == PTH_FREE_THIS) { ev->prev->next = ev->next; ev->next->prev = ev->prev; CloseHandle (ev->hd); ev->hd = NULL; free (ev); } else return FALSE; return TRUE; } int pth_event_free (pth_event_t ev, int mode) { int rc; implicit_init (); enter_pth (__FUNCTION__); rc = do_pth_event_free (ev, mode); leave_pth (__FUNCTION__); return rc; } pth_event_t pth_event_isolate (pth_event_t ev) { pth_event_t ring; if (!ev) return NULL; if (ev->next == ev && ev->prev == ev) return NULL; /* Only one event. */ ring = ev->next; ev->prev->next = ev->next; ev->next->prev = ev->prev; ev->prev = ev; ev->next = ev; return ring; } static int event_count (pth_event_t ev) { pth_event_t r; int cnt = 0; if (ev) { r = ev; do { cnt++; r = r->next; } while (r != ev); } return cnt; } static pth_t spawn_helper_thread (void *(*func)(void *), void *arg) { SECURITY_ATTRIBUTES sa; DWORD tid; HANDLE th; memset (&sa, 0, sizeof sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof sa; if (DBG_INFO) fprintf (stderr, "%s: spawn_helper_thread creating thread ...\n", log_get_prefix (NULL)); th = CreateThread (&sa, 32*1024, (LPTHREAD_START_ROUTINE)func, arg, 0, &tid); if (DBG_INFO) fprintf (stderr, "%s: spawn_helper_thread created thread %p\n", log_get_prefix (NULL), th); return th; } static void free_helper_threads (HANDLE *waitbuf, int *hdidx, int n) { int i; for (i=0; i < n; i++) { CloseHandle (waitbuf[hdidx[i]]); waitbuf[hdidx[i]] = NULL; } } static void * wait_fd_thread (void * ctx) { pth_event_t ev = ctx; wait_for_fd (ev->u.fd, ev->flags & PTH_UNTIL_FD_READABLE, 3600); if (DBG_INFO) fprintf (stderr, "%s: wait_fd_thread: exit.\n", log_get_prefix (NULL)); SetEvent (ev->hd); ExitThread (0); return NULL; } static void * wait_timer_thread (void * ctx) { pth_event_t ev = ctx; int n = ev->u.tv.tv_sec*1000; Sleep (n); SetEvent (ev->hd); if (DBG_INFO) fprintf (stderr, "%s: wait_timer_thread: exit.\n", log_get_prefix (NULL)); ExitThread (0); return NULL; } static int do_pth_wait (pth_event_t ev) { HANDLE waitbuf[MAXIMUM_WAIT_OBJECTS/2]; int hdidx[MAXIMUM_WAIT_OBJECTS/2]; DWORD n = 0; int pos=0, i=0; if (!ev) return 0; n = event_count (ev); if (n > MAXIMUM_WAIT_OBJECTS/2) return -1; if (DBG_INFO) fprintf (stderr, "%s: pth_wait: cnt %lu\n", log_get_prefix (NULL), n); if (ev) { pth_event_t r = ev; do { switch (r->u_type) { case 0: waitbuf[pos++] = r->hd; break; case PTH_EVENT_SIGS: waitbuf[pos++] = pth_signo_ev; if (DBG_INFO) fprintf (stderr, "pth_wait: add signal event.\n"); break; case PTH_EVENT_FD: if (DBG_INFO) fprintf (stderr, "pth_wait: spawn event wait thread.\n"); hdidx[i++] = pos; waitbuf[pos++] = spawn_helper_thread (wait_fd_thread, r); break; case PTH_EVENT_TIME: if (DBG_INFO) fprintf (stderr, "pth_wait: spawn event timer thread.\n"); hdidx[i++] = pos; waitbuf[pos++] = spawn_helper_thread (wait_timer_thread, r); break; case PTH_EVENT_MUTEX: if (DBG_INFO) fprintf (stderr, "pth_wait: ignoring mutex event.\n"); break; } } while ( r != ev ); } if (DBG_INFO) fprintf (stderr, "%s: pth_wait: set %d\n", log_get_prefix (NULL), pos); n = WaitForMultipleObjects (pos, waitbuf, FALSE, INFINITE); free_helper_threads (waitbuf, hdidx, i); if (DBG_INFO) fprintf (stderr, "%s: pth_wait: n %ld\n", log_get_prefix (NULL), n); if (n != WAIT_TIMEOUT) return 1; return 0; } int pth_wait (pth_event_t ev) { int rc; implicit_init (); enter_pth (__FUNCTION__); rc = do_pth_wait (ev); leave_pth (__FUNCTION__); return rc; } int pth_sleep (int sec) { static pth_key_t ev_key = PTH_KEY_INIT; pth_event_t ev; implicit_init (); enter_pth (__FUNCTION__); if (sec == 0) { leave_pth (__FUNCTION__); return 0; } ev = do_pth_event (PTH_EVENT_TIME|PTH_MODE_STATIC, &ev_key, pth_timeout (sec, 0)); if (ev == NULL) { leave_pth (__FUNCTION__); return -1; } do_pth_wait (ev); do_pth_event_free (ev, PTH_FREE_ALL); leave_pth (__FUNCTION__); return 0; } /* Some simple tests. */ #ifdef TEST #include void * thread (void * c) { Sleep (2000); SetEvent (((pth_event_t)c)->hd); fprintf (stderr, "\n\nhallo!.\n"); pth_exit (NULL); return NULL; } int main_1 (int argc, char ** argv) { pth_attr_t t; pth_t hd; pth_event_t ev; pth_init (); ev = pth_event (0, NULL); t = pth_attr_new (); pth_attr_set (t, PTH_ATTR_JOINABLE, 1); pth_attr_set (t, PTH_ATTR_STACK_SIZE, 4096); pth_attr_set (t, PTH_ATTR_NAME, "hello"); hd = pth_spawn (t, thread, ev); pth_wait (ev); pth_attr_destroy (t); pth_event_free (ev, 0); pth_kill (); return 0; } static pth_event_t setup_signals (struct sigset_s *sigs, int *signo) { pth_event_t ev; sigemptyset (sigs); sigaddset (sigs, SIGINT); sigaddset (sigs, SIGTERM); ev = pth_event (PTH_EVENT_SIGS, sigs, signo); return ev; } int main_2 (int argc, char ** argv) { pth_event_t ev; struct sigset_s sigs; int signo = 0; pth_init (); ev = setup_signals (&sigs, &signo); pth_wait (ev); if (pth_event_occured (ev) && signo) fprintf (stderr, "signal caught! signo %d\n", signo); pth_event_free (ev, PTH_FREE_ALL); pth_kill (); return 0; } int main_3 (int argc, char ** argv) { struct sockaddr_in addr, rem; int fd, n = 0, infd; int signo = 0; struct sigset_s sigs; pth_event_t ev; pth_init (); fd = socket (AF_INET, SOCK_STREAM, 0); memset (&addr, 0, sizeof addr); addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons (5050); addr.sin_family = AF_INET; bind (fd, (struct sockaddr*)&addr, sizeof addr); listen (fd, 5); ev = setup_signals (&sigs, &signo); n = sizeof addr; infd = pth_accept_ev (fd, (struct sockaddr *)&rem, &n, ev); fprintf (stderr, "infd %d: %s:%d\n", infd, inet_ntoa (rem.sin_addr), htons (rem.sin_port)); closesocket (infd); pth_event_free (ev, PTH_FREE_ALL); pth_kill (); return 0; } int main (int argc, char ** argv) { pth_event_t ev; pth_key_t ev_key; pth_init (); /*ev = pth_event (PTH_EVENT_TIME, &ev_key, pth_timeout (5, 0)); pth_wait (ev); pth_event_free (ev, PTH_FREE_ALL);*/ pth_sleep (5); pth_kill (); return 0; } #endif #endif /*HAVE_W32_SYSTEM*/ diff --git a/jnlib/w32-pth.h b/jnlib/w32-pth.h index 5ef0ab240..524010d92 100644 --- a/jnlib/w32-pth.h +++ b/jnlib/w32-pth.h @@ -1,244 +1,245 @@ /* w32-pth.h - GNU Pth emulation for W32 (MS Windows). * Copyright (c) 1999-2003 Ralf S. Engelschall * Copyright (C) 2004 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * ------------------------------------------------------------------ * This code is based on Ralf Engelschall's GNU Pth, a non-preemptive * thread scheduling library which can be found at * http://www.gnu.org/software/pth/. */ /* Note that this header is usually used through a symlinked pth.h file. This is needed so that we don't have a pth.h file here which would conflict if a system really has pth available. */ #ifndef W32_PTH_H #define W32_PTH_H #include /* We need this for sockaddr et al. FIXME: too heavyweight - may be we should factor such code out to a second header and adjust all user files to include it only if required. */ #ifndef W32_PTH_HANDLE_INTERNAL #define W32_PTH_HANDLE_INTERNAL int #endif /* Filedescriptor blocking modes. */ enum { PTH_FDMODE_ERROR = -1, PTH_FDMODE_POLL = 0, PTH_FDMODE_BLOCK, PTH_FDMODE_NONBLOCK }; /* Mutex values. */ #define PTH_MUTEX_INITIALIZED (1<<0) #define PTH_MUTEX_LOCKED (1<<1) /* Note: We can't do static initialization, thus we don't define the initializer PTH_MUTEX_INIT. */ #define PTH_KEY_INIT (1<<0) /* Event subject classes. */ #define PTH_EVENT_FD (1<<1) #define PTH_EVENT_SELECT (1<<2) #define PTH_EVENT_SIGS (1<<3) #define PTH_EVENT_TIME (1<<4) #define PTH_EVENT_MSG (1<<5) #define PTH_EVENT_MUTEX (1<<6) #define PTH_EVENT_COND (1<<7) #define PTH_EVENT_TID (1<<8) #define PTH_EVENT_FUNC (1<<9) /* Event occurrence restrictions. */ #define PTH_UNTIL_OCCURRED (1<<11) #define PTH_UNTIL_FD_READABLE (1<<12) #define PTH_UNTIL_FD_WRITEABLE (1<<13) #define PTH_UNTIL_FD_EXCEPTION (1<<14) #define PTH_UNTIL_TID_NEW (1<<15) #define PTH_UNTIL_TID_READY (1<<16) #define PTH_UNTIL_TID_WAITING (1<<17) #define PTH_UNTIL_TID_DEAD (1<<18) /* Event structure handling modes. */ #define PTH_MODE_REUSE (1<<20) #define PTH_MODE_CHAIN (1<<21) #define PTH_MODE_STATIC (1<<22) /* Attribute commands for pth_attr_get and pth_attr_set(). */ enum { PTH_ATTR_PRIO, /* RW [int] Priority of thread. */ PTH_ATTR_NAME, /* RW [char *] Name of thread. */ PTH_ATTR_JOINABLE, /* RW [int] Thread detachment type. */ PTH_ATTR_CANCEL_STATE, /* RW [unsigned int] Thread cancellation state.*/ PTH_ATTR_STACK_SIZE, /* RW [unsigned int] Stack size. */ PTH_ATTR_STACK_ADDR, /* RW [char *] Stack lower address. */ PTH_ATTR_DISPATCHES, /* RO [int] Total number of thread dispatches. */ PTH_ATTR_TIME_SPAWN, /* RO [pth_time_t] Time thread was spawned. */ PTH_ATTR_TIME_LAST, /* RO [pth_time_t] Time thread was last dispatched. */ PTH_ATTR_TIME_RAN, /* RO [pth_time_t] Time thread was running. */ PTH_ATTR_START_FUNC, /* RO [void *(*)(void *)] Thread start function.*/ PTH_ATTR_START_ARG, /* RO [void *] Thread start argument. */ PTH_ATTR_STATE, /* RO [pth_state_t] Scheduling state. */ PTH_ATTR_EVENTS, /* RO [pth_event_t] Events the thread is waiting for. */ PTH_ATTR_BOUND /* RO [int] Whether object is bound to thread. */ }; /* Queries for pth_ctrl(). */ #define PTH_CTRL_GETAVLOAD (1<<1) #define PTH_CTRL_GETPRIO (1<<2) #define PTH_CTRL_GETNAME (1<<3) #define PTH_CTRL_GETTHREADS_NEW (1<<4) #define PTH_CTRL_GETTHREADS_READY (1<<5) #define PTH_CTRL_GETTHREADS_RUNNING (1<<6) #define PTH_CTRL_GETTHREADS_WAITING (1<<7) #define PTH_CTRL_GETTHREADS_SUSPENDED (1<<8) #define PTH_CTRL_GETTHREADS_DEAD (1<<9) #define PTH_CTRL_DUMPSTATE (1<<10) #define PTH_CTRL_GETTHREADS ( PTH_CTRL_GETTHREADS_NEW \ | PTH_CTRL_GETTHREADS_READY \ | PTH_CTRL_GETTHREADS_RUNNING \ | PTH_CTRL_GETTHREADS_WAITING \ | PTH_CTRL_GETTHREADS_SUSPENDED \ | PTH_CTRL_GETTHREADS_DEAD ) /* Event status codes. */ typedef enum { PTH_STATUS_PENDING, PTH_STATUS_OCCURRED, PTH_STATUS_FAILED } pth_status_t; /* Event deallocation types. */ enum { PTH_FREE_THIS, PTH_FREE_ALL }; /* The Pth thread handle object. */ typedef void *pth_t; /* The Mutex object. */ typedef W32_PTH_HANDLE_INTERNAL pth_mutex_t; /* The Event object. */ struct pth_event_s; typedef struct pth_event_s *pth_event_t; /* The Attribute object. */ struct pth_attr_s; typedef struct pth_attr_s *pth_attr_t; /* The Key object. */ typedef int pth_key_t; /* The Pth time object. */ typedef struct timeval pth_time_t; /* Function prototypes. */ int pth_init (void); int pth_kill (void); long pth_ctrl (unsigned long query, ...); int pth_read_ev (int fd, void *buffer, size_t size, pth_event_t ev); int pth_read (int fd, void *buffer, size_t size); int pth_write_ev (int fd, const void *buffer, size_t size, pth_event_t ev); int pth_write (int fd, const void *buffer, size_t size); int pth_select (int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, const struct timeval *timeout); int pth_accept (int fd, struct sockaddr *addr, int *addrlen); int pth_accept_ev (int fd, struct sockaddr *addr, int *addrlen, pth_event_t hd); int pth_connect (int fd, struct sockaddr *name, int namelen); int pth_mutex_release (pth_mutex_t *hd); int pth_mutex_acquire(pth_mutex_t *hd, int try_only, pth_event_t ev_extra); int pth_mutex_init (pth_mutex_t *hd); pth_attr_t pth_attr_new (void); int pth_attr_destroy (pth_attr_t hd); int pth_attr_set (pth_attr_t hd, int field, ...); pth_t pth_spawn (pth_attr_t hd, void *(*func)(void *), void *arg); pth_t pth_self (void); int pth_join (pth_t hd, void **value); int pth_abort (pth_t hd); void pth_exit (void *value); unsigned int pth_waitpid (unsigned int, int *status, int options); int pth_wait (pth_event_t hd); int pth_sleep (int n); pth_time_t pth_timeout (long sec, long usec); pth_event_t pth_event_isolate (pth_event_t hd); int pth_event_free (pth_event_t hd, int mode); int pth_event_status (pth_event_t hd); int pth_event_occurred (pth_event_t hd); pth_event_t pth_event_concat (pth_event_t ev, ...); pth_event_t pth_event (unsigned long spec, ...); /*-- pth_util.c --*/ /* void sigemptyset (struct sigset_s * ss); */ /* int sigaddset (struct sigset_s * ss, int signo); */ #endif /*W32_PTH_H*/ diff --git a/jnlib/xmalloc.c b/jnlib/xmalloc.c index 1cfaab9f7..f5b92ba41 100644 --- a/jnlib/xmalloc.c +++ b/jnlib/xmalloc.c @@ -1,88 +1,89 @@ /* xmalloc.c - standard malloc wrappers * Copyright (C) 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include "libjnlib-config.h" #include "xmalloc.h" static void out_of_core(void) { fputs("\nfatal: out of memory\n", stderr ); exit(2); } void * xmalloc( size_t n ) { void *p = malloc( n ); if( !p ) out_of_core(); return p; } void * xrealloc( void *a, size_t n ) { void *p = realloc( a, n ); if( !p ) out_of_core(); return p; } void * xcalloc( size_t n, size_t m ) { void *p = calloc( n, m ); if( !p ) out_of_core(); return p; } char * xstrdup( const char *string ) { void *p = xmalloc( strlen(string)+1 ); strcpy( p, string ); return p; } char * xstrcat2( const char *a, const char *b ) { size_t n1; char *p; if( !b ) return xstrdup( a ); n1 = strlen(a); p = xmalloc( n1 + strlen(b) + 1 ); memcpy(p, a, n1 ); strcpy(p+n1, b ); return p; } diff --git a/jnlib/xmalloc.h b/jnlib/xmalloc.h index 150ef3664..8bfa7df79 100644 --- a/jnlib/xmalloc.h +++ b/jnlib/xmalloc.h @@ -1,31 +1,32 @@ /* xmalloc.h * Copyright (C) 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_XMALLOC_H #define LIBJNLIB_XMALLOC_H void *xmalloc( size_t n ); void *xrealloc( void *a, size_t n ); void *xcalloc( size_t n, size_t m ); char *xstrdup( const char *string ); char *xstrcat2( const char *a, const char *b ); #endif /*LIBJNLIB_XMALLOC_H*/ diff --git a/kbx/Makefile.am b/kbx/Makefile.am index f42e517bf..063dbb4c0 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -1,51 +1,52 @@ # Keybox Makefile # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in localedir = $(datadir)/locale INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" EXTRA_DIST = mkerrors AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common -I$(top_srcdir)/intl \ $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) noinst_LIBRARIES = libkeybox.a bin_PROGRAMS = kbxutil common_sources = \ keybox.h keybox-defs.h keybox-search-desc.h \ keybox-util.c \ keybox-init.c \ keybox-blob.c \ keybox-file.c \ keybox-search.c \ keybox-update.c \ keybox-openpgp.c \ keybox-dump.c libkeybox_a_SOURCES = $(common_sources) # Note that libcommon is only required to resolve the LIBOBJS. kbxutil_SOURCES = kbxutil.c $(common_sources) kbxutil_LDADD = ../jnlib/libjnlib.a ../gl/libgnu.a \ $(KSBA_LIBS) $(LIBGCRYPT_LIBS) \ -lgpg-error $(LIBINTL) ../common/libcommon.a diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 0569b5a67..19d356007 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -1,567 +1,568 @@ /* kbxutil.c - The Keybox utility * Copyright (C) 2000, 2001, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #define JNLIB_NEED_LOG_LOGV #include "../jnlib/logging.h" #include "../jnlib/argparse.h" #include "../jnlib/stringhelp.h" #include "../jnlib/utf8conv.h" #include "../common/i18n.h" #include "keybox-defs.h" #include enum cmd_and_opt_values { aNull = 0, oArmor = 'a', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', aNoSuchCmd = 500, /* force other values not to be a letter */ aFindByFpr, aFindByKid, aFindByUid, aStats, aImportOpenPGP, oDebug, oDebugAll, oNoArmor, aTest }; static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, /* { aFindByFpr, "find-by-fpr", 0, "|FPR| find key using it's fingerprnt" }, */ /* { aFindByKid, "find-by-kid", 0, "|KID| find key using it's keyid" }, */ /* { aFindByUid, "find-by-uid", 0, "|NAME| find key by user name" }, */ { aStats, "stats", 0, "show key statistics" }, { aImportOpenPGP, "import-openpgp", 0, "import OpenPGP keyblocks"}, { 301, NULL, 0, N_("@\nOptions:\n ") }, /* { oArmor, "armor", 0, N_("create ascii armored output")}, */ /* { oArmor, "armour", 0, "@" }, */ /* { oOutput, "output", 2, N_("use as output file")}, */ { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oDebug, "debug" ,4|16, N_("set debugging flags")}, { oDebugAll, "debug-all" ,0, N_("enable full debugging")}, {0} /* end of list */ }; void myexit (int rc); int keybox_errors_seen = 0; static const char * my_strusage( int level ) { const char *p; switch( level ) { case 11: p = "kbxutil (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: kbxutil [options] [files] (-h for help)"); break; case 41: p = _("Syntax: kbxutil [options] [files]\n" "list, export, import Keybox data\n"); break; default: p = NULL; } return p; } static void i18n_init(void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file( PACKAGE_GT ); #else #ifdef ENABLE_NLS setlocale( LC_ALL, "" ); bindtextdomain( PACKAGE_GT, LOCALEDIR ); textdomain( PACKAGE_GT ); #endif #endif } /* Used by gcry for logging */ static void my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) { /* Map 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 */ /* wrong_args( const char *text ) */ /* { */ /* log_error("usage: kbxutil %s\n", text); */ /* myexit ( 1 ); */ /* } */ #if 0 static int hextobyte( const byte *s ) { int c; if( *s >= '0' && *s <= '9' ) c = 16 * (*s - '0'); else if( *s >= 'A' && *s <= 'F' ) c = 16 * (10 + *s - 'A'); else if( *s >= 'a' && *s <= 'f' ) c = 16 * (10 + *s - 'a'); else return -1; s++; if( *s >= '0' && *s <= '9' ) c += *s - '0'; else if( *s >= 'A' && *s <= 'F' ) c += 10 + *s - 'A'; else if( *s >= 'a' && *s <= 'f' ) c += 10 + *s - 'a'; else return -1; return c; } #endif #if 0 static char * format_fingerprint ( const char *s ) { int i, c; byte fpr[20]; for (i=0; i < 20 && *s; ) { if ( *s == ' ' || *s == '\t' ) { s++; continue; } c = hextobyte(s); if (c == -1) { return NULL; } fpr[i++] = c; s += 2; } return gcry_xstrdup ( fpr ); } #endif #if 0 static int format_keyid ( const char *s, u32 *kid ) { char helpbuf[9]; switch ( strlen ( s ) ) { case 8: kid[0] = 0; kid[1] = strtoul( s, NULL, 16 ); return 10; case 16: mem2str( helpbuf, s, 9 ); kid[0] = strtoul( helpbuf, NULL, 16 ); kid[1] = strtoul( s+8, NULL, 16 ); return 11; } return 0; /* error */ } #endif static char * read_file (const char *fname, size_t *r_length) { FILE *fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; fp = stdin; buf = NULL; buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xtrymalloc (bufsize); else buf = xtryrealloc (buf, bufsize); if (!buf) log_fatal ("can't allocate buffer: %s\n", strerror (errno)); nread = fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && ferror (fp)) { log_error ("error reading `[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; fp = fopen (fname, "rb"); if (!fp) { log_error ("can't open `%s': %s\n", fname, strerror (errno)); return NULL; } if (fstat (fileno(fp), &st)) { log_error ("can't stat `%s': %s\n", fname, strerror (errno)); fclose (fp); return NULL; } buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf) log_fatal ("can't allocate buffer: %s\n", strerror (errno)); 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 void dump_fpr (const unsigned char *buffer, size_t len) { int i; for (i=0; i < len; i++, buffer++) { if (len == 20) { if (i == 10) putchar (' '); printf (" %02X%02X", buffer[0], buffer[1]); i++; buffer++; } else { if (i && !(i % 8)) putchar (' '); printf (" %02X", buffer[0]); } } } static void dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) { printf ("pub %02X%02X%02X%02X", info->primary.keyid[4], info->primary.keyid[5], info->primary.keyid[6], info->primary.keyid[7] ); dump_fpr (info->primary.fpr, info->primary.fprlen); putchar ('\n'); if (info->nsubkeys) { struct _keybox_openpgp_key_info *k; k = &info->subkeys; do { printf ("sub %02X%02X%02X%02X", k->keyid[4], k->keyid[5], k->keyid[6], k->keyid[7] ); dump_fpr (k->fpr, k->fprlen); putchar ('\n'); k = k->next; } while (k); } if (info->nuids) { struct _keybox_openpgp_uid_info *u; u = &info->uids; do { printf ("uid\t\t%.*s\n", u->len, image + u->off); u = u->next; } while (u); } } static void import_openpgp (const char *filename) { gpg_error_t err; char *buffer; size_t buflen, nparsed; unsigned char *p; struct _keybox_openpgp_info info; buffer = read_file (filename, &buflen); if (!buffer) return; p = (unsigned char *)buffer; for (;;) { err = _keybox_parse_openpgp (p, buflen, &nparsed, &info); assert (nparsed <= buflen); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) break; log_info ("%s: failed to parse OpenPGP keyblock: %s\n", filename, gpg_strerror (err)); } else { dump_openpgp_key (&info, p); _keybox_destroy_openpgp_info (&info); } p += nparsed; buflen -= nparsed; } xfree (buffer); } int main( int argc, char **argv ) { ARGPARSE_ARGS pargs; enum cmd_and_opt_values cmd = 0; set_strusage( my_strusage ); gcry_control (GCRYCTL_DISABLE_SECMEM); log_set_prefix ("kbxutil", 1); set_native_charset (NULL); i18n_init (); /* 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) ); } gcry_set_log_handler (my_gcry_logger, NULL); /*create_dotlock(NULL); register locking cleanup */ /* We need to use the gcry malloc function because jnlib does use them */ keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); 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++;*/ /*gcry_control( GCRYCTL_SET_VERBOSITY, (int)opt.verbose );*/ break; case oDebug: /*opt.debug |= pargs.r.ret_ulong; */ break; case oDebugAll: /*opt.debug = ~0;*/ break; case aFindByFpr: case aFindByKid: case aFindByUid: case aStats: case aImportOpenPGP: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } if (log_get_errorcount(0) ) myexit(2); if (!cmd) { /* Default is to list a KBX file */ if (!argc) _keybox_dump_file (NULL, 0, stdout); else { for (; argc; argc--, argv++) _keybox_dump_file (*argv, 0, stdout); } } else if (cmd == aStats ) { if (!argc) _keybox_dump_file (NULL, 1, stdout); else { for (; argc; argc--, argv++) _keybox_dump_file (*argv, 1, stdout); } } else if (cmd == aImportOpenPGP) { if (!argc) import_openpgp ("-"); else { for (; argc; argc--, argv++) import_openpgp (*argv); } } #if 0 else if ( cmd == aFindByFpr ) { char *fpr; if ( argc != 2 ) wrong_args ("kbxfile foingerprint"); fpr = format_fingerprint ( argv[1] ); if ( !fpr ) log_error ("invalid formatted fingerprint\n"); else { kbxfile_search_by_fpr ( argv[0], fpr ); gcry_free ( fpr ); } } else if ( cmd == aFindByKid ) { u32 kid[2]; int mode; if ( argc != 2 ) wrong_args ("kbxfile short-or-long-keyid"); mode = format_keyid ( argv[1], kid ); if ( !mode ) log_error ("invalid formatted keyID\n"); else { kbxfile_search_by_kid ( argv[0], kid, mode ); } } else if ( cmd == aFindByUid ) { if ( argc != 2 ) wrong_args ("kbxfile userID"); kbxfile_search_by_uid ( argv[0], argv[1] ); } #endif else log_error ("unsupported action\n"); myexit(0); return 8; /*NEVER REACHED*/ } void myexit( int rc ) { /* 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 ); */ rc = rc? rc : log_get_errorcount(0)? 2 : keybox_errors_seen? 1 : 0; exit(rc ); } diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index eacc0014a..f3fe31617 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -1,1034 +1,1035 @@ /* keybox-blob.c - KBX Blob handling * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* The keybox data formats The KeyBox uses an augmented OpenPGP/X.509 key format. This makes random access to a keyblock/Certificate easier and also gives the opportunity to store additional information (e.g. the fingerprint) along with the key. All integers are stored in network byte order, offsets are counted from the beginning of the Blob. The first record of a plain KBX file has a special format: u32 length of the first record byte Blob type (1) byte version number (1) byte reserved byte reserved u32 magic 'KBXf' u32 reserved u32 file_created_at u32 last_maintenance_run u32 reserved u32 reserved The OpenPGP and X.509 blob are very similiar, things which are X.509 specific are noted like [X.509: xxx] u32 length of this blob (including these 4 bytes) byte Blob type (2) [X509: 3] byte version number of this blob type (1) u16 Blob flags bit 0 = contains secret key material bit 1 = ephemeral blob (e.g. used while quering external resources) u32 offset to the OpenPGP keyblock or X509 DER encoded certificate u32 and its length u16 number of keys (at least 1!) [X509: always 1] u16 size of additional key information n times: b20 The keys fingerprint (fingerprints are always 20 bytes, MD5 left padded with zeroes) u32 offset to the n-th key's keyID (a keyID is always 8 byte) or 0 if not known which is the case only for X509. u16 special key flags bit 0 = qualified signature (not yet implemented} u16 reserved u16 size of serialnumber(may be zero) n u16 (see above) bytes of serial number u16 number of user IDs u16 size of additional user ID information n times: u32 offset to the n-th user ID u32 length of this user ID. u16 special user ID flags. bit 0 = byte validity byte reserved [For X509, the first user ID is the Issuer, the second the Subject and the others are subjectAltNames] u16 number of signatures u16 size of signature information (4) u32 expiration time of signature with some special values: 0x00000000 = not checked 0x00000001 = missing key 0x00000002 = bad signature 0x10000000 = valid and expires at some date in 1978. 0xffffffff = valid and does not expire u8 assigned ownertrust [X509: not used] u8 all_validity OpenPGP: see ../g10/trustdb/TRUST_* [not yet used] X509: Bit 4 set := key has been revoked. Note that this value matches TRUST_FLAG_REVOKED u16 reserved u32 recheck_after u32 Newest timestamp in the keyblock (useful for KS syncronsiation?) u32 Blob created at u32 size of reserved space (not including this field) reserved space Here we might want to put other data Here comes the keyblock maybe we put a signature here later. b16 MD5 checksum (useful for KS syncronisation), we might also want to use a mac here. b4 resevered */ #include #include #include #include #include #include #include #include "keybox-defs.h" #include #ifdef KEYBOX_WITH_OPENPGP /* include stuff to parse the packets */ #endif #ifdef KEYBOX_WITH_X509 #include #endif /* special values of the signature status */ #define SF_NONE(a) ( !(a) ) #define SF_NOKEY(a) ((a) & (1<<0)) #define SF_BAD(a) ((a) & (1<<1)) #define SF_VALID(a) ((a) & (1<<29)) struct membuf { size_t len; size_t size; char *buf; int out_of_core; }; /* #if MAX_FINGERPRINT_LEN < 20 */ /* #error fingerprints are 20 bytes */ /* #endif */ struct keyboxblob_key { char fpr[20]; u32 off_kid; ulong off_kid_addr; u16 flags; }; struct keyboxblob_uid { ulong off_addr; char *name; /* used only with x509 */ u32 len; u16 flags; byte validity; }; struct keyid_list { struct keyid_list *next; int seqno; byte kid[8]; }; struct fixup_list { struct fixup_list *next; u32 off; u32 val; }; struct keyboxblob { byte *blob; size_t bloblen; off_t fileoffset; /* stuff used only by keybox_create_blob */ unsigned char *serialbuf; const unsigned char *serial; size_t seriallen; int nkeys; struct keyboxblob_key *keys; int nuids; struct keyboxblob_uid *uids; int nsigs; u32 *sigs; struct fixup_list *fixups; int fixup_out_of_core; struct keyid_list *temp_kids; struct membuf bufbuf; /* temporary store for the blob */ struct membuf *buf; }; /* A simple implemention 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; } static void put8 (struct membuf *mb, byte a ) { put_membuf (mb, &a, 1); } static void put16 (struct membuf *mb, u16 a ) { unsigned char tmp[2]; tmp[0] = a>>8; tmp[1] = a; put_membuf (mb, tmp, 2); } static void put32 (struct membuf *mb, u32 a ) { unsigned char tmp[4]; tmp[0] = a>>24; tmp[1] = a>>16; tmp[2] = a>>8; tmp[3] = a; put_membuf (mb, tmp, 4); } /* Store a value in the fixup list */ static void add_fixup (KEYBOXBLOB blob, u32 off, u32 val) { struct fixup_list *fl; if (blob->fixup_out_of_core) return; fl = xtrycalloc(1, sizeof *fl); if (!fl) blob->fixup_out_of_core = 1; else { fl->off = off; fl->val = val; fl->next = blob->fixups; blob->fixups = fl; } } /* Some wrappers */ static u32 make_timestamp (void) { return time(NULL); } #ifdef KEYBOX_WITH_OPENPGP /* OpenPGP specific stuff */ /* We must store the keyid at some place because we can't calculate the offset yet. This is only used for v3 keyIDs. Function returns an index value for later fixup or -1 for out of core. The value must be a non-zero value */ static int pgp_temp_store_kid (KEYBOXBLOB blob, PKT_public_key *pk) { struct keyid_list *k, *r; k = xtrymalloc (sizeof *k); if (!k) return -1; k->kid[0] = pk->keyid[0] >> 24 ; k->kid[1] = pk->keyid[0] >> 16 ; k->kid[2] = pk->keyid[0] >> 8 ; k->kid[3] = pk->keyid[0] ; k->kid[4] = pk->keyid[0] >> 24 ; k->kid[5] = pk->keyid[0] >> 16 ; k->kid[6] = pk->keyid[0] >> 8 ; k->kid[7] = pk->keyid[0] ; k->seqno = 0; k->next = blob->temp_kids; blob->temp_kids = k; for (r=k; r; r = r->next) k->seqno++; return k->seqno; } static int pgp_create_key_part (KEYBOXBLOB blob, KBNODE keyblock) { KBNODE node; size_t fprlen; int n; for (n=0, node = keyblock; node; node = node->next) { if ( node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { PKT_public_key *pk = node->pkt->pkt.public_key; char tmp[20]; fingerprint_from_pk (pk, tmp , &fprlen); memcpy (blob->keys[n].fpr, tmp, 20); if ( fprlen != 20 ) /*v3 fpr - shift right and fill with zeroes*/ { assert (fprlen == 16); memmove (blob->keys[n].fpr+4, blob->keys[n].fpr, 16); memset (blob->keys[n].fpr, 0, 4); blob->keys[n].off_kid = pgp_temp_store_kid (blob, pk); } else { blob->keys[n].off_kid = 0; /* will be fixed up later */ } blob->keys[n].flags = 0; n++; } else if ( node->pkt->pkttype == PKT_SECRET_KEY || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { never_reached (); /* actually not yet implemented */ } } assert (n == blob->nkeys); return 0; } static int pgp_create_uid_part (KEYBOXBLOB blob, KBNODE keyblock) { KBNODE node; int n; for (n=0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *u = node->pkt->pkt.user_id; blob->uids[n].len = u->len; blob->uids[n].flags = 0; blob->uids[n].validity = 0; n++; } } assert (n == blob->nuids); return 0; } static int pgp_create_sig_part (KEYBOXBLOB blob, KBNODE keyblock) { KBNODE node; int n; for (n=0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; blob->sigs[n] = 0; /* FIXME: check the signature here */ n++; } } assert( n == blob->nsigs ); return 0; } static int pgp_create_blob_keyblock (KEYBOXBLOB blob, KBNODE keyblock) { struct membuf *a = blob->buf; KBNODE node; int rc; int n; u32 kbstart = a->len; add_fixup (blob, kbstart); for (n = 0, node = keyblock; node; node = node->next) { rc = build_packet ( a, node->pkt ); if ( rc ) { gpg_log_error ("build_packet(%d) for keyboxblob failed: %s\n", node->pkt->pkttype, gpg_errstr(rc) ); return GPGERR_WRITE_FILE; } if ( node->pkt->pkttype == PKT_USER_ID ) { PKT_user_id *u = node->pkt->pkt.user_id; /* build_packet has set the offset of the name into u ; * now we can do the fixup */ add_fixup (blob, blob->uids[n].off_addr, u->stored_at); n++; } } assert (n == blob->nuids); add_fixup (blob, a->len - kbstart); return 0; } #endif /*KEYBOX_WITH_OPENPGP*/ #ifdef KEYBOX_WITH_X509 /* X.509 specific stuff */ /* Write the raw certificate out */ static int x509_create_blob_cert (KEYBOXBLOB blob, ksba_cert_t cert) { struct membuf *a = blob->buf; const unsigned char *image; size_t length; u32 kbstart = a->len; /* Store our offset for later fixup */ add_fixup (blob, 8, kbstart); image = ksba_cert_get_image (cert, &length); if (!image) return gpg_error (GPG_ERR_GENERAL); put_membuf (a, image, length); add_fixup (blob, 12, a->len - kbstart); return 0; } #endif /*KEYBOX_WITH_X509*/ /* Write a stored keyID out to the buffer */ static void write_stored_kid (KEYBOXBLOB blob, int seqno) { struct keyid_list *r; for ( r = blob->temp_kids; r; r = r->next ) { if (r->seqno == seqno ) { put_membuf (blob->buf, r->kid, 8); return; } } never_reached (); } /* Release a list of key IDs */ static void release_kid_list (struct keyid_list *kl) { struct keyid_list *r, *r2; for ( r = kl; r; r = r2 ) { r2 = r->next; xfree (r); } } static int create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) { struct membuf *a = blob->buf; int i; put32 ( a, 0 ); /* blob length, needs fixup */ put8 ( a, blobtype); put8 ( a, 1 ); /* blob type version */ put16 ( a, as_ephemeral? 2:0 ); /* blob flags */ put32 ( a, 0 ); /* offset to the raw data, needs fixup */ put32 ( a, 0 ); /* length of the raw data, needs fixup */ put16 ( a, blob->nkeys ); put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ for ( i=0; i < blob->nkeys; i++ ) { put_membuf (a, blob->keys[i].fpr, 20); blob->keys[i].off_kid_addr = a->len; put32 ( a, 0 ); /* offset to keyid, fixed up later */ put16 ( a, blob->keys[i].flags ); put16 ( a, 0 ); /* reserved */ } put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/ if (blob->serial) put_membuf (a, blob->serial, blob->seriallen); put16 ( a, blob->nuids ); put16 ( a, 4 + 4 + 2 + 1 + 1 ); /* size of uid info */ for (i=0; i < blob->nuids; i++) { blob->uids[i].off_addr = a->len; put32 ( a, 0 ); /* offset to userid, fixed up later */ put32 ( a, blob->uids[i].len ); put16 ( a, blob->uids[i].flags ); put8 ( a, 0 ); /* validity */ put8 ( a, 0 ); /* reserved */ } put16 ( a, blob->nsigs ); put16 ( a, 4 ); /* size of sig info */ for (i=0; i < blob->nsigs; i++) { put32 ( a, blob->sigs[i]); } put8 ( a, 0 ); /* assigned ownertrust */ put8 ( a, 0 ); /* validity of all user IDs */ put16 ( a, 0 ); /* reserved */ put32 ( a, 0 ); /* time of next recheck */ put32 ( a, 0 ); /* newest timestamp (none) */ put32 ( a, make_timestamp() ); /* creation time */ put32 ( a, 0 ); /* size of reserved space */ /* reserved space (which is currently of size 0) */ /* space where we write keyIDs and and other stuff so that the pointers can actually point to somewhere */ if (blobtype == BLOBTYPE_PGP) { /* We need to store the keyids for all pgp v3 keys because those key IDs are not part of the fingerprint. While we are doing that, we fixup all the keyID offsets */ for (i=0; i < blob->nkeys; i++ ) { if (blob->keys[i].off_kid) { /* this is a v3 one */ add_fixup (blob, blob->keys[i].off_kid_addr, a->len); write_stored_kid (blob, blob->keys[i].off_kid); } else { /* the better v4 key IDs - just store an offset 8 bytes back */ add_fixup (blob, blob->keys[i].off_kid_addr, blob->keys[i].off_kid_addr - 8); } } } if (blobtype == BLOBTYPE_X509) { /* We don't want to point to ASN.1 encoded UserIDs (DNs) but to the utf-8 string represenation of them */ for (i=0; i < blob->nuids; i++ ) { if (blob->uids[i].name) { /* this is a v3 one */ add_fixup (blob, blob->uids[i].off_addr, a->len); put_membuf (blob->buf, blob->uids[i].name, blob->uids[i].len); } } } return 0; } static int create_blob_trailer (KEYBOXBLOB blob) { return 0; } static int create_blob_finish (KEYBOXBLOB blob) { struct membuf *a = blob->buf; unsigned char *p; unsigned char *pp; int i; size_t n; /* write a placeholder for the checksum */ for (i = 0; i < 16; i++ ) put32 (a, 0); /* Hmmm: why put32() ?? */ /* get the memory area */ n = 0; /* (Just to avoid compiler warning.) */ p = get_membuf (a, &n); if (!p) return gpg_error (GPG_ERR_ENOMEM); assert (n >= 20); /* fixup the length */ add_fixup (blob, 0, n); /* do the fixups */ if (blob->fixup_out_of_core) return gpg_error (GPG_ERR_ENOMEM); { struct fixup_list *fl; for (fl = blob->fixups; fl; fl = fl->next) { assert (fl->off+4 <= n); p[fl->off+0] = fl->val >> 24; p[fl->off+1] = fl->val >> 16; p[fl->off+2] = fl->val >> 8; p[fl->off+3] = fl->val; } } /* calculate and store the MD5 checksum */ gcry_md_hash_buffer (GCRY_MD_MD5, p + n - 16, p, n - 16); pp = xtrymalloc (n); if ( !pp ) return gpg_error (gpg_err_code_from_errno (errno)); memcpy (pp , p, n); blob->blob = pp; blob->bloblen = n; return 0; } #ifdef KEYBOX_WITH_OPENPGP int _keybox_create_pgp_blob (KEYBOXBLOB *r_blob, KBNODE keyblock, int as_ephemeral) { int rc = 0; KBNODE node; KEYBOXBLOB blob; *r_blob = NULL; blob = xtrycalloc (1, sizeof *blob); if (!blob) return gpg_error (gpg_err_code_from_errno (errno)); /* fixme: Do some sanity checks on the keyblock */ /* count userids and keys so that we can allocate the arrays */ for (node = keyblock; node; node = node->next) { switch (node->pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_SECRET_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_SUBKEY: blob->nkeys++; break; case PKT_USER_ID: blob->nuids++; break; case PKT_SIGNATURE: blob->nsigs++; break; default: break; } } blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys ); blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids ); blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs ); if (!blob->keys || !blob->uids || !blob->sigs) { rc = gpg_error (GPG_ERR_ENOMEM); goto leave; } rc = pgp_create_key_part ( blob, keyblock ); if (rc) goto leave; rc = pgp_create_uid_part ( blob, keyblock ); if (rc) goto leave; rc = pgp_create_sig_part ( blob, keyblock ); if (rc) goto leave; init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; rc = create_blob_header (blob, BLOBTYPE_OPENPGP, as_ephemeral); if (rc) goto leave; rc = pgp_create_blob_keyblock (blob, keyblock); if (rc) goto leave; rc = create_blob_trailer (blob); if (rc) goto leave; rc = create_blob_finish ( blob ); if (rc) goto leave; leave: release_kid_list (blob->temp_kids); blob->temp_kids = NULL; if (rc) { keybox_release_blob (blob); *r_blob = NULL; } else { *r_blob = blob; } return rc; } #endif /*KEYBOX_WITH_OPENPGP*/ #ifdef KEYBOX_WITH_X509 /* return an allocated string with the email address extracted from a DN */ static char * x509_email_kludge (const char *name) { const char *p; unsigned char *buf; int n; if (strncmp (name, "1.2.840.113549.1.9.1=#", 22)) return NULL; /* This looks pretty much like an email address in the subject's DN we use this to add an additional user ID entry. This way, openSSL generated keys get a nicer and usable listing */ name += 22; for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) ; if (*p != '#' || !n) return NULL; buf = xtrymalloc (n+3); if (!buf) return NULL; /* oops, out of core */ *buf = '<'; for (n=1, p=name; *p != '#'; p +=2, n++) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; return (char *)buf; } /* Note: We should move calculation of the digest into libksba and remove that parameter */ int _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, unsigned char *sha1_digest, int as_ephemeral) { int i, rc = 0; KEYBOXBLOB blob; unsigned char *sn; char *p; char **names = NULL; size_t max_names; *r_blob = NULL; blob = xtrycalloc (1, sizeof *blob); if( !blob ) return gpg_error (gpg_err_code_from_errno (errno)); sn = ksba_cert_get_serial (cert); if (sn) { size_t n, len; n = gcry_sexp_canon_len (sn, 0, NULL, NULL); if (n < 2) { xfree (sn); return gpg_error (GPG_ERR_GENERAL); } blob->serialbuf = sn; sn++; n--; /* skip '(' */ for (len=0; n && *sn && *sn != ':' && digitp (sn); n--, sn++) len = len*10 + atoi_1 (sn); if (*sn != ':') { xfree (blob->serialbuf); blob->serialbuf = NULL; return gpg_error (GPG_ERR_GENERAL); } sn++; blob->serial = sn; blob->seriallen = len; } blob->nkeys = 1; /* create list of names */ blob->nuids = 0; max_names = 100; names = xtrymalloc (max_names * sizeof *names); if (!names) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } p = ksba_cert_get_issuer (cert, 0); if (!p) { rc = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } names[blob->nuids++] = p; for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) { if (blob->nuids >= max_names) { char **tmp; max_names += 100; tmp = xtryrealloc (names, max_names * sizeof *names); if (!tmp) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } } names[blob->nuids++] = p; if (!i && (p=x509_email_kludge (p))) names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/ } /* space for signature information */ blob->nsigs = 1; blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys ); blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids ); blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs ); if (!blob->keys || !blob->uids || !blob->sigs) { rc = gpg_error (GPG_ERR_ENOMEM); goto leave; } memcpy (blob->keys[0].fpr, sha1_digest, 20); blob->keys[0].off_kid = 0; /* We don't have keyids */ blob->keys[0].flags = 0; /* issuer and subject names */ for (i=0; i < blob->nuids; i++) { blob->uids[i].name = names[i]; blob->uids[i].len = strlen(names[i]); names[i] = NULL; blob->uids[i].flags = 0; blob->uids[i].validity = 0; } xfree (names); names = NULL; /* signatures */ blob->sigs[0] = 0; /* not yet checked */ /* Create a temporary buffer for further processing */ init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; /* write out what we already have */ rc = create_blob_header (blob, BLOBTYPE_X509, as_ephemeral); if (rc) goto leave; rc = x509_create_blob_cert (blob, cert); if (rc) goto leave; rc = create_blob_trailer (blob); if (rc) goto leave; rc = create_blob_finish ( blob ); if (rc) goto leave; leave: release_kid_list (blob->temp_kids); blob->temp_kids = NULL; if (blob && names) { for (i=0; i < blob->nuids; i++) xfree (names[i]); } xfree (names); if (rc) { _keybox_release_blob (blob); *r_blob = NULL; } else { *r_blob = blob; } return rc; } #endif /*KEYBOX_WITH_X509*/ int _keybox_new_blob (KEYBOXBLOB *r_blob, unsigned char *image, size_t imagelen, off_t off) { KEYBOXBLOB blob; *r_blob = NULL; blob = xtrycalloc (1, sizeof *blob); if (!blob) return gpg_error (gpg_err_code_from_errno (errno)); blob->blob = image; blob->bloblen = imagelen; blob->fileoffset = off; *r_blob = blob; return 0; } void _keybox_release_blob (KEYBOXBLOB blob) { int i; if (!blob) return; /* hmmm: release membuf here?*/ xfree (blob->keys ); xfree (blob->serialbuf); for (i=0; i < blob->nuids; i++) xfree (blob->uids[i].name); xfree (blob->uids ); xfree (blob->sigs ); xfree (blob->blob ); xfree (blob ); } const unsigned char * _keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n ) { *n = blob->bloblen; return blob->blob; } off_t _keybox_get_blob_fileoffset (KEYBOXBLOB blob) { return blob->fileoffset; } void _keybox_update_header_blob (KEYBOXBLOB blob) { if (blob->bloblen >= 32 && blob->blob[4] == BLOBTYPE_HEADER) { u32 val = make_timestamp (); /* Update the last maintenance run times tamp. */ blob->blob[20] = (val >> 24); blob->blob[20+1] = (val >> 16); blob->blob[20+2] = (val >> 8); blob->blob[20+3] = (val ); } } diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index 7bbed8519..ad53c71a7 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -1,238 +1,239 @@ /* keybox-defs.h - interal Keybox defintions * Copyright (C) 2001, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef KEYBOX_DEFS_H #define KEYBOX_DEFS_H 1 #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX #include #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include /* off_t */ /* We include the type defintions from jnlib instead of defining our owns here. This will not allow us build KBX in a standalone way but there is currently no need for it anyway. Same goes for stringhelp.h which for example provides a replacement for stpcpy - fixme: Better the LIBOBJ mechnism. */ #include "../jnlib/types.h" #include "../jnlib/stringhelp.h" #include "keybox.h" enum { BLOBTYPE_EMPTY = 0, BLOBTYPE_HEADER = 1, BLOBTYPE_PGP = 2, BLOBTYPE_X509 = 3 }; typedef struct keyboxblob *KEYBOXBLOB; typedef struct keybox_name *KB_NAME; typedef struct keybox_name const * CONST_KB_NAME; struct keybox_name { struct keybox_name *next; int secret; /*DOTLOCK lockhd;*/ int is_locked; int did_full_scan; char fname[1]; }; struct keybox_handle { CONST_KB_NAME kb; int secret; /* this is for a secret keybox */ FILE *fp; int eof; int error; int ephemeral; struct { KEYBOXBLOB blob; off_t offset; size_t pk_no; size_t uid_no; unsigned int n_packets; /*used for delete and update*/ } found; struct { char *name; char *pattern; } word_match; }; /* Openpgp helper structures. */ struct _keybox_openpgp_key_info { struct _keybox_openpgp_key_info *next; unsigned char keyid[8]; int fprlen; /* Either 16 or 20 */ unsigned char fpr[20]; }; struct _keybox_openpgp_uid_info { struct _keybox_openpgp_uid_info *next; size_t off; size_t len; }; struct _keybox_openpgp_info { int is_secret; /* True if this is a secret key. */ unsigned int nsubkeys;/* Total number of subkeys. */ unsigned int nuids; /* Total number of user IDs in the keyblock. */ unsigned int nsigs; /* Total number of signatures in the keyblock. */ /* Note, we use 2 structs here to better cope with the most common use of having one primary and one subkey - this allows us to statically allocate this structure and only malloc stuff for more than one subkey. */ struct _keybox_openpgp_key_info primary; struct _keybox_openpgp_key_info subkeys; struct _keybox_openpgp_uid_info uids; }; typedef struct _keybox_openpgp_info *keybox_openpgp_info_t; /* Don't know whether this is needed: */ /* static struct { */ /* const char *homedir; */ /* int dry_run; */ /* int quiet; */ /* int verbose; */ /* int preserve_permissions; */ /* } keybox_opt; */ /*-- keybox-blob.c --*/ #ifdef KEYBOX_WITH_OPENPGP /* fixme */ #endif /*KEYBOX_WITH_OPENPGP*/ #ifdef KEYBOX_WITH_X509 int _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, unsigned char *sha1_digest, int as_ephemeral); #endif /*KEYBOX_WITH_X509*/ int _keybox_new_blob (KEYBOXBLOB *r_blob, unsigned char *image, size_t imagelen, off_t off); void _keybox_release_blob (KEYBOXBLOB blob); const unsigned char *_keybox_get_blob_image (KEYBOXBLOB blob, size_t *n); off_t _keybox_get_blob_fileoffset (KEYBOXBLOB blob); void _keybox_update_header_blob (KEYBOXBLOB blob); /*-- keybox-openpgp.c --*/ gpg_error_t _keybox_parse_openpgp (const unsigned char *image, size_t imagelen, size_t *nparsed, keybox_openpgp_info_t info); void _keybox_destroy_openpgp_info (keybox_openpgp_info_t info); /*-- keybox-file.c --*/ int _keybox_read_blob (KEYBOXBLOB *r_blob, FILE *fp); int _keybox_read_blob2 (KEYBOXBLOB *r_blob, FILE *fp, int *skipped_deleted); int _keybox_write_blob (KEYBOXBLOB blob, FILE *fp); int _keybox_write_header_blob (FILE *fp); /*-- keybox-search.c --*/ gpg_err_code_t _keybox_get_flag_location (const unsigned char *buffer, size_t length, int what, size_t *flag_off, size_t *flag_size); /*-- keybox-dump.c --*/ int _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp); int _keybox_dump_file (const char *filename, int stats_only, FILE *outfp); /*-- keybox-util.c --*/ void *_keybox_malloc (size_t n); void *_keybox_calloc (size_t n, size_t m); void *_keybox_realloc (void *p, size_t n); void _keybox_free (void *p); #define xtrymalloc(a) _keybox_malloc ((a)) #define xtrycalloc(a,b) _keybox_calloc ((a),(b)) #define xtryrealloc(a,b) _keybox_realloc((a),(b)) #define xfree(a) _keybox_free ((a)) #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) #ifndef STR #define STR(v) #v #endif #define STR2(v) STR(v) /* a couple of handy macros */ #define return_if_fail(expr) do { \ if (!(expr)) { \ fprintf (stderr, "%s:%d: assertion `%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return; \ } } while (0) #define return_null_if_fail(expr) do { \ if (!(expr)) { \ fprintf (stderr, "%s:%d: assertion `%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return NULL; \ } } while (0) #define return_val_if_fail(expr,val) do { \ if (!(expr)) { \ fprintf (stderr, "%s:%d: assertion `%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return (val); \ } } while (0) #define never_reached() do { \ fprintf (stderr, "%s:%d: oops; should never get here\n", \ __FILE__, __LINE__ ); \ } while (0) /* some macros to replace ctype ones and avoid locale problems */ #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* the atoi macros assume that the buffer has only valid digits */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #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 /*KEYBOX_DEFS_H*/ diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c index 495fb249e..d28611377 100644 --- a/kbx/keybox-dump.c +++ b/kbx/keybox-dump.c @@ -1,484 +1,485 @@ /* keybox-dump.c - Debug helpers * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "keybox-defs.h" static ulong get32 (const byte *buffer) { ulong a; a = *buffer << 24; a |= buffer[1] << 16; a |= buffer[2] << 8; a |= buffer[3]; return a; } static ulong get16 (const byte *buffer) { ulong a; a = *buffer << 8; a |= buffer[1]; return a; } void print_string (FILE *fp, const byte *p, size_t n, int delim) { for ( ; n; n--, p++ ) { if (*p < 0x20 || (*p >= 0x7f && *p < 0xa0) || *p == delim) { putc('\\', fp); if( *p == '\n' ) putc('n', fp); else if( *p == '\r' ) putc('r', fp); else if( *p == '\f' ) putc('f', fp); else if( *p == '\v' ) putc('v', fp); else if( *p == '\b' ) putc('b', fp); else if( !*p ) putc('0', fp); else fprintf(fp, "x%02x", *p ); } else putc(*p, fp); } } static int dump_header_blob (const byte *buffer, size_t length, FILE *fp) { unsigned long n; if (length < 32) { fprintf (fp, "[blob too short]\n"); return -1; } fprintf (fp, "Version: %d\n", buffer[5]); if ( memcmp (buffer+8, "KBXf", 4)) fprintf (fp, "[Error: invalid magic number]\n"); n = get32 (buffer+16); fprintf( fp, "created-at: %lu\n", n ); n = get32 (buffer+20); fprintf( fp, "last-maint: %lu\n", n ); return 0; } /* Dump one block to FP */ int _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) { const byte *buffer; size_t length; int type; ulong n, nkeys, keyinfolen; ulong nuids, uidinfolen; ulong nsigs, siginfolen; ulong rawdata_off, rawdata_len; ulong nserial; const byte *p; buffer = _keybox_get_blob_image (blob, &length); if (length < 32) { fprintf (fp, "[blob too short]\n"); return -1; } n = get32( buffer ); if (n > length) fprintf (fp, "[blob larger than length - output truncated]\n"); else length = n; /* ignore the rest */ fprintf (fp, "Length: %lu\n", n ); type = buffer[4]; switch (type) { case BLOBTYPE_EMPTY: fprintf (fp, "Type: Empty\n"); return 0; case BLOBTYPE_HEADER: fprintf (fp, "Type: Header\n"); return dump_header_blob (buffer, length, fp); case BLOBTYPE_PGP: fprintf (fp, "Type: OpenPGP\n"); break; case BLOBTYPE_X509: fprintf (fp, "Type: X.509\n"); break; default: fprintf (fp, "Type: %d\n", type); fprintf (fp, "[can't dump this blob type]\n"); return 0; } fprintf (fp, "Version: %d\n", buffer[5]); if (length < 40) { fprintf (fp, "[blob too short]\n"); return -1; } n = get16 (buffer + 6); fprintf( fp, "Blob-Flags: %04lX", n); if (n) { int any = 0; fputs (" (", fp); if ((n & 1)) { fputs ("secret", fp); any++; } if ((n & 2)) { if (any) putc (',', fp); fputs ("ephemeral", fp); any++; } putc (')', fp); } putc ('\n', fp); rawdata_off = get32 (buffer + 8); rawdata_len = get32 (buffer + 12); fprintf( fp, "Data-Offset: %lu\n", rawdata_off ); fprintf( fp, "Data-Length: %lu\n", rawdata_len ); nkeys = get16 (buffer + 16); fprintf (fp, "Key-Count: %lu\n", nkeys ); if (!nkeys) fprintf (fp, "[Error: no keys]\n"); if (nkeys > 1 && type == BLOBTYPE_X509) fprintf (fp, "[Error: only one key allowed for X509]\n"); keyinfolen = get16 (buffer + 18 ); fprintf (fp, "Key-Info-Length: %lu\n", keyinfolen); /* fixme: check bounds */ p = buffer + 20; for (n=0; n < nkeys; n++, p += keyinfolen) { int i; ulong kidoff, kflags; fprintf (fp, "Key-Fpr[%lu]: ", n ); for (i=0; i < 20; i++ ) fprintf (fp, "%02X", p[i]); kidoff = get32 (p + 20); fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff ); fprintf (fp, "Key-Kid[%lu]: ", n ); /* fixme: check bounds */ for (i=0; i < 8; i++ ) fprintf (fp, "%02X", buffer[kidoff+i] ); kflags = get16 (p + 24 ); fprintf( fp, "\nKey-Flags[%lu]: %04lX\n", n, kflags); } /* serial number */ fputs ("Serial-No: ", fp); nserial = get16 (p); p += 2; if (!nserial) fputs ("none", fp); else { for (; nserial; nserial--, p++) fprintf (fp, "%02X", *p); } putc ('\n', fp); /* user IDs */ nuids = get16 (p); fprintf (fp, "Uid-Count: %lu\n", nuids ); uidinfolen = get16 (p + 2); fprintf (fp, "Uid-Info-Length: %lu\n", uidinfolen); /* fixme: check bounds */ p += 4; for (n=0; n < nuids; n++, p += uidinfolen) { ulong uidoff, uidlen, uflags; uidoff = get32( p ); uidlen = get32( p+4 ); if (type == BLOBTYPE_X509 && !n) { fprintf (fp, "Issuer-Off: %lu\n", uidoff ); fprintf (fp, "Issuer-Len: %lu\n", uidlen ); fprintf (fp, "Issuer: \""); } else if (type == BLOBTYPE_X509 && n == 1) { fprintf (fp, "Subject-Off: %lu\n", uidoff ); fprintf (fp, "Subject-Len: %lu\n", uidlen ); fprintf (fp, "Subject: \""); } else { fprintf (fp, "Uid-Off[%lu]: %lu\n", n, uidoff ); fprintf (fp, "Uid-Len[%lu]: %lu\n", n, uidlen ); fprintf (fp, "Uid[%lu]: \"", n ); } print_string (fp, buffer+uidoff, uidlen, '\"'); fputs ("\"\n", fp); uflags = get16 (p + 8); if (type == BLOBTYPE_X509 && !n) { fprintf (fp, "Issuer-Flags: %04lX\n", uflags ); fprintf (fp, "Issuer-Validity: %d\n", p[10] ); } else if (type == BLOBTYPE_X509 && n == 1) { fprintf (fp, "Subject-Flags: %04lX\n", uflags ); fprintf (fp, "Subject-Validity: %d\n", p[10] ); } else { fprintf (fp, "Uid-Flags[%lu]: %04lX\n", n, uflags ); fprintf (fp, "Uid-Validity[%lu]: %d\n", n, p[10] ); } } nsigs = get16 (p); fprintf (fp, "Sig-Count: %lu\n", nsigs ); siginfolen = get16 (p + 2); fprintf (fp, "Sig-Info-Length: %lu\n", siginfolen ); /* fixme: check bounds */ p += 4; for (n=0; n < nsigs; n++, p += siginfolen) { ulong sflags; sflags = get32 (p); fprintf (fp, "Sig-Expire[%lu]: ", n ); if (!sflags) fputs ("[not checked]", fp); else if (sflags == 1 ) fputs ("[missing key]", fp); else if (sflags == 2 ) fputs ("[bad signature]", fp); else if (sflags < 0x10000000) fprintf (fp, "[bad flag %0lx]", sflags); else if (sflags == 0xffffffff) fputs ("0", fp ); else fputs ("a time"/*strtimestamp( sflags )*/, fp ); putc ('\n', fp ); } fprintf (fp, "Ownertrust: %d\n", p[0] ); fprintf (fp, "All-Validity: %d\n", p[1] ); p += 4; n = get32 (p); p += 4; fprintf (fp, "Recheck-After: %lu\n", n ); n = get32 (p ); p += 4; fprintf( fp, "Latest-Timestamp: %lu\n", n ); n = get32 (p ); p += 4; fprintf (fp, "Created-At: %lu\n", n ); n = get32 (p ); p += 4; fprintf (fp, "Reserved-Space: %lu\n", n ); /* check that the keyblock is at the correct offset and other bounds */ /*fprintf (fp, "Blob-Checksum: [MD5-hash]\n");*/ return 0; } struct file_stats_s { unsigned long too_short_blobs; unsigned long too_large_blobs; unsigned long total_blob_count; unsigned long empty_blob_count; unsigned long header_blob_count; unsigned long pgp_blob_count; unsigned long x509_blob_count; unsigned long unknown_blob_count; unsigned long non_flagged; unsigned long secret_flagged; unsigned long ephemeral_flagged; }; static int update_stats (KEYBOXBLOB blob, struct file_stats_s *s) { const unsigned char *buffer; size_t length; int type; unsigned long n; buffer = _keybox_get_blob_image (blob, &length); if (length < 32) { s->too_short_blobs++; return -1; } n = get32( buffer ); if (n > length) s->too_large_blobs++; else length = n; /* ignore the rest */ s->total_blob_count++; type = buffer[4]; switch (type) { case BLOBTYPE_EMPTY: s->empty_blob_count++; return 0; case BLOBTYPE_HEADER: s->header_blob_count++; return 0; case BLOBTYPE_PGP: s->pgp_blob_count++; break; case BLOBTYPE_X509: s->x509_blob_count++; break; default: s->unknown_blob_count++; return 0; } if (length < 40) { s->too_short_blobs++; return -1; } n = get16 (buffer + 6); if (n) { if ((n & 1)) s->secret_flagged++; if ((n & 2)) s->ephemeral_flagged++; } else s->non_flagged++; return 0; } int _keybox_dump_file (const char *filename, int stats_only, FILE *outfp) { FILE *fp; KEYBOXBLOB blob; int rc; unsigned long count = 0; struct file_stats_s stats; memset (&stats, 0, sizeof stats); if (!filename) { filename = "-"; fp = stdin; } else fp = fopen (filename, "rb"); if (!fp) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); fprintf (outfp, "can't open `%s': %s\n", filename, strerror(errno)); return tmperr; } while ( !(rc = _keybox_read_blob (&blob, fp)) ) { if (stats_only) { update_stats (blob, &stats); } else { fprintf (outfp, "BEGIN-RECORD: %lu\n", count ); _keybox_dump_blob (blob, outfp); fprintf (outfp, "END-RECORD\n"); } _keybox_release_blob (blob); count++; } if (rc == -1) rc = 0; if (rc) fprintf (outfp, "error reading `%s': %s\n", filename, gpg_strerror (rc)); if (fp != stdin) fclose (fp); if (stats_only) { fprintf (outfp, "Total number of blobs: %8lu\n" " header: %8lu\n" " empty: %8lu\n" " openpgp: %8lu\n" " x509: %8lu\n" " non flagged: %8lu\n" " secret flagged: %8lu\n" " ephemeral flagged: %8lu\n", stats.total_blob_count, stats.header_blob_count, stats.empty_blob_count, stats.pgp_blob_count, stats.x509_blob_count, stats.non_flagged, stats.secret_flagged, stats.ephemeral_flagged); if (stats.unknown_blob_count) fprintf (outfp, " unknown blob types: %8lu\n", stats.unknown_blob_count); if (stats.too_short_blobs) fprintf (outfp, " too short blobs: %8lu\n", stats.too_short_blobs); if (stats.too_large_blobs) fprintf (outfp, " too large blobs: %8lu\n", stats.too_large_blobs); } return rc; } diff --git a/kbx/keybox-file.c b/kbx/keybox-file.c index 3883ce607..e68e96cf9 100644 --- a/kbx/keybox-file.c +++ b/kbx/keybox-file.c @@ -1,162 +1,163 @@ /* keybox-file.c - file oeprations * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "keybox-defs.h" #if !defined(HAVE_FTELLO) && !defined(ftello) static off_t ftello (FILE *stream) { long int off; off = ftell (stream); if (off == -1) return (off_t)-1; return off; } #endif /* !defined(HAVE_FTELLO) && !defined(ftello) */ /* Read a block at the current postion and return it in r_blob. r_blob may be NULL to simply skip the current block */ int _keybox_read_blob2 (KEYBOXBLOB *r_blob, FILE *fp, int *skipped_deleted) { unsigned char *image; size_t imagelen = 0; int c1, c2, c3, c4, type; int rc; off_t off; *skipped_deleted = 0; again: *r_blob = NULL; off = ftello (fp); if (off == (off_t)-1) return gpg_error (gpg_err_code_from_errno (errno)); if ((c1 = getc (fp)) == EOF || (c2 = getc (fp)) == EOF || (c3 = getc (fp)) == EOF || (c4 = getc (fp)) == EOF || (type = getc (fp)) == EOF) { if ( c1 == EOF && !ferror (fp) ) return -1; /* eof */ return gpg_error (gpg_err_code_from_errno (errno)); } imagelen = (c1 << 24) | (c2 << 16) | (c3 << 8 ) | c4; if (imagelen > 500000) /* Sanity check. */ return gpg_error (GPG_ERR_TOO_LARGE); if (imagelen < 5) return gpg_error (GPG_ERR_TOO_SHORT); if (!type) { /* Special treatment for empty blobs. */ if (fseek (fp, imagelen-5, SEEK_CUR)) return gpg_error (gpg_err_code_from_errno (errno)); *skipped_deleted = 1; goto again; } image = xtrymalloc (imagelen); if (!image) return gpg_error (gpg_err_code_from_errno (errno)); image[0] = c1; image[1] = c2; image[2] = c3; image[3] = c4; image[4] = type; if (fread (image+5, imagelen-5, 1, fp) != 1) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); xfree (image); return tmperr; } rc = r_blob? _keybox_new_blob (r_blob, image, imagelen, off) : 0; if (rc || !r_blob) xfree (image); return rc; } int _keybox_read_blob (KEYBOXBLOB *r_blob, FILE *fp) { int dummy; return _keybox_read_blob2 (r_blob, fp, &dummy); } /* Write the block to the current file position */ int _keybox_write_blob (KEYBOXBLOB blob, FILE *fp) { const unsigned char *image; size_t length; image = _keybox_get_blob_image (blob, &length); if (fwrite (image, length, 1, fp) != 1) return gpg_error (gpg_err_code_from_errno (errno)); return 0; } /* Write a fresh header type blob. */ int _keybox_write_header_blob (FILE *fp) { unsigned char image[32]; u32 val; memset (image, 0, sizeof image); /* Length of this blob. */ image[3] = 32; image[4] = BLOBTYPE_HEADER; image[5] = 1; /* Version */ memcpy (image+8, "KBXf", 4); val = time (NULL); /* created_at and last maintenance run. */ image[16] = (val >> 24); image[16+1] = (val >> 16); image[16+2] = (val >> 8); image[16+3] = (val ); image[20] = (val >> 24); image[20+1] = (val >> 16); image[20+2] = (val >> 8); image[20+3] = (val ); if (fwrite (image, 32, 1, fp) != 1) return gpg_error (gpg_err_code_from_errno (errno)); return 0; } diff --git a/kbx/keybox-init.c b/kbx/keybox-init.c index 46a29978a..6c01b4f3a 100644 --- a/kbx/keybox-init.c +++ b/kbx/keybox-init.c @@ -1,132 +1,133 @@ /* keybox-init.c - Initalization of the library * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "keybox-defs.h" #define compare_filenames strcmp static KB_NAME kb_names; /* Register a filename for plain keybox files. Returns a pointer to be used to create a handles etc or NULL to indicate that it has already been registered */ void * keybox_register_file (const char *fname, int secret) { KB_NAME kr; for (kr=kb_names; kr; kr = kr->next) { if ( !compare_filenames (kr->fname, fname) ) return NULL; /* already registered */ } kr = xtrymalloc (sizeof *kr + strlen (fname)); if (!kr) return NULL; strcpy (kr->fname, fname); kr->secret = !!secret; /* kr->lockhd = NULL;*/ kr->is_locked = 0; kr->did_full_scan = 0; /* keep a list of all issued pointers */ kr->next = kb_names; kb_names = kr; /* create the offset table the first time a function here is used */ /* if (!kb_offtbl) */ /* kb_offtbl = new_offset_hash_table (); */ return kr; } int keybox_is_writable (void *token) { KB_NAME r = token; return r? !access (r->fname, W_OK) : 0; } /* Create a new handle for the resource associated with TOKEN. SECRET is just a cross-check. The returned handle must be released using keybox_release (). */ KEYBOX_HANDLE keybox_new (void *token, int secret) { KEYBOX_HANDLE hd; KB_NAME resource = token; assert (resource && !resource->secret == !secret); hd = xtrycalloc (1, sizeof *hd); if (hd) { hd->kb = resource; hd->secret = !!secret; } return hd; } void keybox_release (KEYBOX_HANDLE hd) { if (!hd) return; _keybox_release_blob (hd->found.blob); if (hd->fp) { fclose (hd->fp); hd->fp = NULL; } xfree (hd->word_match.name); xfree (hd->word_match.pattern); xfree (hd); } const char * keybox_get_resource_name (KEYBOX_HANDLE hd) { if (!hd || !hd->kb) return NULL; return hd->kb->fname; } int keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes) { if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); hd->ephemeral = yes; return 0; } diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index 7401949c9..8ac713979 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -1,516 +1,517 @@ /* keybox-openpgp.c - OpenPGP key parsing * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This is a simple OpenPGP parser suitable for all OpenPGP key material. It just provides the functionality required to build and parse an KBX OpenPGP key blob. Thus it is not a complete parser. However it is self-contained and optimized for fast in-memory parsing. Note that we don't support old ElGamal v3 keys anymore. */ #include #include #include #include #include #include #include "keybox-defs.h" #include enum packet_types { PKT_NONE =0, PKT_PUBKEY_ENC =1, /* public key encrypted packet */ PKT_SIGNATURE =2, /* secret key encrypted packet */ PKT_SYMKEY_ENC =3, /* session key packet (OpenPGP)*/ PKT_ONEPASS_SIG =4, /* one pass sig packet (OpenPGP)*/ PKT_SECRET_KEY =5, /* secret key */ PKT_PUBLIC_KEY =6, /* public key */ PKT_SECRET_SUBKEY =7, /* secret subkey (OpenPGP) */ PKT_COMPRESSED =8, /* compressed data packet */ PKT_ENCRYPTED =9, /* conventional encrypted data */ PKT_MARKER =10, /* marker packet (OpenPGP) */ PKT_PLAINTEXT =11, /* plaintext data with filename and mode */ PKT_RING_TRUST =12, /* keyring trust packet */ PKT_USER_ID =13, /* user id packet */ PKT_PUBLIC_SUBKEY =14, /* public subkey (OpenPGP) */ PKT_OLD_COMMENT =16, /* comment packet from an OpenPGP draft */ PKT_ATTRIBUTE =17, /* PGP's attribute packet */ PKT_ENCRYPTED_MDC =18, /* integrity protected encrypted data */ PKT_MDC =19, /* manipulation detection code packet */ PKT_COMMENT =61, /* new comment packet (private) */ PKT_GPG_CONTROL =63 /* internal control packet */ }; /* Assume a valid OpenPGP packet at the address pointed to by BUFBTR which is of amaximum length as stored at BUFLEN. Return the header information of that packet and advance the pointer stored at BUFPTR to the next packet; also adjust the length stored at BUFLEN to match the remaining bytes. If there are no more packets, store NULL at BUFPTR. Return an non-zero error code on failure or the follwing data on success: R_DATAPKT = Pointer to the begin of the packet data. R_DATALEN = Length of this data. This has already been checked to fit into the buffer. R_PKTTYPE = The packet type. R_NTOTAL = The total number of bytes of this packet Note that these values are only updated on success. */ static gpg_error_t next_packet (unsigned char const **bufptr, size_t *buflen, unsigned char const **r_data, size_t *r_datalen, int *r_pkttype, size_t *r_ntotal) { const unsigned char *buf = *bufptr; size_t len = *buflen; int c, ctb, pkttype; unsigned long pktlen; if (!len) return gpg_error (GPG_ERR_NO_DATA); ctb = *buf++; len--; if ( !(ctb & 0x80) ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid CTB. */ pktlen = 0; if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */ { pkttype = (ctb & 0x3f); if (!len) return gpg_error (GPG_ERR_INV_PACKET); /* No 1st length byte. */ c = *buf++; len--; if (pkttype == PKT_COMPRESSED) return gpg_error (GPG_ERR_UNEXPECTED); /* ... packet in a keyblock. */ if ( c < 192 ) pktlen = c; else if ( c < 224 ) { pktlen = (c - 192) * 256; if (!len) return gpg_error (GPG_ERR_INV_PACKET); /* No 2nd length byte. */ c = *buf++; len--; pktlen += c + 192; } else if (c == 255) { if (len <4 ) return gpg_error (GPG_ERR_INV_PACKET); /* No length bytes. */ pktlen = (*buf++) << 24; pktlen |= (*buf++) << 16; pktlen |= (*buf++) << 8; pktlen |= (*buf++); len -= 4; } else /* Partial length encoding is not allowed for key packets. */ return gpg_error (GPG_ERR_UNEXPECTED); } else /* Old style CTB. */ { int lenbytes; pktlen = 0; pkttype = (ctb>>2)&0xf; lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); if (!lenbytes) /* Not allowed in key packets. */ return gpg_error (GPG_ERR_UNEXPECTED); if (len < lenbytes) return gpg_error (GPG_ERR_INV_PACKET); /* Not enough length bytes. */ for (; lenbytes; lenbytes--) { pktlen <<= 8; pktlen |= *buf++; len--; } } /* Do some basic sanity check. */ switch (pkttype) { case PKT_SIGNATURE: case PKT_SECRET_KEY: case PKT_PUBLIC_KEY: case PKT_SECRET_SUBKEY: case PKT_MARKER: case PKT_RING_TRUST: case PKT_USER_ID: case PKT_PUBLIC_SUBKEY: case PKT_OLD_COMMENT: case PKT_ATTRIBUTE: case PKT_COMMENT: case PKT_GPG_CONTROL: break; /* Okay these are allowed packets. */ default: return gpg_error (GPG_ERR_UNEXPECTED); } if (pktlen == 0xffffffff) return gpg_error (GPG_ERR_INV_PACKET); if (pktlen > len) return gpg_error (GPG_ERR_INV_PACKET); /* Packet length header too long. */ *r_data = buf; *r_datalen = pktlen; *r_pkttype = pkttype; *r_ntotal = (buf - *bufptr) + pktlen; *bufptr = buf + pktlen; *buflen = len - pktlen; if (!*buflen) *bufptr = NULL; return 0; } /* Parse a key packet and store the ionformation in KI. */ static gpg_error_t parse_key (const unsigned char *data, size_t datalen, struct _keybox_openpgp_key_info *ki) { gpg_error_t err; const unsigned char *data_start = data; int i, version, algorithm; size_t n; unsigned long timestamp, expiredate; int npkey; unsigned char hashbuffer[768]; const unsigned char *mpi_n = NULL; size_t mpi_n_len = 0, mpi_e_len = 0; gcry_md_hd_t md; if (datalen < 5) return gpg_error (GPG_ERR_INV_PACKET); version = *data++; datalen--; if (version < 2 || version > 4 ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */ timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3])); data +=4; datalen -=4; if (version < 4) { unsigned short ndays; if (datalen < 2) return gpg_error (GPG_ERR_INV_PACKET); ndays = ((data[0]<<8)|(data[1])); data +=2; datalen -= 2; if (ndays) expiredate = ndays? (timestamp + ndays * 86400L) : 0; } else expiredate = 0; /* This is stored in the self-signature. */ if (!datalen) return gpg_error (GPG_ERR_INV_PACKET); algorithm = *data++; datalen--; switch (algorithm) { case 1: case 2: case 3: /* RSA */ npkey = 2; break; case 16: case 20: /* Elgamal */ npkey = 3; break; case 17: /* DSA */ npkey = 4; break; default: /* Unknown algorithm. */ return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); } for (i=0; i < npkey; i++ ) { unsigned int nbits, nbytes; if (datalen < 2) return gpg_error (GPG_ERR_INV_PACKET); nbits = ((data[0]<<8)|(data[1])); data += 2; datalen -=2; nbytes = (nbits+7) / 8; if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); /* For use by v3 fingerprint calculation we need to know the RSA modulus and exponent. */ if (i==0) { mpi_n = data; mpi_n_len = nbytes; } else if (i==1) mpi_e_len = nbytes; data += nbytes; datalen -= nbytes; } n = data - data_start; if (version < 4) { /* We do not support any other algorithm than RSA in v3 packets. */ if (algorithm < 1 || algorithm > 3) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); err = gcry_md_open (&md, GCRY_MD_MD5, 0); if (err) return err; /* Oops */ gcry_md_write (md, mpi_n, mpi_n_len); gcry_md_write (md, mpi_n+mpi_n_len+2, mpi_e_len); memcpy (ki->fpr, gcry_md_read (md, 0), 16); gcry_md_close (md); ki->fprlen = 16; if (mpi_n_len < 8) { /* Moduli less than 64 bit are out of the specs scope. Zero them out becuase this is what gpg does too. */ memset (ki->keyid, 0, 8); } else memcpy (ki->keyid, mpi_n + mpi_n_len - 8, 8); } else { /* Its a pitty that we need to prefix the buffer with the tag and a length header: We can't simply pass it to the fast hashing fucntion for that reason. It might be a good idea to have a scatter-gather enabled hash function. What we do here is to use a static buffer if this one is large enough and only use the regular hash fucntions if this buffer is not large enough. */ if ( 3 + n < sizeof hashbuffer ) { hashbuffer[0] = 0x99; /* CTB */ hashbuffer[1] = (n >> 8); /* 2 byte length header. */ hashbuffer[2] = n; memcpy (hashbuffer + 3, data_start, n); gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); } else { err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) return err; /* Oops */ gcry_md_putc (md, 0x99 ); /* CTB */ gcry_md_putc (md, (n >> 8) ); /* 2 byte length header. */ gcry_md_putc (md, n ); gcry_md_write (md, data_start, n); memcpy (ki->fpr, gcry_md_read (md, 0), 20); gcry_md_close (md); } ki->fprlen = 20; memcpy (ki->keyid, ki->fpr+12, 8); } return 0; } /* The caller must pass the address of an INFO structure which will get filled on success with information pertaining to the OpenPGP keyblock IMAGE of length IMAGELEN. Note that a caller does only need to release this INFO structure when the function returns success. If NPARSED is not NULL the actual number of bytes parsed will be stored at this address. */ gpg_error_t _keybox_parse_openpgp (const unsigned char *image, size_t imagelen, size_t *nparsed, keybox_openpgp_info_t info) { gpg_error_t err = 0; const unsigned char *image_start, *data; size_t n, datalen; int pkttype; int first = 1; struct _keybox_openpgp_key_info *k, **ktail = NULL; struct _keybox_openpgp_uid_info *u, **utail = NULL; memset (info, 0, sizeof *info); if (nparsed) *nparsed = 0; image_start = image; while (image) { err = next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n); if (err) break; if (first) { if (pkttype == PKT_PUBLIC_KEY) ; else if (pkttype == PKT_SECRET_KEY) info->is_secret = 1; else { err = gpg_error (GPG_ERR_UNEXPECTED); break; } first = 0; } else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) break; /* Next keyblock encountered - ready. */ if (nparsed) *nparsed += n; if (pkttype == PKT_SIGNATURE) { /* For now we only count the total number of signatures. */ info->nsigs++; } else if (pkttype == PKT_USER_ID) { info->nuids++; if (info->nuids == 1) { info->uids.off = data - image_start; info->uids.len = datalen; utail = &info->uids.next; } else { u = xtrycalloc (1, sizeof *u); if (!u) { err = gpg_error_from_errno (errno); break; } u->off = data - image_start; u->len = datalen; *utail = u; utail = &u->next; } } else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) { err = parse_key (data, datalen, &info->primary); if (err) break; } else if( pkttype == PKT_PUBLIC_SUBKEY && datalen && *data == '#' ) { /* Early versions of GnuPG used old PGP comment packets; * luckily all those comments are prefixed by a hash * sign - ignore these packets. */ } else if (pkttype == PKT_PUBLIC_SUBKEY || pkttype == PKT_SECRET_SUBKEY) { info->nsubkeys++; if (info->nsubkeys == 1) { err = parse_key (data, datalen, &info->subkeys); if (err) { info->nsubkeys--; if (gpg_err_code (err) != GPG_ERR_UNKNOWN_ALGORITHM) break; /* We ignore subkeys with unknown algorithms. */ } else ktail = &info->subkeys.next; } else { k = xtrycalloc (1, sizeof *k); if (!k) { err = gpg_error_from_errno (errno); break; } err = parse_key (data, datalen, k); if (err) { xfree (k); info->nsubkeys--; if (gpg_err_code (err) != GPG_ERR_UNKNOWN_ALGORITHM) break; /* We ignore subkeys with unknown algorithms. */ } else { *ktail = k; ktail = &k->next; } } } } if (err) { _keybox_destroy_openpgp_info (info); if (!first && (gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM || gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM)) { /* We are able to skip to the end of this keyblock. */ while (image) { if (next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n) ) break; /* Another error - stop here. */ if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) break; /* Next keyblock encountered - ready. */ if (nparsed) *nparsed += n; } } } return err; } /* Release any malloced data in INFO but not INFO itself! */ void _keybox_destroy_openpgp_info (keybox_openpgp_info_t info) { struct _keybox_openpgp_key_info *k, *k2; struct _keybox_openpgp_uid_info *u, *u2; assert (!info->primary.next); for (k=info->subkeys.next; k; k = k2) { k2 = k->next; xfree (k); } for (u=info->uids.next; u; u = u2) { u2 = u->next; xfree (u); } } diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index 4be59c27d..f3a69d0f1 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -1,72 +1,73 @@ /* keybox-search-desc.h - Keybox serch description * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This file is a temporary kludge until we can come up with solution to share this description between keybox and the application specific keydb */ #ifndef KEYBOX_SEARCH_DESC_H #define KEYBOX_SEARCH_DESC_H 1 typedef enum { KEYDB_SEARCH_MODE_NONE, KEYDB_SEARCH_MODE_EXACT, KEYDB_SEARCH_MODE_SUBSTR, KEYDB_SEARCH_MODE_MAIL, KEYDB_SEARCH_MODE_MAILSUB, KEYDB_SEARCH_MODE_MAILEND, KEYDB_SEARCH_MODE_WORDS, KEYDB_SEARCH_MODE_SHORT_KID, KEYDB_SEARCH_MODE_LONG_KID, KEYDB_SEARCH_MODE_FPR16, KEYDB_SEARCH_MODE_FPR20, KEYDB_SEARCH_MODE_FPR, KEYDB_SEARCH_MODE_ISSUER, KEYDB_SEARCH_MODE_ISSUER_SN, KEYDB_SEARCH_MODE_SN, KEYDB_SEARCH_MODE_SUBJECT, KEYDB_SEARCH_MODE_FIRST, KEYDB_SEARCH_MODE_NEXT } KeydbSearchMode; struct keydb_search_desc { KeydbSearchMode mode; int (*skipfnc)(void *,void*); /* used to be: void*, u32* */ void *skipfncvalue; const unsigned char *sn; int snlen; /* -1 := sn is a hex string */ union { const char *name; unsigned char fpr[24]; unsigned char kid[8]; } u; }; struct keydb_search_desc; typedef struct keydb_search_desc KEYDB_SEARCH_DESC; typedef struct keydb_search_desc KEYBOX_SEARCH_DESC; #endif /*KEYBOX_SEARCH_DESC_H*/ diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 2ce3c1923..f95e6eb06 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -1,944 +1,945 @@ /* keybox-search.c - Search operations * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "../jnlib/stringhelp.h" /* ascii_xxxx() */ #include "keybox-defs.h" #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)) struct sn_array_s { int snlen; unsigned char *sn; }; static inline ulong get32 (const byte *buffer) { ulong a; a = *buffer << 24; a |= buffer[1] << 16; a |= buffer[2] << 8; a |= buffer[3]; return a; } static inline ulong get16 (const byte *buffer) { ulong a; a = *buffer << 8; a |= buffer[1]; return a; } static inline int blob_get_type (KEYBOXBLOB blob) { const unsigned char *buffer; size_t length; buffer = _keybox_get_blob_image (blob, &length); if (length < 32) return -1; /* blob too short */ return buffer[4]; } static inline unsigned int blob_get_blob_flags (KEYBOXBLOB blob) { const unsigned char *buffer; size_t length; buffer = _keybox_get_blob_image (blob, &length); if (length < 8) return 0; /* oops */ return get16 (buffer + 6); } /* Return information on the flag WHAT within the blob BUFFER,LENGTH. Return the offset and the length (in bytes) of the flag in FLAGOFF,FLAG_SIZE. */ gpg_err_code_t _keybox_get_flag_location (const unsigned char *buffer, size_t length, int what, size_t *flag_off, size_t *flag_size) { size_t pos; size_t nkeys, keyinfolen; size_t nuids, uidinfolen; size_t nserial; size_t nsigs, siginfolen; switch (what) { case KEYBOX_FLAG_BLOB: if (length < 8) return GPG_ERR_INV_OBJ; *flag_off = 6; *flag_size = 2; break; case KEYBOX_FLAG_OWNERTRUST: case KEYBOX_FLAG_VALIDITY: case KEYBOX_FLAG_CREATED_AT: if (length < 20) return GPG_ERR_INV_OBJ; /* Key info. */ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return GPG_ERR_INV_OBJ; pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return GPG_ERR_INV_OBJ; /* Out of bounds. */ /* Serial number. */ nserial = get16 (buffer+pos); pos += 2 + nserial; if (pos+4 > length) return GPG_ERR_INV_OBJ; /* Out of bounds. */ /* User IDs. */ nuids = get16 (buffer + pos); pos += 2; uidinfolen = get16 (buffer + pos); pos += 2; if (uidinfolen < 12 ) return GPG_ERR_INV_OBJ; pos += uidinfolen*nuids; if (pos+4 > length) return GPG_ERR_INV_OBJ ; /* Out of bounds. */ /* Signature info. */ nsigs = get16 (buffer + pos); pos += 2; siginfolen = get16 (buffer + pos); pos += 2; if (siginfolen < 4 ) return GPG_ERR_INV_OBJ; pos += siginfolen*nsigs; if (pos+1+1+2+4+4+4+4 > length) return GPG_ERR_INV_OBJ ; /* Out of bounds. */ *flag_size = 1; *flag_off = pos; switch (what) { case KEYBOX_FLAG_VALIDITY: *flag_off += 1; break; case KEYBOX_FLAG_CREATED_AT: *flag_size = 4; *flag_off += 1+2+4+4+4; break; default: break; } break; default: return GPG_ERR_INV_FLAG; } return 0; } /* Return one of the flags WHAT in VALUE from teh blob BUFFER of LENGTH bytes. Return 0 on success or an raw error code. */ static gpg_err_code_t get_flag_from_image (const unsigned char *buffer, size_t length, int what, unsigned int *value) { gpg_err_code_t ec; size_t pos, size; *value = 0; ec = _keybox_get_flag_location (buffer, length, what, &pos, &size); if (!ec) switch (size) { case 1: *value = buffer[pos]; break; case 2: *value = get16 (buffer + pos); break; case 4: *value = get32 (buffer + pos); break; default: ec = GPG_ERR_BUG; break; } return ec; } static int blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; size_t nserial; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return 0; /* out of bounds */ /*serial*/ nserial = get16 (buffer+pos); off = pos + 2; if (off+nserial > length) return 0; /* out of bounds */ return nserial == snlen && !memcmp (buffer+off, sn, snlen); } static int blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; int idx; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20; if (pos + keyinfolen*nkeys > length) return 0; /* out of bounds */ for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; if (!memcmp (buffer + off, fpr, 20)) return 1; /* found */ } return 0; /* not found */ } static int blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, int fproff, int fprlen) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; int idx; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20; if (pos + keyinfolen*nkeys > length) return 0; /* out of bounds */ for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; if (!memcmp (buffer + off + fproff, fpr, fprlen)) return 1; /* found */ } return 0; /* not found */ } static int blob_cmp_name (KEYBOXBLOB blob, int idx, const char *name, size_t namelen, int substr) { const unsigned char *buffer; size_t length; size_t pos, off, len; size_t nkeys, keyinfolen; size_t nuids, uidinfolen; size_t nserial; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return 0; /* out of bounds */ /*serial*/ nserial = get16 (buffer+pos); pos += 2 + nserial; if (pos+4 > length) return 0; /* out of bounds */ /* user ids*/ nuids = get16 (buffer + pos); pos += 2; uidinfolen = get16 (buffer + pos); pos += 2; if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */) return 0; /* invalid blob */ if (pos + uidinfolen*nuids > length) return 0; /* out of bounds */ if (idx < 0) { /* compare all names starting with that (negated) index */ idx = -idx; for ( ;idx < nuids; idx++) { size_t mypos = pos; mypos += idx*uidinfolen; off = get32 (buffer+mypos); len = get32 (buffer+mypos+4); if (off+len > length) return 0; /* error: better stop here out of bounds */ if (len < 1) continue; /* empty name */ if (substr) { if (ascii_memcasemem (buffer+off, len, name, namelen)) return 1; /* found */ } else { if (len == namelen && !memcmp (buffer+off, name, len)) return 1; /* found */ } } return 0; /* not found */ } else { if (idx > nuids) return 0; /* no user ID with that idx */ pos += idx*uidinfolen; off = get32 (buffer+pos); len = get32 (buffer+pos+4); if (off+len > length) return 0; /* out of bounds */ if (len < 1) return 0; /* empty name */ if (substr) { return !!ascii_memcasemem (buffer+off, len, name, namelen); } else { return len == namelen && !memcmp (buffer+off, name, len); } } } /* compare all email addresses of the subject. With SUBSTR given as True a substring search is done in the mail address */ static int blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr) { const unsigned char *buffer; size_t length; size_t pos, off, len; size_t nkeys, keyinfolen; size_t nuids, uidinfolen; size_t nserial; int idx; /* fixme: this code is common to blob_cmp_mail */ buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); if (keyinfolen < 28) return 0; /* invalid blob */ pos = 20 + keyinfolen*nkeys; if (pos+2 > length) return 0; /* out of bounds */ /*serial*/ nserial = get16 (buffer+pos); pos += 2 + nserial; if (pos+4 > length) return 0; /* out of bounds */ /* user ids*/ nuids = get16 (buffer + pos); pos += 2; uidinfolen = get16 (buffer + pos); pos += 2; if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */) return 0; /* invalid blob */ if (pos + uidinfolen*nuids > length) return 0; /* out of bounds */ if (namelen < 1) return 0; for (idx=1 ;idx < nuids; idx++) { size_t mypos = pos; mypos += idx*uidinfolen; off = get32 (buffer+mypos); len = get32 (buffer+mypos+4); if (off+len > length) return 0; /* error: better stop here out of bounds */ if (len < 2 || buffer[off] != '<') continue; /* empty name or trailing 0 not stored */ len--; /* one back */ if ( len < 3 || buffer[off+len] != '>') continue; /* not a proper email address */ len--; if (substr) { if (ascii_memcasemem (buffer+off+1, len, name, namelen)) return 1; /* found */ } else { if (len == namelen && !ascii_memcasecmp (buffer+off+1, name, len)) return 1; /* found */ } } return 0; /* not found */ } /* The has_foo functions are used as helpers for search */ static inline int has_short_kid (KEYBOXBLOB blob, const unsigned char *kid) { return blob_cmp_fpr_part (blob, kid+4, 16, 4); } static inline int has_long_kid (KEYBOXBLOB blob, const unsigned char *kid) { return blob_cmp_fpr_part (blob, kid, 12, 8); } static inline int has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr) { return blob_cmp_fpr (blob, fpr); } static inline int has_issuer (KEYBOXBLOB blob, const char *name) { size_t namelen; return_val_if_fail (name, 0); if (blob_get_type (blob) != BLOBTYPE_X509) return 0; namelen = strlen (name); return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0); } static inline int has_issuer_sn (KEYBOXBLOB blob, const char *name, const unsigned char *sn, int snlen) { size_t namelen; return_val_if_fail (name, 0); return_val_if_fail (sn, 0); if (blob_get_type (blob) != BLOBTYPE_X509) return 0; namelen = strlen (name); return (blob_cmp_sn (blob, sn, snlen) && blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0)); } static inline int has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) { return_val_if_fail (sn, 0); if (blob_get_type (blob) != BLOBTYPE_X509) return 0; return blob_cmp_sn (blob, sn, snlen); } static inline int has_subject (KEYBOXBLOB blob, const char *name) { size_t namelen; return_val_if_fail (name, 0); if (blob_get_type (blob) != BLOBTYPE_X509) return 0; namelen = strlen (name); return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0); } static inline int has_subject_or_alt (KEYBOXBLOB blob, const char *name, int substr) { size_t namelen; return_val_if_fail (name, 0); if (blob_get_type (blob) != BLOBTYPE_X509) return 0; namelen = strlen (name); return blob_cmp_name (blob, -1 /* all subject names*/, name, namelen, substr); } static inline int has_mail (KEYBOXBLOB blob, const char *name, int substr) { size_t namelen; return_val_if_fail (name, 0); if (blob_get_type (blob) != BLOBTYPE_X509) return 0; namelen = strlen (name); if (namelen && name[namelen-1] == '>') namelen--; return blob_cmp_mail (blob, name, namelen, substr); } static void release_sn_array (struct sn_array_s *array, size_t size) { size_t n; for (n=0; n < size; n++) xfree (array[n].sn); xfree (array); } /* The search API */ int keybox_search_reset (KEYBOX_HANDLE hd) { if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (hd->found.blob) { _keybox_release_blob (hd->found.blob); hd->found.blob = NULL; } if (hd->fp) { fclose (hd->fp); hd->fp = NULL; } hd->error = 0; hd->eof = 0; return 0; } /* Note: When in ephemeral mode the search function does visit all blobs but in standard mode, blobs flagged as ephemeral are ignored. */ int keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc) { int rc; size_t n; int need_words, any_skip; KEYBOXBLOB blob = NULL; struct sn_array_s *sn_array = NULL; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); /* clear last found result */ if (hd->found.blob) { _keybox_release_blob (hd->found.blob); hd->found.blob = NULL; } if (hd->error) return hd->error; /* still in error state */ if (hd->eof) return -1; /* still EOF */ /* figure out what information we need */ need_words = any_skip = 0; for (n=0; n < ndesc; n++) { switch (desc[n].mode) { case KEYDB_SEARCH_MODE_WORDS: need_words = 1; break; case KEYDB_SEARCH_MODE_FIRST: /* always restart the search in this mode */ keybox_search_reset (hd); break; default: break; } if (desc[n].skipfnc) any_skip = 1; if (desc[n].snlen == -1 && !sn_array) { sn_array = xtrycalloc (ndesc, sizeof *sn_array); if (!sn_array) return (hd->error = gpg_error (gpg_err_code_from_errno (errno))); } } if (!hd->fp) { hd->fp = fopen (hd->kb->fname, "rb"); if (!hd->fp) { hd->error = gpg_error (gpg_err_code_from_errno (errno)); xfree (sn_array); return hd->error; } } /* kludge: we need to convert an SN given as hexstring to it's binary representation - in some cases we are not able to store it in the search descriptor, because due to its usage it is not possible to free allocated memory */ if (sn_array) { const unsigned char *s; int i, odd; size_t snlen; for (n=0; n < ndesc; n++) { if (!desc[n].sn) ; else if (desc[n].snlen == -1) { unsigned char *sn; s = desc[n].sn; for (i=0; *s && *s != '/'; s++, i++) ; odd = (i & 1); snlen = (i+1)/2; sn_array[n].sn = xtrymalloc (snlen); if (!sn_array[n].sn) { hd->error = gpg_error (gpg_err_code_from_errno (errno)); release_sn_array (sn_array, n); return hd->error; } sn_array[n].snlen = snlen; sn = sn_array[n].sn; s = desc[n].sn; if (odd) { *sn++ = xtoi_1 (s); s++; } for (; *s && *s != '/'; s += 2) *sn++ = xtoi_2 (s); } else { const unsigned char *sn; sn = desc[n].sn; snlen = desc[n].snlen; sn_array[n].sn = xtrymalloc (snlen); if (!sn_array[n].sn) { hd->error = gpg_error (gpg_err_code_from_errno (errno)); release_sn_array (sn_array, n); return hd->error; } sn_array[n].snlen = snlen; memcpy (sn_array[n].sn, sn, snlen); } } } for (;;) { unsigned int blobflags; _keybox_release_blob (blob); blob = NULL; rc = _keybox_read_blob (&blob, hd->fp); if (rc) break; if (blob_get_type (blob) == BLOBTYPE_HEADER) continue; blobflags = blob_get_blob_flags (blob); if (!hd->ephemeral && (blobflags & 2)) continue; /* not in ephemeral mode but blob is flagged ephemeral */ for (n=0; n < ndesc; n++) { switch (desc[n].mode) { case KEYDB_SEARCH_MODE_NONE: never_reached (); break; case KEYDB_SEARCH_MODE_EXACT: if (has_subject_or_alt (blob, desc[n].u.name, 0)) goto found; break; case KEYDB_SEARCH_MODE_MAIL: if (has_mail (blob, desc[n].u.name, 0)) goto found; break; case KEYDB_SEARCH_MODE_MAILSUB: if (has_mail (blob, desc[n].u.name, 1)) goto found; break; case KEYDB_SEARCH_MODE_SUBSTR: if (has_subject_or_alt (blob, desc[n].u.name, 1)) goto found; break; case KEYDB_SEARCH_MODE_MAILEND: case KEYDB_SEARCH_MODE_WORDS: never_reached (); /* not yet implemented */ break; case KEYDB_SEARCH_MODE_ISSUER: if (has_issuer (blob, desc[n].u.name)) goto found; break; case KEYDB_SEARCH_MODE_ISSUER_SN: if (has_issuer_sn (blob, desc[n].u.name, sn_array? sn_array[n].sn : desc[n].sn, sn_array? sn_array[n].snlen : desc[n].snlen)) goto found; break; case KEYDB_SEARCH_MODE_SN: if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, sn_array? sn_array[n].snlen : desc[n].snlen)) goto found; break; case KEYDB_SEARCH_MODE_SUBJECT: if (has_subject (blob, desc[n].u.name)) goto found; break; case KEYDB_SEARCH_MODE_SHORT_KID: if (has_short_kid (blob, desc[n].u.kid)) goto found; break; case KEYDB_SEARCH_MODE_LONG_KID: if (has_long_kid (blob, desc[n].u.kid)) goto found; break; case KEYDB_SEARCH_MODE_FPR: case KEYDB_SEARCH_MODE_FPR20: if (has_fingerprint (blob, desc[n].u.fpr)) goto found; break; case KEYDB_SEARCH_MODE_FIRST: goto found; break; case KEYDB_SEARCH_MODE_NEXT: goto found; break; default: rc = gpg_error (GPG_ERR_INV_VALUE); goto found; } } continue; found: for (n=any_skip?0:ndesc; n < ndesc; n++) { /* if (desc[n].skipfnc */ /* && desc[n].skipfnc (desc[n].skipfncvalue, aki)) */ /* break; */ } if (n == ndesc) break; /* got it */ } if (!rc) { hd->found.blob = blob; } else if (rc == -1) { _keybox_release_blob (blob); hd->eof = 1; } else { _keybox_release_blob (blob); hd->error = rc; } if (sn_array) release_sn_array (sn_array, ndesc); return rc; } /* Functions to return a certificate or a keyblock. To be used after a successful search operation. */ #ifdef KEYBOX_WITH_X509 /* Return the last found cert. Caller must free it. */ int keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert) { const unsigned char *buffer; size_t length; size_t cert_off, cert_len; ksba_reader_t reader = NULL; ksba_cert_t cert = NULL; int rc; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); if (blob_get_type (hd->found.blob) != BLOBTYPE_X509) return gpg_error (GPG_ERR_WRONG_BLOB_TYPE); buffer = _keybox_get_blob_image (hd->found.blob, &length); if (length < 40) return gpg_error (GPG_ERR_TOO_SHORT); cert_off = get32 (buffer+8); cert_len = get32 (buffer+12); if (cert_off+cert_len > length) return gpg_error (GPG_ERR_TOO_SHORT); rc = ksba_reader_new (&reader); if (rc) return rc; rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len); if (rc) { ksba_reader_release (reader); /* fixme: need to map the error codes */ return gpg_error (GPG_ERR_GENERAL); } rc = ksba_cert_new (&cert); if (rc) { ksba_reader_release (reader); return rc; } rc = ksba_cert_read_der (cert, reader); if (rc) { ksba_cert_release (cert); ksba_reader_release (reader); /* fixme: need to map the error codes */ return gpg_error (GPG_ERR_GENERAL); } *r_cert = cert; ksba_reader_release (reader); return 0; } #endif /*KEYBOX_WITH_X509*/ /* Return the flags named WHAT at the address of VALUE. IDX is used only for certain flags and should be 0 if not required. */ int keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value) { const unsigned char *buffer; size_t length; gpg_err_code_t ec; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); buffer = _keybox_get_blob_image (hd->found.blob, &length); ec = get_flag_from_image (buffer, length, what, value); return ec? gpg_error (ec):0; } diff --git a/kbx/keybox-update.c b/kbx/keybox-update.c index a16c18e23..bb43d287b 100644 --- a/kbx/keybox-update.c +++ b/kbx/keybox-update.c @@ -1,730 +1,731 @@ /* keybox-update.c - keybox update operations * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include "keybox-defs.h" #define EXTSEP_S "." #if !defined(HAVE_FSEEKO) && !defined(fseeko) #ifdef HAVE_LIMITS_H # include #endif #ifndef LONG_MAX # define LONG_MAX ((long) ((unsigned long) -1 >> 1)) #endif #ifndef LONG_MIN # define LONG_MIN (-1 - LONG_MAX) #endif /**************** * A substitute for fseeko, for hosts that don't have it. */ static int fseeko (FILE * stream, off_t newpos, int whence) { while (newpos != (long) newpos) { long pos = newpos < 0 ? LONG_MIN : LONG_MAX; if (fseek (stream, pos, whence) != 0) return -1; newpos -= pos; whence = SEEK_CUR; } return fseek (stream, (long) newpos, whence); } #endif /* !defined(HAVE_FSEEKO) && !defined(fseeko) */ static int create_tmp_file (const char *template, char **r_bakfname, char **r_tmpfname, FILE **r_fp) { char *bakfname, *tmpfname; *r_bakfname = NULL; *r_tmpfname = NULL; # ifdef USE_ONLY_8DOT3 /* Here is another Windoze bug?: * you cant rename("pubring.kbx.tmp", "pubring.kbx"); * but rename("pubring.kbx.tmp", "pubring.aaa"); * works. So we replace .kbx by .bak or .tmp */ if (strlen (template) > 4 && !strcmp (template+strlen(template)-4, EXTSEP_S "kbx") ) { bakfname = xtrymalloc (strlen (template) + 1); if (!bakfname) return gpg_error (gpg_err_code_from_errno (errno)); strcpy (bakfname, template); strcpy (bakfname+strlen(template)-4, EXTSEP_S "bak"); tmpfname = xtrymalloc (strlen (template) + 1); if (!tmpfname) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); xfree (bakfname); return tmperr; } strcpy (tmpfname,template); strcpy (tmpfname + strlen (template)-4, EXTSEP_S "tmp"); } else { /* File does not end with kbx; hmmm. */ bakfname = xtrymalloc ( strlen (template) + 5); if (!bakfname) return gpg_error (gpg_err_code_from_errno (errno)); strcpy (stpcpy (bakfname, template), EXTSEP_S "bak"); tmpfname = xtrymalloc ( strlen (template) + 5); if (!tmpfname) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); xfree (bakfname); return tmperr; } strcpy (stpcpy (tmpfname, template), EXTSEP_S "tmp"); } # else /* Posix file names */ bakfname = xtrymalloc (strlen (template) + 2); if (!bakfname) return gpg_error (gpg_err_code_from_errno (errno)); strcpy (stpcpy (bakfname,template),"~"); tmpfname = xtrymalloc ( strlen (template) + 5); if (!tmpfname) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); xfree (bakfname); return tmperr; } strcpy (stpcpy (tmpfname,template), EXTSEP_S "tmp"); # endif /* Posix filename */ *r_fp = fopen (tmpfname, "wb"); if (!*r_fp) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); xfree (tmpfname); xfree (bakfname); return tmperr; } *r_bakfname = bakfname; *r_tmpfname = tmpfname; return 0; } static int rename_tmp_file (const char *bakfname, const char *tmpfname, const char *fname, int secret ) { int rc=0; /* restrict the permissions for secret keyboxs */ #ifndef HAVE_DOSISH_SYSTEM /* if (secret && !opt.preserve_permissions) */ /* { */ /* if (chmod (tmpfname, S_IRUSR | S_IWUSR) ) */ /* { */ /* log_debug ("chmod of `%s' failed: %s\n", */ /* tmpfname, strerror(errno) ); */ /* return KEYBOX_Write_File; */ /* } */ /* } */ #endif /* fixme: invalidate close caches (not used with stdio)*/ /* iobuf_ioctl (NULL, 2, 0, (char*)tmpfname ); */ /* iobuf_ioctl (NULL, 2, 0, (char*)bakfname ); */ /* iobuf_ioctl (NULL, 2, 0, (char*)fname ); */ /* first make a backup file except for secret keyboxs */ if (!secret) { #if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) remove (bakfname); #endif if (rename (fname, bakfname) ) { return gpg_error (gpg_err_code_from_errno (errno)); } } /* then rename the file */ #if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) remove (fname); #endif if (rename (tmpfname, fname) ) { rc = gpg_error (gpg_err_code_from_errno (errno)); if (secret) { /* log_info ("WARNING: 2 files with confidential" */ /* " information exists.\n"); */ /* log_info ("%s is the unchanged one\n", fname ); */ /* log_info ("%s is the new one\n", tmpfname ); */ /* log_info ("Please fix this possible security flaw\n"); */ } return rc; } return 0; } /* Perform insert/delete/update operation. mode 1 = insert 2 = delete 3 = update */ static int blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob, int secret, off_t start_offset, unsigned int n_packets ) { FILE *fp, *newfp; int rc=0; char *bakfname = NULL; char *tmpfname = NULL; char buffer[4096]; int nread, nbytes; /* Open the source file. Because we do a rename, we have to check the permissions of the file */ if (access (fname, W_OK)) return gpg_error (gpg_err_code_from_errno (errno)); fp = fopen (fname, "rb"); if (mode == 1 && !fp && errno == ENOENT) { /* Insert mode but file does not exist: Create a new keybox file. */ newfp = fopen (fname, "wb"); if (!newfp ) return gpg_error (gpg_err_code_from_errno (errno)); rc = _keybox_write_header_blob (newfp); if (rc) return rc; rc = _keybox_write_blob (blob, newfp); if (rc) return rc; if ( fclose (newfp) ) return gpg_error (gpg_err_code_from_errno (errno)); /* if (chmod( fname, S_IRUSR | S_IWUSR )) */ /* { */ /* log_debug ("%s: chmod failed: %s\n", fname, strerror(errno) ); */ /* return KEYBOX_File_Error; */ /* } */ return 0; /* Ready. */ } if (!fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } /* Create the new file. */ rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp); if (rc) { fclose(fp); goto leave; } /* prepare for insert */ if (mode == 1) { /* Copy everything to the new file. */ while ( (nread = fread (buffer, 1, DIM(buffer), fp)) > 0 ) { if (fwrite (buffer, nread, 1, newfp) != 1) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } } if (ferror (fp)) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } } /* Prepare for delete or update. */ if ( mode == 2 || mode == 3 ) { off_t current = 0; /* Copy first part to the new file. */ while ( current < start_offset ) { nbytes = DIM(buffer); if (current + nbytes > start_offset) nbytes = start_offset - current; nread = fread (buffer, 1, nbytes, fp); if (!nread) break; current += nread; if (fwrite (buffer, nread, 1, newfp) != 1) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } } if (ferror (fp)) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } /* Skip this blob. */ rc = _keybox_read_blob (NULL, fp); if (rc) return rc; } /* Do an insert or update. */ if ( mode == 1 || mode == 3 ) { rc = _keybox_write_blob (blob, newfp); if (rc) return rc; } /* Copy the rest of the packet for an delete or update. */ if (mode == 2 || mode == 3) { while ( (nread = fread (buffer, 1, DIM(buffer), fp)) > 0 ) { if (fwrite (buffer, nread, 1, newfp) != 1) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } } if (ferror (fp)) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } } /* Close both files. */ if (fclose(fp)) { rc = gpg_error (gpg_err_code_from_errno (errno)); fclose (newfp); goto leave; } if (fclose(newfp)) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } rc = rename_tmp_file (bakfname, tmpfname, fname, secret); leave: xfree(bakfname); xfree(tmpfname); return rc; } #ifdef KEYBOX_WITH_X509 int keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert, unsigned char *sha1_digest) { int rc; const char *fname; KEYBOXBLOB blob; if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); if (!hd->kb) return gpg_error (GPG_ERR_INV_HANDLE); fname = hd->kb->fname; if (!fname) return gpg_error (GPG_ERR_INV_HANDLE); /* Close this one otherwise we will mess up the position for a next search. Fixme: it would be better to adjust the position after the write opertions. */ if (hd->fp) { fclose (hd->fp); hd->fp = NULL; } rc = _keybox_create_x509_blob (&blob, cert, sha1_digest, hd->ephemeral); if (!rc) { rc = blob_filecopy (1, fname, blob, hd->secret, 0, 0 ); _keybox_release_blob (blob); /* if (!rc && !hd->secret && kb_offtbl) */ /* { */ /* update_offset_hash_table_from_kb (kb_offtbl, kb, 0); */ /* } */ } return rc; } int keybox_update_cert (KEYBOX_HANDLE hd, ksba_cert_t cert, unsigned char *sha1_digest) { return -1; } #endif /*KEYBOX_WITH_X509*/ /* Note: We assume that the keybox has been locked before the current search was executed. This is needed so that we can depend on the offset information of the flags. */ int keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value) { off_t off; const char *fname; FILE *fp; gpg_err_code_t ec; size_t flag_pos, flag_size; const unsigned char *buffer; size_t length; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); if (!hd->kb) return gpg_error (GPG_ERR_INV_HANDLE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); fname = hd->kb->fname; if (!fname) return gpg_error (GPG_ERR_INV_HANDLE); off = _keybox_get_blob_fileoffset (hd->found.blob); if (off == (off_t)-1) return gpg_error (GPG_ERR_GENERAL); buffer = _keybox_get_blob_image (hd->found.blob, &length); ec = _keybox_get_flag_location (buffer, length, what, &flag_pos, &flag_size); if (ec) return gpg_error (ec); off += flag_pos; if (hd->fp) { fclose (hd->fp); hd->fp = NULL; } fp = fopen (hd->kb->fname, "r+b"); if (!fp) return gpg_error (gpg_err_code_from_errno (errno)); ec = 0; if (fseeko (fp, off, SEEK_SET)) ec = gpg_error (gpg_err_code_from_errno (errno)); else { unsigned char tmp[4]; tmp[0] = value >> 24; tmp[1] = value >> 16; tmp[2] = value >> 8; tmp[3] = value; switch (flag_size) { case 1: case 2: case 4: if (fwrite (tmp+4-flag_size, flag_size, 1, fp) != 1) ec = gpg_err_code_from_errno (errno); break; default: ec = GPG_ERR_BUG; break; } } if (fclose (fp)) { if (!ec) ec = gpg_err_code_from_errno (errno); } return gpg_error (ec); } int keybox_delete (KEYBOX_HANDLE hd) { off_t off; const char *fname; FILE *fp; int rc; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!hd->found.blob) return gpg_error (GPG_ERR_NOTHING_FOUND); if (!hd->kb) return gpg_error (GPG_ERR_INV_HANDLE); fname = hd->kb->fname; if (!fname) return gpg_error (GPG_ERR_INV_HANDLE); off = _keybox_get_blob_fileoffset (hd->found.blob); if (off == (off_t)-1) return gpg_error (GPG_ERR_GENERAL); off += 4; if (hd->fp) { fclose (hd->fp); hd->fp = NULL; } fp = fopen (hd->kb->fname, "r+b"); if (!fp) return gpg_error (gpg_err_code_from_errno (errno)); if (fseeko (fp, off, SEEK_SET)) rc = gpg_error (gpg_err_code_from_errno (errno)); else if (putc (0, fp) == EOF) rc = gpg_error (gpg_err_code_from_errno (errno)); else rc = 0; if (fclose (fp)) { if (!rc) rc = gpg_error (gpg_err_code_from_errno (errno)); } return rc; } /* Compress the keybox file. This should be run with the file locked. */ int keybox_compress (KEYBOX_HANDLE hd) { int read_rc, rc; const char *fname; FILE *fp, *newfp; char *bakfname = NULL; char *tmpfname = NULL; int first_blob; KEYBOXBLOB blob = NULL; u32 cut_time; int any_changes = 0; int skipped_deleted; if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); if (!hd->kb) return gpg_error (GPG_ERR_INV_HANDLE); if (hd->secret) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); fname = hd->kb->fname; if (!fname) return gpg_error (GPG_ERR_INV_HANDLE); if (hd->fp) { fclose (hd->fp); hd->fp = NULL; } /* Open the source file. Because we do a rename, we have to check the permissions of the file */ if (access (fname, W_OK)) return gpg_error (gpg_err_code_from_errno (errno)); fp = fopen (fname, "rb"); if (!fp && errno == ENOENT) return 0; /* Ready. File has been deleted right after the access above. */ if (!fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); return rc; } /* A quick test to see if we need to compress the file at all. We schedule a compress run after 3 hours. */ if ( !_keybox_read_blob (&blob, fp) ) { const unsigned char *buffer; size_t length; buffer = _keybox_get_blob_image (blob, &length); if (length > 4 && buffer[4] == BLOBTYPE_HEADER) { u32 last_maint = ((buffer[20] << 24) | (buffer[20+1] << 16) | (buffer[20+2] << 8) | (buffer[20+3])); if ( (last_maint + 3*3600) > time (NULL) ) { fclose (fp); _keybox_release_blob (blob); return 0; /* Compress run not yet needed. */ } } _keybox_release_blob (blob); rewind (fp); } /* Create the new file. */ rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp); if (rc) { fclose(fp); return rc;; } /* Processing loop. By reading using _keybox_read_blob we automagically skip and blobs flagged as deleted. Thus what we only have to do is to check all ephemeral flagged blocks whether their time has come and write out all other blobs. */ cut_time = time(NULL) - 86400; first_blob = 1; skipped_deleted = 0; for (rc=0; !(read_rc = _keybox_read_blob2 (&blob, fp, &skipped_deleted)); _keybox_release_blob (blob), blob = NULL ) { unsigned int blobflags; const unsigned char *buffer; size_t length, pos, size; u32 created_at; if (skipped_deleted) any_changes = 1; buffer = _keybox_get_blob_image (blob, &length); if (first_blob) { first_blob = 0; if (length > 4 && buffer[4] == BLOBTYPE_HEADER) { /* Write out the blob with an updated maintenance time stamp. */ _keybox_update_header_blob (blob); rc = _keybox_write_blob (blob, newfp); if (rc) break; continue; } /* The header blob is missing. Insert it. */ rc = _keybox_write_header_blob (newfp); if (rc) break; any_changes = 1; } else if (length > 4 && buffer[4] == BLOBTYPE_HEADER) { /* Oops: There is another header record - remove it. */ any_changes = 1; continue; } if (_keybox_get_flag_location (buffer, length, KEYBOX_FLAG_BLOB, &pos, &size) || size != 2) { rc = gpg_error (GPG_ERR_BUG); break; } blobflags = ((buffer[pos] << 8) | (buffer[pos+1])); if ((blobflags & 2)) { /* This is an ephemeral blob. */ if (_keybox_get_flag_location (buffer, length, KEYBOX_FLAG_CREATED_AT, &pos, &size) || size != 4) created_at = 0; /* oops. */ else created_at = ((buffer[pos] << 24) | (buffer[pos+1] << 16) | (buffer[pos+2] << 8) | (buffer[pos+3])); if (created_at && created_at < cut_time) { any_changes = 1; continue; /* Skip this blob. */ } } rc = _keybox_write_blob (blob, newfp); if (rc) break; } if (skipped_deleted) any_changes = 1; _keybox_release_blob (blob); blob = NULL; if (!rc && read_rc == -1) rc = 0; else if (!rc) rc = read_rc; /* Close both files. */ if (fclose(fp) && !rc) rc = gpg_error (gpg_err_code_from_errno (errno)); if (fclose(newfp) && !rc) rc = gpg_error (gpg_err_code_from_errno (errno)); /* Rename or remove the temporary file. */ if (rc || !any_changes) remove (tmpfname); else rc = rename_tmp_file (bakfname, tmpfname, fname, hd->secret); xfree(bakfname); xfree(tmpfname); return rc; } diff --git a/kbx/keybox-util.c b/kbx/keybox-util.c index ed5d93de0..6eb85ba3f 100644 --- a/kbx/keybox-util.c +++ b/kbx/keybox-util.c @@ -1,72 +1,73 @@ /* keybox-util.c - Utility functions for Keybox * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include "keybox-defs.h" static void *(*alloc_func)(size_t n) = malloc; static void *(*realloc_func)(void *p, size_t n) = realloc; static void (*free_func)(void*) = free; void keybox_set_malloc_hooks ( void *(*new_alloc_func)(size_t n), void *(*new_realloc_func)(void *p, size_t n), void (*new_free_func)(void*) ) { alloc_func = new_alloc_func; realloc_func = new_realloc_func; free_func = new_free_func; } void * _keybox_malloc (size_t n) { return alloc_func (n); } void * _keybox_realloc (void *a, size_t n) { return realloc_func (a, n); } void * _keybox_calloc (size_t n, size_t m) { void *p = _keybox_malloc (n*m); if (p) memset (p, 0, n* m); return p; } void _keybox_free (void *p) { if (p) free_func (p); } diff --git a/kbx/keybox.h b/kbx/keybox.h index af1fc4516..0f97fb7fc 100644 --- a/kbx/keybox.h +++ b/kbx/keybox.h @@ -1,115 +1,116 @@ /* keybox.h - Keybox operations * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef KEYBOX_H #define KEYBOX_H 1 #ifdef __cplusplus extern "C" { #if 0 } #endif #endif #include "keybox-search-desc.h" #define KEYBOX_WITH_OPENPGP 1 #define KEYBOX_WITH_X509 1 #ifdef KEYBOX_WITH_OPENPGP # undef KEYBOX_WITH_OPENPGP /*#include */ #endif #ifdef KEYBOX_WITH_X509 # include #endif typedef struct keybox_handle *KEYBOX_HANDLE; typedef enum { KEYBOX_FLAG_BLOB, /* The blob flags. */ KEYBOX_FLAG_VALIDITY, /* The validity of the entire key. */ KEYBOX_FLAG_OWNERTRUST, /* The assigned ownertrust. */ KEYBOX_FLAG_KEY, /* The key flags; requires a key index. */ KEYBOX_FLAG_UID, /* The user ID flags; requires an uid index. */ KEYBOX_FLAG_UID_VALIDITY,/* The validity of a specific uid, requires an uid index. */ KEYBOX_FLAG_CREATED_AT /* The date the block was created. */ } keybox_flag_t; /*-- keybox-init.c --*/ void *keybox_register_file (const char *fname, int secret); int keybox_is_writable (void *token); KEYBOX_HANDLE keybox_new (void *token, int secret); void keybox_release (KEYBOX_HANDLE hd); const char *keybox_get_resource_name (KEYBOX_HANDLE hd); int keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes); /*-- keybox-search.c --*/ #ifdef KEYBOX_WITH_X509 int keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *ret_cert); #endif /*KEYBOX_WITH_X509*/ int keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value); int keybox_search_reset (KEYBOX_HANDLE hd); int keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc); /*-- keybox-update.c --*/ #ifdef KEYBOX_WITH_X509 int keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert, unsigned char *sha1_digest); int keybox_update_cert (KEYBOX_HANDLE hd, ksba_cert_t cert, unsigned char *sha1_digest); #endif /*KEYBOX_WITH_X509*/ int keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value); int keybox_delete (KEYBOX_HANDLE hd); int keybox_compress (KEYBOX_HANDLE hd); /*-- --*/ #if 0 int keybox_lock (KEYBOX_HANDLE hd, int yes); int keybox_get_keyblock (KEYBOX_HANDLE hd, KBNODE *ret_kb); int keybox_locate_writable (KEYBOX_HANDLE hd); int keybox_search_reset (KEYBOX_HANDLE hd); int keybox_search (KEYBOX_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc); int keybox_rebuild_cache (void *); #endif /*-- keybox-util.c --*/ void keybox_set_malloc_hooks ( void *(*new_alloc_func)(size_t n), void *(*new_realloc_func)(void *p, size_t n), void (*new_free_func)(void*) ); #ifdef __cplusplus } #endif #endif /*KEYBOX_H*/ diff --git a/kbx/mkerrors b/kbx/mkerrors index 5adb7bfdf..d3d096c5d 100755 --- a/kbx/mkerrors +++ b/kbx/mkerrors @@ -1,71 +1,72 @@ #!/bin/sh # mkerrors - Extract error strings from assuan.h # and create C source for assuan_strerror # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. cat < #include "keybox-defs.h" /** * keybox_strerror: * @err: Error code * * This function returns a textual representaion of the given * errorcode. If this is an unknown value, a string with the value * is returned (Beware: it is hold in a static buffer). * * Return value: String with the error description. **/ const char * keybox_strerror (KeyboxError err) { const char *s; static char buf[25]; switch (err) { EOF awk ' /KEYBOX_No_Error/ { okay=1 } !okay {next} /}/ { exit 0 } /KEYBOX_[A-Za-z_]*/ { print_code($1) } function print_code( s ) { printf " case %s: s=\"", s ; gsub(/_/, " ", s ); printf "%s\"; break;\n", tolower(substr(s,8)); } ' cat < #endif struct app_local_s; /* Defined by all app-*.c. */ struct app_ctx_s { int initialized; /* The application has been initialied and the function pointers may be used. Note that for unsupported operations the particular function pointer is set to NULL */ int ref_count; /* Number of connections currently using this application context. fixme: We might want to merg this witghn INITIALIZED above. */ int slot; /* Used reader. */ /* If this is used by GnuPG 1.4 we need to know the assuan context in case we need to divert the operation to an already running agent. This if ASSUAN_CTX is not NULL we take this as indication that all operations are diverted to gpg-agent. */ #if GNUPG_MAJOR_VERSION == 1 assuan_context_t assuan_ctx; #endif /*GNUPG_MAJOR_VERSION == 1*/ unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ const char *apptype; unsigned int card_version; int did_chv1; int force_chv1; /* True if the card does not cache CHV1. */ int did_chv2; int did_chv3; struct app_local_s *app_local; /* Local to the application. */ struct { void (*deinit) (app_t app); gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); gpg_error_t (*readkey) (app_t app, const char *certid, unsigned char **pk, size_t *pklen); gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); gpg_error_t (*setattr) (app_t app, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); gpg_error_t (*sign) (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); gpg_error_t (*auth) (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); gpg_error_t (*decipher) (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); gpg_error_t (*writekey) (app_t app, ctrl_t ctrl, const char *certid, unsigned int flags, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg, const unsigned char *pk, size_t pklen); gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*check_pin) (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); } fnc; }; #if GNUPG_MAJOR_VERSION == 1 gpg_error_t app_select_openpgp (app_t app); gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); gpg_error_t app_openpgp_storekey (app_t app, int keyno, unsigned char *template, size_t template_len, time_t created_at, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); #else /*-- app-help.c --*/ gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); /*-- app.c --*/ void app_dump_state (void); void application_notify_card_removed (int slot); gpg_error_t check_application_conflict (ctrl_t ctrl, const char *name); gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app); void release_application (app_t app); gpg_error_t app_munge_serialno (app_t app); gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl); gpg_error_t app_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen); gpg_error_t app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen); gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); gpg_error_t app_setattr (app_t app, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); gpg_error_t app_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); gpg_error_t app_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); gpg_error_t app_decipher (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); gpg_error_t app_writekey (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer); gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_check_pin (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); /*-- app-openpgp.c --*/ gpg_error_t app_select_openpgp (app_t app); /*-- app-nks.c --*/ gpg_error_t app_select_nks (app_t app); /*-- app-dinsig.c --*/ gpg_error_t app_select_dinsig (app_t app); /*-- app-p15.c --*/ gpg_error_t app_select_p15 (app_t app); #endif #endif /*GNUPG_SCD_APP_COMMON_H*/ diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index 75cd12c59..752e8a346 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -1,458 +1,459 @@ /* app-dinsig.c - The DINSIG (DIN V 66291-1) card application. * Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* The German signature law and its bylaw (SigG and SigV) is currently used with an interface specification described in DIN V 66291-1. The AID to be used is: 'D27600006601'. The file IDs for certificates utilize the generic format: Cxyz C being the hex digit 'C' (12). x being the service indicator: '0' := SigG conform digital signature. '1' := entity authentication. '2' := key encipherment. '3' := data encipherment. '4' := key agreement. other values are reserved for future use. y being the security environment number using '0' for cards not supporting a SE number. z being the certificate type: '0' := C.CH (base certificate of card holder) or C.ICC. '1' .. '7' := C.CH (business or professional certificate of card holder. '8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA). 'E' := C.RCA (self certified certificate of the Root-CA). 'F' := reserved. The file IDs used by default are: '1F00' EF.SSD (security service descriptor). [o,o] '2F02' EF.GDO (global data objects) [m,m] 'A000' EF.PROT (signature log). Cyclic file with 20 records of 53 byte. Read and update after user authentication. [o,o] 'B000' EF.PK.RCA.DS (public keys of Root-CA). Size is 512b or size of keys. [m (unless a 'C00E' is present),m] 'B001' EF.PK.CA.DS (public keys of CAs). Size is 512b or size of keys. [o,o] 'C00n' EF.C.CH.DS (digital signature certificate of card holder) with n := 0 .. 7. Size is 2k or size of cert. Read and update allowed after user authentication. [m,m] 'C00m' EF.C.CA.DS (digital signature certificate of CA) with m := 8 .. E. Size is 1k or size of cert. Read always allowed, update after user authentication. [o,o] 'C100' EF.C.ICC.AUT (AUT certificate of ICC) [o,m] 'C108' EF.C.CA.AUT (AUT certificate of CA) [o,m] 'D000' EF.DM (display message) [-,m] The letters in brackets indicate optional or mandatory files: The first for card terminals under full control and the second for "business" card terminals. */ #include #include #include #include #include #include #include #include "scdaemon.h" #include "iso7816.h" #include "app-common.h" #include "tlv.h" static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) { gpg_error_t err; char ct_buf[100], id_buf[100]; char hexkeygrip[41]; size_t len, certoff; unsigned char *der; size_t derlen; ksba_cert_t cert; int fid; /* Return the certificate of the card holder. */ fid = 0xC000; len = app_help_read_length_of_cert (app->slot, fid, &certoff); if (!len) return 0; /* Card has not been personalized. */ sprintf (ct_buf, "%d", 101); sprintf (id_buf, "DINSIG.%04X", fid); send_status_info (ctrl, "CERTINFO", ct_buf, strlen (ct_buf), id_buf, strlen (id_buf), NULL, (size_t)0); /* Now we need to read the certificate, so that we can get the public key out of it. */ err = iso7816_read_binary (app->slot, certoff, len-certoff, &der, &derlen); if (err) { log_info ("error reading entire certificate from FID 0x%04X: %s\n", fid, gpg_strerror (err)); return 0; } err = ksba_cert_new (&cert); if (err) { xfree (der); return err; } err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); der = NULL; if (err) { log_error ("failed to parse the certificate at FID 0x%04X: %s\n", fid, gpg_strerror (err)); ksba_cert_release (cert); return err; } err = app_help_get_keygrip_string (cert, hexkeygrip); if (err) { log_error ("failed to calculate the keygrip for FID 0x%04X\n", fid); ksba_cert_release (cert); return gpg_error (GPG_ERR_CARD); } ksba_cert_release (cert); sprintf (id_buf, "DINSIG.%04X", fid); send_status_info (ctrl, "KEYPAIRINFO", hexkeygrip, 40, id_buf, strlen (id_buf), NULL, (size_t)0); return 0; } /* Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. FIXME: This needs some cleanups and caching with do_learn_status. */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { int fid; gpg_error_t err; unsigned char *buffer; const unsigned char *p; size_t buflen, n; int class, tag, constructed, ndef; size_t totobjlen, objlen, hdrlen; int rootca = 0; *cert = NULL; *certlen = 0; if (strncmp (certid, "DINSIG.", 7) ) return gpg_error (GPG_ERR_INV_ID); certid += 7; if (!hexdigitp (certid) || !hexdigitp (certid+1) || !hexdigitp (certid+2) || !hexdigitp (certid+3) || certid[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (certid); if (fid != 0xC000 ) return gpg_error (GPG_ERR_NOT_FOUND); /* Read the entire file. fixme: This could be optimized by first reading the header to figure out how long the certificate actually is. */ err = iso7816_select_file (app->slot, fid, 0, NULL, NULL); if (err) { log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); return err; } err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen); if (err) { log_error ("error reading certificate from FID 0x%04X: %s\n", fid, gpg_strerror (err)); return err; } if (!buflen || *buffer == 0xff) { log_info ("no certificate contained in FID 0x%04X\n", fid); err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } /* Now figure something out about the object. */ p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed ) ; else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) rootca = 1; else return gpg_error (GPG_ERR_INV_OBJ); totobjlen = objlen + hdrlen; assert (totobjlen <= buflen); err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (rootca) ; else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) { const unsigned char *save_p; /* The certificate seems to be contained in a userCertificate container. Skip this and assume the following sequence is the certificate. */ if (n < objlen) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } p += objlen; n -= objlen; save_p = p; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) return gpg_error (GPG_ERR_INV_OBJ); totobjlen = objlen + hdrlen; assert (save_p + totobjlen <= buffer + buflen); memmove (buffer, save_p, totobjlen); } *cert = buffer; buffer = NULL; *certlen = totobjlen; leave: xfree (buffer); return err; } /* Verify the PIN if required. */ static gpg_error_t verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { if (!app->did_chv1 || app->force_chv1 ) { const char *s; char *pinvalue; int rc; rc = pincb (pincb_arg, "PIN", &pinvalue); if (rc) { log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); return rc; } /* We require the PIN to be at least 6 and at max 8 bytes. According to the specs, this should all be ASCII. */ for (s=pinvalue; digitp (s); s++) ; if (*s) { log_error ("Non-numeric digits found in PIN\n"); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } if (strlen (pinvalue) < 6) { log_error ("PIN is too short; minimum length is 6\n"); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } else if (strlen (pinvalue) > 8) { log_error ("PIN is too large; maximum length is 8\n"); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (gpg_err_code (rc) == GPG_ERR_INV_VALUE) { /* We assume that ISO 9564-1 encoding is used and we failed because the first nibble we passed was 3 and not 2. DIN says something about looking up such an encoding in the SSD but I was not able to find any tag relevant to this. */ char paddedpin[8]; int i, ndigits; for (ndigits=0, s=pinvalue; *s; ndigits++, s++) ; i = 0; paddedpin[i++] = 0x20 | (ndigits & 0x0f); for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 ) paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); if (i < sizeof paddedpin && *s) paddedpin[i++] = (((*s - '0') << 4) | 0x0f); while (i < sizeof paddedpin) paddedpin[i++] = 0xff; rc = iso7816_verify (app->slot, 0x81, paddedpin, sizeof paddedpin); } if (rc) { log_error ("verify PIN failed\n"); xfree (pinvalue); return rc; } app->did_chv1 = 1; xfree (pinvalue); } return 0; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that in the 3rd argument. */ static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; int rc; int fid; unsigned char data[35]; /* Must be large enough for a SHA-1 digest + the largest OID _prefix above. */ if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (indatalen != 20 && indatalen != 16 && indatalen != 35) return gpg_error (GPG_ERR_INV_VALUE); /* Check that the provided ID is vaid. This is not really needed but we do it to to enforce correct usage by the caller. */ if (strncmp (keyidstr, "DINSIG.", 7) ) return gpg_error (GPG_ERR_INV_ID); keyidstr += 7; if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) || keyidstr[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (keyidstr); if (fid != 0xC000) return gpg_error (GPG_ERR_NOT_FOUND); /* Prepare the DER object from INDATA. */ if (indatalen == 35) { /* Alright, the caller was so kind to send us an already prepared DER object. Check that it is what we want and that it matches the hash algorithm. */ if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) ; else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15)) ; else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data, indata, indatalen); } else { if (hashalgo == GCRY_MD_SHA1) memcpy (data, sha1_prefix, 15); else if (hashalgo == GCRY_MD_RMD160) memcpy (data, rmd160_prefix, 15); else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data+15, indata, indatalen); } rc = verify_pin (app, pincb, pincb_arg); if (!rc) rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); return rc; } /* Select the DINSIG application on the card in SLOT. This function must be used before any other DINSIG application functions. */ gpg_error_t app_select_dinsig (APP app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; int slot = app->slot; int rc; rc = iso7816_select_application (slot, aid, sizeof aid, 0); if (!rc) { app->apptype = "DINSIG"; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = NULL; app->fnc.setattr = NULL; app->fnc.genkey = NULL; app->fnc.sign = do_sign; app->fnc.auth = NULL; app->fnc.decipher = NULL; app->fnc.change_pin = NULL; app->fnc.check_pin = NULL; app->force_chv1 = 1; } return rc; } diff --git a/scd/app-help.c b/scd/app-help.c index 27cbea5c7..861d8e3bc 100644 --- a/scd/app-help.c +++ b/scd/app-help.c @@ -1,162 +1,163 @@ /* app-help.c - Application helper functions * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "scdaemon.h" #include "app-common.h" #include "iso7816.h" #include "tlv.h" /* Return the KEYGRIP for the certificate CERT as an hex encoded string in the user provided buffer HEXKEYGRIP which must be of at least 41 bytes. */ gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip) { gpg_error_t err; gcry_sexp_t s_pkey; ksba_sexp_t p; size_t n; unsigned char array[20]; int i; p = ksba_cert_get_public_key (cert); if (!p) return gpg_error (GPG_ERR_BUG); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) return gpg_error (GPG_ERR_INV_SEXP); err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n); xfree (p); if (err) return err; /* Can't parse that S-expression. */ if (!gcry_pk_get_keygrip (s_pkey, array)) { gcry_sexp_release (s_pkey); return gpg_error (GPG_ERR_GENERAL); /* Failed to calculate the keygrip.*/ } gcry_sexp_release (s_pkey); for (i=0; i < 20; i++) sprintf (hexkeygrip+i*2, "%02X", array[i]); return 0; } /* Given the SLOT and the File ID FID, return the length of the certificate contained in that file. Returns 0 if the file does not exists or does not contain a certificate. If R_CERTOFF is not NULL, the length the header will be stored at this address; thus to parse the X.509 certificate a read should start at that offset. On success the file is still selected. */ size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff) { gpg_error_t err; unsigned char *buffer; const unsigned char *p; size_t buflen, n; int class, tag, constructed, ndef; size_t resultlen, objlen, hdrlen; err = iso7816_select_file (slot, fid, 0, NULL, NULL); if (err) { log_info ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); return 0; } err = iso7816_read_binary (slot, 0, 32, &buffer, &buflen); if (err) { log_info ("error reading certificate from FID 0x%04X: %s\n", fid, gpg_strerror (err)); return 0; } if (!buflen || *buffer == 0xff) { log_info ("no certificate contained in FID 0x%04X\n", fid); xfree (buffer); return 0; } p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) { log_info ("error parsing certificate in FID 0x%04X: %s\n", fid, gpg_strerror (err)); xfree (buffer); return 0; } /* All certificates should commence with a SEQUENCE except for the special ROOT CA which are enclosed in a SET. */ if ( !(class == CLASS_UNIVERSAL && constructed && (tag == TAG_SEQUENCE || tag == TAG_SET))) { log_info ("contents of FID 0x%04X does not look like a certificate\n", fid); return 0; } resultlen = objlen + hdrlen; if (r_certoff) { /* The callers want the offset to the actual certificate. */ *r_certoff = hdrlen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) return 0; if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) { /* The certificate seems to be contained in a userCertificate container. Assume the following sequence is the certificate. */ *r_certoff += hdrlen + objlen; if (*r_certoff > resultlen) { *r_certoff = 0; return 0; /* That should never happen. */ } } else *r_certoff = 0; } return resultlen; } diff --git a/scd/app-nks.c b/scd/app-nks.c index 73ec8ea01..1ca8d4187 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -1,518 +1,519 @@ /* app-nks.c - The Telesec NKS 2.0 card application. * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include "scdaemon.h" #include "i18n.h" #include "iso7816.h" #include "app-common.h" #include "tlv.h" static struct { int fid; /* File ID. */ int certtype; /* Type of certificate or 0 if it is not a certificate. */ int iskeypair; /* If true has the FID of the correspoding certificate. */ int issignkey; /* True if file is a key usable for signing. */ int isenckey; /* True if file is a key usable for decryption. */ } filelist[] = { { 0x4531, 0, 0xC000, 1, 0 }, { 0xC000, 101 }, { 0x4331, 100 }, { 0x4332, 100 }, { 0xB000, 110 }, { 0x45B1, 0, 0xC200, 0, 1 }, { 0xC200, 101 }, { 0x43B1, 100 }, { 0x43B2, 100 }, { 0, 0 } }; /* Read the file with FID, assume it contains a public key and return its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */ static gpg_error_t keygripstr_from_pk_file (int slot, int fid, char *r_gripstr) { gpg_error_t err; unsigned char grip[20]; unsigned char *buffer[2]; size_t buflen[2]; gcry_sexp_t sexp; int i; err = iso7816_select_file (slot, fid, 0, NULL, NULL); if (err) return err; err = iso7816_read_record (slot, 1, 1, 0, &buffer[0], &buflen[0]); if (err) return err; err = iso7816_read_record (slot, 2, 1, 0, &buffer[1], &buflen[1]); if (err) { xfree (buffer[0]); return err; } for (i=0; i < 2; i++) { /* Check that the value appears like an integer encoded as Simple-TLV. We don't check the tag because the tests cards I have use 1 for both, the modulus and the exponent - the example in the documentation gives 2 for the exponent. */ if (buflen[i] < 3) err = gpg_error (GPG_ERR_TOO_SHORT); else if (buffer[i][1] != buflen[i]-2 ) err = gpg_error (GPG_ERR_INV_OBJ); } if (!err) err = gcry_sexp_build (&sexp, NULL, "(public-key (rsa (n %b) (e %b)))", (int)buflen[0]-2, buffer[0]+2, (int)buflen[1]-2, buffer[1]+2); xfree (buffer[0]); xfree (buffer[1]); if (err) return err; if (!gcry_pk_get_keygrip (sexp, grip)) { err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by libgcrypt. */ } else { for (i=0; i < 20; i++) sprintf (r_gripstr+i*2, "%02X", grip[i]); } gcry_sexp_release (sexp); return err; } static gpg_error_t do_learn_status (APP app, CTRL ctrl) { gpg_error_t err; char ct_buf[100], id_buf[100]; int i; /* Output information about all useful objects. */ for (i=0; filelist[i].fid; i++) { if (filelist[i].certtype) { size_t len; len = app_help_read_length_of_cert (app->slot, filelist[i].fid, NULL); if (len) { /* FIXME: We should store the length in the application's context so that a following readcert does only need to read that many bytes. */ sprintf (ct_buf, "%d", filelist[i].certtype); sprintf (id_buf, "NKS-DF01.%04X", filelist[i].fid); send_status_info (ctrl, "CERTINFO", ct_buf, strlen (ct_buf), id_buf, strlen (id_buf), NULL, (size_t)0); } } else if (filelist[i].iskeypair) { char gripstr[40+1]; err = keygripstr_from_pk_file (app->slot, filelist[i].fid, gripstr); if (err) log_error ("can't get keygrip from FID 0x%04X: %s\n", filelist[i].fid, gpg_strerror (err)); else { sprintf (id_buf, "NKS-DF01.%04X", filelist[i].fid); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, id_buf, strlen (id_buf), NULL, (size_t)0); } } } return 0; } /* Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { int i, fid; gpg_error_t err; unsigned char *buffer; const unsigned char *p; size_t buflen, n; int class, tag, constructed, ndef; size_t totobjlen, objlen, hdrlen; int rootca = 0; *cert = NULL; *certlen = 0; if (strncmp (certid, "NKS-DF01.", 9) ) return gpg_error (GPG_ERR_INV_ID); certid += 9; if (!hexdigitp (certid) || !hexdigitp (certid+1) || !hexdigitp (certid+2) || !hexdigitp (certid+3) || certid[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (certid); for (i=0; filelist[i].fid; i++) if ((filelist[i].certtype || filelist[i].iskeypair) && filelist[i].fid == fid) break; if (!filelist[i].fid) return gpg_error (GPG_ERR_NOT_FOUND); /* If the requested objects is a plain public key, redirect it to the corresponding certificate. The whole system is a bit messy becuase we sometime use the key directly or let the caller retrieve the key from the certificate. The valid point behind that is to support not-yet stored certificates. */ if (filelist[i].iskeypair) fid = filelist[i].iskeypair; /* Read the entire file. fixme: This could be optimized by first reading the header to figure out how long the certificate actually is. */ err = iso7816_select_file (app->slot, fid, 0, NULL, NULL); if (err) { log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); return err; } err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen); if (err) { log_error ("error reading certificate from FID 0x%04X: %s\n", fid, gpg_strerror (err)); return err; } if (!buflen || *buffer == 0xff) { log_info ("no certificate contained in FID 0x%04X\n", fid); err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } /* Now figure something out about the object. */ p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed ) ; else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) rootca = 1; else return gpg_error (GPG_ERR_INV_OBJ); totobjlen = objlen + hdrlen; assert (totobjlen <= buflen); err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (rootca) ; else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) { const unsigned char *save_p; /* The certificate seems to be contained in a userCertificate container. Skip this and assume the following sequence is the certificate. */ if (n < objlen) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } p += objlen; n -= objlen; save_p = p; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) return gpg_error (GPG_ERR_INV_OBJ); totobjlen = objlen + hdrlen; assert (save_p + totobjlen <= buffer + buflen); memmove (buffer, save_p, totobjlen); } *cert = buffer; buffer = NULL; *certlen = totobjlen; leave: xfree (buffer); return err; } /* Verify the PIN if required. */ static gpg_error_t verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { /* Note that force_chv1 is never set but we do it here anyway so that other applications may reuse this function. For example it makes sense to set force_chv1 for German signature law cards. NKS is very similar to the DINSIG draft standard. */ if (!app->did_chv1 || app->force_chv1 ) { char *pinvalue; int rc; rc = pincb (pincb_arg, "PIN", &pinvalue); if (rc) { log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); return rc; } /* The following limits are due to TCOS but also defined in the NKS specs. */ if (strlen (pinvalue) < 6) { log_error ("PIN is too short; minimum length is 6\n"); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } else if (strlen (pinvalue) > 16) { log_error ("PIN is too large; maximum length is 16\n"); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } /* Also it is possible to use a local PIN, we use the gloabl PIN for this application. */ rc = iso7816_verify (app->slot, 0, pinvalue, strlen (pinvalue)); if (rc) { if ( gpg_error (rc) == GPG_ERR_USE_CONDITIONS ) log_error (_("the NullPIN has not yet been changed\n")); else log_error ("verify PIN failed\n"); xfree (pinvalue); return rc; } app->did_chv1 = 1; xfree (pinvalue); } return 0; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that in the 3rd argument. */ static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; int rc, i; int fid; unsigned char data[35]; /* Must be large enough for a SHA-1 digest + the largest OID _prefix above. */ if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (indatalen != 20 && indatalen != 16 && indatalen != 35) return gpg_error (GPG_ERR_INV_VALUE); /* Check that the provided ID is vaid. This is not really needed but we do it to to enforce correct usage by the caller. */ if (strncmp (keyidstr, "NKS-DF01.", 9) ) return gpg_error (GPG_ERR_INV_ID); keyidstr += 9; if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) || keyidstr[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (keyidstr); for (i=0; filelist[i].fid; i++) if (filelist[i].iskeypair && filelist[i].fid == fid) break; if (!filelist[i].fid) return gpg_error (GPG_ERR_NOT_FOUND); if (!filelist[i].issignkey) return gpg_error (GPG_ERR_INV_ID); /* Prepare the DER object from INDATA. */ if (indatalen == 35) { /* Alright, the caller was so kind to send us an already prepared DER object. Check that it is waht we want and that it matches the hash algorithm. */ if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) ; else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15)) ; else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data, indata, indatalen); } else { if (hashalgo == GCRY_MD_SHA1) memcpy (data, sha1_prefix, 15); else if (hashalgo == GCRY_MD_RMD160) memcpy (data, rmd160_prefix, 15); else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data+15, indata, indatalen); } rc = verify_pin (app, pincb, pincb_arg); if (!rc) rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); return rc; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ static gpg_error_t do_decipher (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static const unsigned char mse_parm[] = { 0x80, 1, 0x10, /* Select algorithm RSA. */ 0x84, 1, 0x81 /* Select local secret key 1 for decryption. */ }; int rc, i; int fid; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); /* Check that the provided ID is vaid. This is not really needed but we do it to to enforce correct usage by the caller. */ if (strncmp (keyidstr, "NKS-DF01.", 9) ) return gpg_error (GPG_ERR_INV_ID); keyidstr += 9; if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) || keyidstr[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (keyidstr); for (i=0; filelist[i].fid; i++) if (filelist[i].iskeypair && filelist[i].fid == fid) break; if (!filelist[i].fid) return gpg_error (GPG_ERR_NOT_FOUND); if (!filelist[i].isenckey) return gpg_error (GPG_ERR_INV_ID); /* Do the TCOS specific MSE. */ rc = iso7816_manage_security_env (app->slot, 0xC1, 0xB8, mse_parm, sizeof mse_parm); if (!rc) rc = verify_pin (app, pincb, pincb_arg); if (!rc) rc = iso7816_decipher (app->slot, indata, indatalen, 0x81, outdata, outdatalen); return rc; } /* Select the NKS 2.0 application on the card in SLOT. */ gpg_error_t app_select_nks (APP app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 }; int slot = app->slot; int rc; rc = iso7816_select_application (slot, aid, sizeof aid, 0); if (!rc) { app->apptype = "NKS"; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = NULL; app->fnc.setattr = NULL; app->fnc.genkey = NULL; app->fnc.sign = do_sign; app->fnc.auth = NULL; app->fnc.decipher = do_decipher; app->fnc.change_pin = NULL; app->fnc.check_pin = NULL; } return rc; } diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 5e9281a38..842881f3a 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1,2567 +1,2568 @@ /* app-openpgp.c - The OpenPGP card application. * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * $Id$ */ #include #include #include #include #include #include #include #if GNUPG_MAJOR_VERSION == 1 /* This is used with GnuPG version < 1.9. The code has been source copied from the current GnuPG >= 1.9 and is maintained over there. */ #include "options.h" #include "errors.h" #include "memory.h" #include "util.h" #include "cardglue.h" #else /* GNUPG_MAJOR_VERSION != 1 */ #include "scdaemon.h" #endif /* GNUPG_MAJOR_VERSION != 1 */ #include "i18n.h" #include "iso7816.h" #include "app-common.h" #include "tlv.h" static struct { int tag; int constructed; int get_from; /* Constructed DO with this DO or 0 for direct access. */ int binary; int dont_cache; int flush_on_error; int get_immediate_in_v11; /* Enable a hack to bypass the cache of this data object if it is used in 1.1 and later versions of the card. This does not work with composite DO and is currently only useful for the CHV status bytes. */ char *desc; } data_objects[] = { { 0x005E, 0, 0, 1, 0, 0, 0, "Login Data" }, { 0x5F50, 0, 0, 0, 0, 0, 0, "URL" }, { 0x0065, 1, 0, 1, 0, 0, 0, "Cardholder Related Data"}, { 0x005B, 0, 0x65, 0, 0, 0, 0, "Name" }, { 0x5F2D, 0, 0x65, 0, 0, 0, 0, "Language preferences" }, { 0x5F35, 0, 0x65, 0, 0, 0, 0, "Sex" }, { 0x006E, 1, 0, 1, 0, 0, 0, "Application Related Data" }, { 0x004F, 0, 0x6E, 1, 0, 0, 0, "AID" }, { 0x0073, 1, 0, 1, 0, 0, 0, "Discretionary Data Objects" }, { 0x0047, 0, 0x6E, 1, 1, 0, 0, "Card Capabilities" }, { 0x00C0, 0, 0x6E, 1, 1, 0, 0, "Extended Card Capabilities" }, { 0x00C1, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Signature" }, { 0x00C2, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Decryption" }, { 0x00C3, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Authentication" }, { 0x00C4, 0, 0x6E, 1, 0, 1, 1, "CHV Status Bytes" }, { 0x00C5, 0, 0x6E, 1, 0, 0, 0, "Fingerprints" }, { 0x00C6, 0, 0x6E, 1, 0, 0, 0, "CA Fingerprints" }, { 0x00CD, 0, 0x6E, 1, 0, 0, 0, "Generation time" }, { 0x007A, 1, 0, 1, 0, 0, 0, "Security Support Template" }, { 0x0093, 0, 0x7A, 1, 1, 0, 0, "Digital Signature Counter" }, { 0x0101, 0, 0, 0, 0, 0, 0, "Private DO 1"}, { 0x0102, 0, 0, 0, 0, 0, 0, "Private DO 2"}, { 0x0103, 0, 0, 0, 0, 0, 0, "Private DO 3"}, { 0x0104, 0, 0, 0, 0, 0, 0, "Private DO 4"}, { 0 } }; /* One cache item for DOs. */ struct cache_s { struct cache_s *next; int tag; size_t length; unsigned char data[1]; }; /* Object with application (i.e. OpenPGP card) specific data. */ struct app_local_s { /* A linked list with cached DOs. */ struct cache_s *cache; /* Keep track of the public keys. */ struct { int read_done; /* True if we have at least tried to read them. */ unsigned char *key; /* This is a malloced buffer with a canonical encoded S-expression encoding a public key. Might be NULL if key is not available. */ size_t keylen; /* The length of the above S-expression. This is usullay only required for cross checks because the length of an S-expression is implicitly available. */ } pk[3]; /* Keep track of card capabilities. */ struct { unsigned int get_challenge:1; unsigned int key_import:1; unsigned int change_force_chv:1; unsigned int private_dos:1; } extcap; /* Flags used to control the application. */ struct { unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */ unsigned int def_chv2:1; /* Use 123456 for CHV2. */ } flags; }; /***** Local prototypes *****/ static unsigned long convert_sig_counter_value (const unsigned char *value, size_t valuelen); static unsigned long get_sig_counter (app_t app); /* Deconstructor. */ static void do_deinit (app_t app) { if (app && app->app_local) { struct cache_s *c, *c2; int i; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } for (i=0; i < DIM (app->app_local->pk); i++) { xfree (app->app_local->pk[i].key); app->app_local->pk[i].read_done = 0; } xfree (app->app_local); app->app_local = NULL; } } /* Wrapper around iso7816_get_data which first tries to get the data from the cache. With GET_IMMEDIATE passed as true, the cache is bypassed. */ static gpg_error_t get_cached_data (app_t app, int tag, unsigned char **result, size_t *resultlen, int get_immediate) { gpg_error_t err; int i; unsigned char *p; size_t len; struct cache_s *c; *result = NULL; *resultlen = 0; if (!get_immediate) { for (c=app->app_local->cache; c; c = c->next) if (c->tag == tag) { if(c->length) { p = xtrymalloc (c->length); if (!p) return gpg_error (gpg_err_code_from_errno (errno)); memcpy (p, c->data, c->length); *result = p; } *resultlen = c->length; return 0; } } err = iso7816_get_data (app->slot, tag, &p, &len); if (err) return err; *result = p; *resultlen = len; /* Check whether we should cache this object. */ if (get_immediate) return 0; for (i=0; data_objects[i].tag; i++) if (data_objects[i].tag == tag) { if (data_objects[i].dont_cache) return 0; break; } /* Okay, cache it. */ for (c=app->app_local->cache; c; c = c->next) assert (c->tag != tag); c = xtrymalloc (sizeof *c + len); if (c) { memcpy (c->data, p, len); c->length = len; c->tag = tag; c->next = app->app_local->cache; app->app_local->cache = c; } return 0; } /* Remove DO at TAG from the cache. */ static void flush_cache_item (app_t app, int tag) { struct cache_s *c, *cprev; int i; if (!app->app_local) return; for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next) if (c->tag == tag) { if (cprev) cprev->next = c->next; else app->app_local->cache = c->next; xfree (c); for (c=app->app_local->cache; c ; c = c->next) { assert (c->tag != tag); /* Oops: duplicated entry. */ } return; } /* Try again if we have an outer tag. */ for (i=0; data_objects[i].tag; i++) if (data_objects[i].tag == tag && data_objects[i].get_from && data_objects[i].get_from != tag) flush_cache_item (app, data_objects[i].get_from); } /* Flush all entries from the cache which might be out of sync after an error. */ static void flush_cache_after_error (app_t app) { int i; for (i=0; data_objects[i].tag; i++) if (data_objects[i].flush_on_error) flush_cache_item (app, data_objects[i].tag); } /* Flush the entire cache. */ static void flush_cache (app_t app) { if (app && app->app_local) { struct cache_s *c, *c2; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } app->app_local->cache = NULL; } } /* Get the DO identified by TAG from the card in SLOT and return a buffer with its content in RESULT and NBYTES. The return value is NULL if not found or a pointer which must be used to release the buffer holding value. */ static void * get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, int *r_rc) { int rc, i; unsigned char *buffer; size_t buflen; unsigned char *value; size_t valuelen; int dummyrc; if (!r_rc) r_rc = &dummyrc; *result = NULL; *nbytes = 0; *r_rc = 0; for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) ; if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) { rc = iso7816_get_data (app->slot, tag, &buffer, &buflen); if (rc) { *r_rc = rc; return NULL; } *result = buffer; *nbytes = buflen; return buffer; } value = NULL; rc = -1; if (data_objects[i].tag && data_objects[i].get_from) { rc = get_cached_data (app, data_objects[i].get_from, &buffer, &buflen, (data_objects[i].dont_cache || data_objects[i].get_immediate_in_v11)); if (!rc) { const unsigned char *s; s = find_tlv_unchecked (buffer, buflen, tag, &valuelen); if (!s) value = NULL; /* not found */ else if (valuelen > buflen - (s - buffer)) { log_error ("warning: constructed DO too short\n"); value = NULL; xfree (buffer); buffer = NULL; } else value = buffer + (s - buffer); } } if (!value) /* Not in a constructed DO, try simple. */ { rc = get_cached_data (app, tag, &buffer, &buflen, (data_objects[i].dont_cache || data_objects[i].get_immediate_in_v11)); if (!rc) { value = buffer; valuelen = buflen; } } if (!rc) { *nbytes = valuelen; *result = value; return buffer; } *r_rc = rc; return NULL; } static void dump_all_do (int slot) { int rc, i, j; unsigned char *buffer; size_t buflen; for (i=0; data_objects[i].tag; i++) { if (data_objects[i].get_from) continue; rc = iso7816_get_data (slot, data_objects[i].tag, &buffer, &buflen); if (gpg_err_code (rc) == GPG_ERR_NO_OBJ) ; else if (rc) log_info ("DO `%s' not available: %s\n", data_objects[i].desc, gpg_strerror (rc)); else { if (data_objects[i].binary) { log_info ("DO `%s': ", data_objects[i].desc); log_printhex ("", buffer, buflen); } else log_info ("DO `%s': `%.*s'\n", data_objects[i].desc, (int)buflen, buffer); /* FIXME: sanitize */ if (data_objects[i].constructed) { for (j=0; data_objects[j].tag; j++) { const unsigned char *value; size_t valuelen; if (j==i || data_objects[i].tag != data_objects[j].get_from) continue; value = find_tlv_unchecked (buffer, buflen, data_objects[j].tag, &valuelen); if (!value) ; /* not found */ else if (valuelen > buflen - (value - buffer)) log_error ("warning: constructed DO too short\n"); else { if (data_objects[j].binary) { log_info ("DO `%s': ", data_objects[j].desc); log_printhex ("", value, valuelen); } else log_info ("DO `%s': `%.*s'\n", data_objects[j].desc, (int)valuelen, value); /* FIXME: sanitize */ } } } } xfree (buffer); buffer = NULL; } } /* Count the number of bits, assuming the A represents an unsigned big integer of length LEN bytes. */ static unsigned int count_bits (const unsigned char *a, size_t len) { unsigned int n = len * 8; int i; for (; len && !*a; len--, a++, n -=8) ; if (len) { for (i=7; i && !(*a & (1< Were FLAGS is a plain hexadecimal number representing flag values. The lsb is here the rightmost bit. Defined flags bits are: Bit 0 = CHV1 and CHV2 are not syncronized Bit 1 = CHV2 has been been set to the default PIN of "123456" (this implies that bit 0 is also set). */ static void parse_login_data (app_t app) { unsigned char *buffer, *p; size_t buflen, len; void *relptr; /* Set defaults. */ app->app_local->flags.no_sync = 0; app->app_local->flags.def_chv2 = 0; /* Read the DO. */ relptr = get_one_do (app, 0x005E, &buffer, &buflen, NULL); if (!relptr) return; /* Ooops. */ for (; buflen; buflen--, buffer++) if (*buffer == '\n') break; if (buflen < 2 || buffer[1] != '\x14') return; /* No control sequences. */ buflen--; buffer++; do { buflen--; buffer++; if (buflen > 1 && *buffer == 'F' && buffer[1] == '=') { /* Flags control sequence found. */ int lastdig = 0; /* For now we are only interested in the last digit, so skip any leading digits but bail out on invalid characters. */ for (p=buffer+2, len = buflen-2; len && hexdigitp (p); p++, len--) lastdig = xtoi_1 (p); if (len && !(*p == '\n' || *p == '\x18')) goto next; /* Invalid characters in field. */ app->app_local->flags.no_sync = !!(lastdig & 1); app->app_local->flags.def_chv2 = (lastdig & 3) == 3; } next: for (; buflen && *buffer != '\x18'; buflen--, buffer++) if (*buffer == '\n') buflen = 1; } while (buflen); xfree (relptr); } /* Note, that FPR must be at least 20 bytes. */ static gpg_error_t store_fpr (int slot, int keynumber, u32 timestamp, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, unsigned char *fpr, unsigned int card_version) { unsigned int n, nbits; unsigned char *buffer, *p; int rc; for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */ ; for (; elen && !*e; elen--, e++) /* strip leading zeroes */ ; n = 6 + 2 + mlen + 2 + elen; p = buffer = xtrymalloc (3 + n); if (!buffer) return gpg_error_from_errno (errno); *p++ = 0x99; /* ctb */ *p++ = n >> 8; /* 2 byte length header */ *p++ = n; *p++ = 4; /* key packet version */ *p++ = timestamp >> 24; *p++ = timestamp >> 16; *p++ = timestamp >> 8; *p++ = timestamp; *p++ = 1; /* RSA */ nbits = count_bits (m, mlen); *p++ = nbits >> 8; *p++ = nbits; memcpy (p, m, mlen); p += mlen; nbits = count_bits (e, elen); *p++ = nbits >> 8; *p++ = nbits; memcpy (p, e, elen); p += elen; gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3); xfree (buffer); rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6) + keynumber, fpr, 20); if (rc) log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); if (!rc && card_version > 0x0100) { unsigned char buf[4]; buf[0] = timestamp >> 24; buf[1] = timestamp >> 16; buf[2] = timestamp >> 8; buf[3] = timestamp; rc = iso7816_put_data (slot, 0xCE + keynumber, buf, 4); if (rc) log_error (_("failed to store the creation date: %s\n"), gpg_strerror (rc)); } return rc; } static void send_fpr_if_not_null (ctrl_t ctrl, const char *keyword, int number, const unsigned char *fpr) { int i; char buf[41]; char numbuf[25]; for (i=0; i < 20 && !fpr[i]; i++) ; if (i==20) return; /* All zero. */ for (i=0; i< 20; i++) sprintf (buf+2*i, "%02X", fpr[i]); if (number == -1) *numbuf = 0; /* Don't print the key number */ else sprintf (numbuf, "%d", number); send_status_info (ctrl, keyword, numbuf, (size_t)strlen(numbuf), buf, (size_t)strlen (buf), NULL, 0); } static void send_fprtime_if_not_null (ctrl_t ctrl, const char *keyword, int number, const unsigned char *stamp) { char numbuf1[50], numbuf2[50]; unsigned long value; value = (stamp[0] << 24) | (stamp[1]<<16) | (stamp[2]<<8) | stamp[3]; if (!value) return; sprintf (numbuf1, "%d", number); sprintf (numbuf2, "%lu", value); send_status_info (ctrl, keyword, numbuf1, (size_t)strlen(numbuf1), numbuf2, (size_t)strlen(numbuf2), NULL, 0); } static void send_key_data (ctrl_t ctrl, const char *name, const unsigned char *a, size_t alen) { char *p, *buf = xmalloc (alen*2+1); for (p=buf; alen; a++, alen--, p += 2) sprintf (p, "%02X", *a); send_status_info (ctrl, "KEY-DATA", name, (size_t)strlen(name), buf, (size_t)strlen (buf), NULL, 0); xfree (buf); } /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { static struct { const char *name; int tag; int special; } table[] = { { "DISP-NAME", 0x005B }, { "LOGIN-DATA", 0x005E }, { "DISP-LANG", 0x5F2D }, { "DISP-SEX", 0x5F35 }, { "PUBKEY-URL", 0x5F50 }, { "KEY-FPR", 0x00C5, 3 }, { "KEY-TIME", 0x00CD, 4 }, { "CA-FPR", 0x00C6, 3 }, { "CHV-STATUS", 0x00C4, 1 }, { "SIG-COUNTER", 0x0093, 2 }, { "SERIALNO", 0x004F, -1 }, { "AID", 0x004F }, { "EXTCAP", 0x0000, -2 }, { "PRIVATE-DO-1", 0x0101 }, { "PRIVATE-DO-2", 0x0102 }, { "PRIVATE-DO-3", 0x0103 }, { "PRIVATE-DO-4", 0x0104 }, { "$AUTHKEYID", 0x0000, -3 }, { "$DISPSERIALNO",0x0000, -4 }, { NULL, 0 } }; int idx, i, rc; void *relptr; unsigned char *value; size_t valuelen; for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) ; if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); if (table[idx].special == -1) { /* The serial number is very special. We could have used the AID DO to retrieve it, but we have it already in the app context and the stamp argument is required anyway which we can't by other means. The AID DO is available anyway but not hex formatted. */ char *serial; time_t stamp; char tmp[50]; if (!app_get_serial_and_stamp (app, &serial, &stamp)) { sprintf (tmp, "%lu", (unsigned long)stamp); send_status_info (ctrl, "SERIALNO", serial, strlen (serial), tmp, strlen (tmp), NULL, 0); xfree (serial); } return 0; } if (table[idx].special == -2) { char tmp[50]; sprintf (tmp, "gc=%d ki=%d fc=%d pd=%d", app->app_local->extcap.get_challenge, app->app_local->extcap.key_import, app->app_local->extcap.change_force_chv, app->app_local->extcap.private_dos); send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); return 0; } if (table[idx].special == -3) { char const tmp[] = "OPENPGP.3"; send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); return 0; } if (table[idx].special == -4) { char *serial; time_t stamp; if (!app_get_serial_and_stamp (app, &serial, &stamp)) { if (strlen (serial) > 16+12) { send_status_info (ctrl, table[idx].name, serial+16, 12, NULL, 0); xfree (serial); return 0; } xfree (serial); } return gpg_error (GPG_ERR_INV_NAME); } relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc); if (relptr) { if (table[idx].special == 1) { char numbuf[7*23]; for (i=0,*numbuf=0; i < valuelen && i < 7; i++) sprintf (numbuf+strlen (numbuf), " %d", value[i]); send_status_info (ctrl, table[idx].name, numbuf, strlen (numbuf), NULL, 0); } else if (table[idx].special == 2) { char numbuf[50]; sprintf (numbuf, "%lu", convert_sig_counter_value (value, valuelen)); send_status_info (ctrl, table[idx].name, numbuf, strlen (numbuf), NULL, 0); } else if (table[idx].special == 3) { if (valuelen >= 60) for (i=0; i < 3; i++) send_fpr_if_not_null (ctrl, table[idx].name, i+1, value+i*20); } else if (table[idx].special == 4) { if (valuelen >= 12) for (i=0; i < 3; i++) send_fprtime_if_not_null (ctrl, table[idx].name, i+1, value+i*4); } else send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); xfree (relptr); } return rc; } /* Retrieve the fingerprint from the card inserted in SLOT and write the according hex representation to FPR. Caller must have provide a buffer at FPR of least 41 bytes. Returns 0 on success or an error code. */ #if GNUPG_MAJOR_VERSION > 1 static gpg_error_t retrieve_fpr_from_card (app_t app, int keyno, char *fpr) { gpg_error_t err = 0; void *relptr; unsigned char *value; size_t valuelen; int i; assert (keyno >=0 && keyno <= 2); relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL); if (relptr && valuelen >= 60) { for (i = 0; i < 20; i++) sprintf (fpr + (i * 2), "%02X", value[(keyno*20)+i]); } else err = gpg_error (GPG_ERR_NOT_FOUND); xfree (relptr); return err; } #endif /*GNUPG_MAJOR_VERSION > 1*/ /* Retrieve the public key material for the RSA key, whose fingerprint is FPR, from gpg output, which can be read through the stream FP. The RSA modulus will be stored at the address of M and MLEN, the public exponent at E and ELEN. Returns zero on success, an error code on failure. Caller must release the allocated buffers at M and E if the function returns success. */ #if GNUPG_MAJOR_VERSION > 1 static gpg_error_t retrieve_key_material (FILE *fp, const char *hexkeyid, const unsigned char **m, size_t *mlen, const unsigned char **e, size_t *elen) { gcry_error_t err = 0; char *line = NULL; /* read_line() buffer. */ size_t line_size = 0; /* Helper for for read_line. */ int found_key = 0; /* Helper to find a matching key. */ unsigned char *m_new = NULL; unsigned char *e_new = NULL; size_t m_new_n = 0; size_t e_new_n = 0; /* Loop over all records until we have found the subkey corresponsing to the fingerprint. Inm general the first record should be the pub record, but we don't rely on that. Given that we only need to look at one key, it is sufficient to compare the keyid so that we don't need to look at "fpr" records. */ for (;;) { char *p; char *fields[6]; int nfields; size_t max_length; gcry_mpi_t mpi; int i; max_length = 4096; i = read_line (fp, &line, &line_size, &max_length); if (!i) break; /* EOF. */ if (i < 0) { err = gpg_error_from_errno (errno); goto leave; /* Error. */ } if (!max_length) { err = gpg_error (GPG_ERR_TRUNCATED); goto leave; /* Line truncated - we better stop processing. */ } /* Parse the line into fields. */ for (nfields=0, p=line; p && nfields < DIM (fields); nfields++) { fields[nfields] = p; p = strchr (p, ':'); if (p) *(p++) = 0; } if (!nfields) continue; /* No fields at all - skip line. */ if (!found_key) { if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) && nfields > 4 && !strcmp (fields[4], hexkeyid)) found_key = 1; continue; } if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) break; /* Next key - stop. */ if ( strcmp (fields[0], "pkd") ) continue; /* Not a key data record. */ i = 0; /* Avoid erroneous compiler warning. */ if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1 || (!i && m_new) || (i && e_new)) { err = gpg_error (GPG_ERR_GENERAL); goto leave; /* Error: Invalid key data record or not an RSA key. */ } err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL); if (err) mpi = NULL; else if (!i) err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi); else err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi); gcry_mpi_release (mpi); if (err) goto leave; } if (m_new && e_new) { *m = m_new; *mlen = m_new_n; m_new = NULL; *e = e_new; *elen = e_new_n; e_new = NULL; } else err = gpg_error (GPG_ERR_GENERAL); leave: xfree (m_new); xfree (e_new); xfree (line); return err; } #endif /*GNUPG_MAJOR_VERSION > 1*/ /* Get the public key for KEYNO and store it as an S-expresion with the APP handle. On error that field gets cleared. If we already know about the public key we will just return. Note that this does not mean a key is available; this is soley indicated by the presence of the app->app_local->pk[KEYNO-1].key field. Note that GnuPG 1.x does not need this and it would be too time consuming to send it just for the fun of it. However, given that we use the same code in gpg 1.4, we can't use the gcry S-expresion here but need to open encode it. */ #if GNUPG_MAJOR_VERSION > 1 static gpg_error_t get_public_key (app_t app, int keyno) { gpg_error_t err = 0; unsigned char *buffer; const unsigned char *keydata, *m, *e; size_t buflen, keydatalen, mlen, elen; unsigned char *mbuf = NULL; unsigned char *ebuf = NULL; char *keybuf = NULL; char *keybuf_p; if (keyno < 1 || keyno > 3) return gpg_error (GPG_ERR_INV_ID); keyno--; /* Already cached? */ if (app->app_local->pk[keyno].read_done) return 0; xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; m = e = NULL; /* (avoid cc warning) */ if (app->card_version > 0x0100) { /* We may simply read the public key out of these cards. */ err = iso7816_read_public_key (app->slot, (const unsigned char*)(keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, &buffer, &buflen); if (err) { log_error (_("reading public key failed: %s\n"), gpg_strerror (err)); goto leave; } keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); if (!keydata) { err = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the public key data\n")); goto leave; } m = find_tlv (keydata, keydatalen, 0x0081, &mlen); if (!m) { err = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the RSA modulus\n")); goto leave; } e = find_tlv (keydata, keydatalen, 0x0082, &elen); if (!e) { err = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the RSA public exponent\n")); goto leave; } /* Prepend numbers with a 0 if needed. */ if (mlen && (*m & 0x80)) { mbuf = xtrymalloc ( mlen + 1); if (!mbuf) { err = gpg_error_from_errno (errno); goto leave; } *mbuf = 0; memcpy (mbuf+1, m, mlen); mlen++; m = mbuf; } if (elen && (*e & 0x80)) { ebuf = xtrymalloc ( elen + 1); if (!ebuf) { err = gpg_error_from_errno (errno); goto leave; } *ebuf = 0; memcpy (ebuf+1, e, elen); elen++; e = ebuf; } } else { /* Due to a design problem in v1.0 cards we can't get the public key out of these cards without doing a verify on CHV3. Clearly that is not an option and thus we try to locate the key using an external helper. The helper we use here is gpg itself, which should know about the key in any case. */ char fpr[41]; char *hexkeyid; char *command = NULL; FILE *fp; int ret; buffer = NULL; /* We don't need buffer. */ err = retrieve_fpr_from_card (app, keyno, fpr); if (err) { log_error ("error while retrieving fpr from card: %s\n", gpg_strerror (err)); goto leave; } hexkeyid = fpr + 24; ret = asprintf (&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr); if (ret < 0) { err = gpg_error_from_errno (errno); goto leave; } fp = popen (command, "r"); free (command); if (!fp) { err = gpg_error_from_errno (errno); log_error ("running gpg failed: %s\n", gpg_strerror (err)); goto leave; } err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen); fclose (fp); if (err) { log_error ("error while retrieving key material through pipe: %s\n", gpg_strerror (err)); goto leave; } } /* Allocate a buffer to construct the S-expression. */ /* FIXME: We should provide a generalized S-expression creation mechanism. */ keybuf = xtrymalloc (50 + 2*35 + mlen + elen + 1); if (!keybuf) { err = gpg_error_from_errno (errno); goto leave; } sprintf (keybuf, "(10:public-key(3:rsa(1:n%u:", (unsigned int) mlen); keybuf_p = keybuf + strlen (keybuf); memcpy (keybuf_p, m, mlen); keybuf_p += mlen; sprintf (keybuf_p, ")(1:e%u:", (unsigned int)elen); keybuf_p += strlen (keybuf_p); memcpy (keybuf_p, e, elen); keybuf_p += elen; strcpy (keybuf_p, ")))"); keybuf_p += strlen (keybuf_p); app->app_local->pk[keyno].key = (unsigned char*)keybuf; app->app_local->pk[keyno].keylen = (keybuf_p - keybuf); leave: /* Set a flag to indicate that we tried to read the key. */ app->app_local->pk[keyno].read_done = 1; xfree (buffer); xfree (mbuf); xfree (ebuf); return 0; } #endif /* GNUPG_MAJOR_VERSION > 1 */ /* Send the KEYPAIRINFO back. KEYNO needs to be in the range [1,3]. This is used by the LEARN command. */ static gpg_error_t send_keypair_info (app_t app, ctrl_t ctrl, int keyno) { gpg_error_t err = 0; /* Note that GnuPG 1.x does not need this and it would be too time consuming to send it just for the fun of it. */ #if GNUPG_MAJOR_VERSION > 1 unsigned char grip[20]; char gripstr[41]; char idbuf[50]; int i; err = get_public_key (app, keyno); if (err) goto leave; assert (keyno >= 1 && keyno <= 3); if (!app->app_local->pk[keyno-1].key) goto leave; /* No such key - ignore. */ err = keygrip_from_canon_sexp (app->app_local->pk[keyno-1].key, app->app_local->pk[keyno-1].keylen, grip); if (err) goto leave; for (i=0; i < 20; i++) sprintf (gripstr+i*2, "%02X", grip[i]); sprintf (idbuf, "OPENPGP.%d", keyno); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, idbuf, strlen (idbuf), NULL, (size_t)0); leave: #endif /* GNUPG_MAJOR_VERSION > 1 */ return err; } /* Handle the LEARN command for OpenPGP. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) { do_getattr (app, ctrl, "EXTCAP"); do_getattr (app, ctrl, "DISP-NAME"); do_getattr (app, ctrl, "DISP-LANG"); do_getattr (app, ctrl, "DISP-SEX"); do_getattr (app, ctrl, "PUBKEY-URL"); do_getattr (app, ctrl, "LOGIN-DATA"); do_getattr (app, ctrl, "KEY-FPR"); if (app->card_version > 0x0100) do_getattr (app, ctrl, "KEY-TIME"); do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); do_getattr (app, ctrl, "SIG-COUNTER"); if (app->app_local->extcap.private_dos) { do_getattr (app, ctrl, "PRIVATE-DO-1"); do_getattr (app, ctrl, "PRIVATE-DO-2"); if (app->did_chv2) do_getattr (app, ctrl, "PRIVATE-DO-3"); if (app->did_chv3) do_getattr (app, ctrl, "PRIVATE-DO-4"); } send_keypair_info (app, ctrl, 1); send_keypair_info (app, ctrl, 2); send_keypair_info (app, ctrl, 3); return 0; } /* Handle the READKEY command for OpenPGP. On success a canonical encoded S-expression with the public key will get stored at PK and its length (for assertions) at PKLEN; the caller must release that buffer. On error PK and PKLEN are not changed and an error code is returned. */ static gpg_error_t do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { #if GNUPG_MAJOR_VERSION > 1 gpg_error_t err; int keyno; unsigned char *buf; if (!strcmp (keyid, "OPENPGP.1")) keyno = 1; else if (!strcmp (keyid, "OPENPGP.2")) keyno = 2; else if (!strcmp (keyid, "OPENPGP.3")) keyno = 3; else return gpg_error (GPG_ERR_INV_ID); err = get_public_key (app, keyno); if (err) return err; buf = app->app_local->pk[keyno-1].key; if (!buf) return gpg_error (GPG_ERR_NO_PUBKEY); *pklen = app->app_local->pk[keyno-1].keylen;; *pk = xtrymalloc (*pklen); if (!*pk) { err = gpg_error_from_errno (errno); *pklen = 0; return err; } memcpy (*pk, buf, *pklen); return 0; #else return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Verify CHV2 if required. Depending on the configuration of the card CHV1 will also be verified. */ static gpg_error_t verify_chv2 (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; if (!app->did_chv2) { char *pinvalue; iso7816_pininfo_t pininfo; memset (&pininfo, 0, sizeof pininfo); pininfo.mode = 1; pininfo.minlen = 6; rc = pincb (pincb_arg, "PIN", &pinvalue); if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } if (strlen (pinvalue) < 6) { log_error (_("PIN for CHV%d is too short;" " minimum length is %d\n"), 2, 6); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc)); xfree (pinvalue); flush_cache_after_error (app); return rc; } app->did_chv2 = 1; if (!app->did_chv1 && !app->force_chv1) { rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); xfree (pinvalue); flush_cache_after_error (app); return rc; } app->did_chv1 = 1; } xfree (pinvalue); } return rc; } /* Verify CHV3 if required. */ static gpg_error_t verify_chv3 (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; #if GNUPG_MAJOR_VERSION != 1 if (!opt.allow_admin) { log_info (_("access to admin commands is not configured\n")); return gpg_error (GPG_ERR_EACCES); } #endif if (!app->did_chv3) { char *pinvalue; void *relptr; unsigned char *value; size_t valuelen; relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); if (!relptr || valuelen < 7) { log_error (_("error retrieving CHV status from card\n")); xfree (relptr); return gpg_error (GPG_ERR_CARD); } if (value[6] == 0) { log_info (_("card is permanently locked!\n")); xfree (relptr); return gpg_error (GPG_ERR_BAD_PIN); } log_info(_("%d Admin PIN attempts remaining before card" " is permanently locked\n"), value[6]); xfree (relptr); /* TRANSLATORS: Do not translate the "|A|" prefix but keep it at the start of the string. We need this elsewhere to get some infos on the string. */ rc = pincb (pincb_arg, _("|A|Admin PIN"), &pinvalue); if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } if (strlen (pinvalue) < 8) { log_error (_("PIN for CHV%d is too short;" " minimum length is %d\n"), 3, 8); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); xfree (pinvalue); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 3, gpg_strerror (rc)); flush_cache_after_error (app); return rc; } app->did_chv3 = 1; } return rc; } /* Handle the SETATTR operation. All arguments are already basically checked. */ static gpg_error_t do_setattr (app_t app, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { gpg_error_t rc; int idx; static struct { const char *name; int tag; int need_chv; int special; } table[] = { { "DISP-NAME", 0x005B, 3 }, { "LOGIN-DATA", 0x005E, 3, 2 }, { "DISP-LANG", 0x5F2D, 3 }, { "DISP-SEX", 0x5F35, 3 }, { "PUBKEY-URL", 0x5F50, 3 }, { "CHV-STATUS-1", 0x00C4, 3, 1 }, { "CA-FPR-1", 0x00CA, 3 }, { "CA-FPR-2", 0x00CB, 3 }, { "CA-FPR-3", 0x00CC, 3 }, { "PRIVATE-DO-1", 0x0101, 2 }, { "PRIVATE-DO-2", 0x0102, 3 }, { "PRIVATE-DO-3", 0x0103, 2 }, { "PRIVATE-DO-4", 0x0104, 3 }, { NULL, 0 } }; for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) ; if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); switch (table[idx].need_chv) { case 2: rc = verify_chv2 (app, pincb, pincb_arg); break; case 3: rc = verify_chv3 (app, pincb, pincb_arg); break; default: rc = 0; } if (rc) return rc; /* Flush the cache before writing it, so that the next get operation will reread the data from the card and thus get synced in case of errors (e.g. data truncated by the card). */ flush_cache_item (app, table[idx].tag); rc = iso7816_put_data (app->slot, table[idx].tag, value, valuelen); if (rc) log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc)); if (table[idx].special == 1) app->force_chv1 = (valuelen && *value == 0); else if (table[idx].special == 2) parse_login_data (app); return rc; } /* Handle the PASSWD command. */ static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; int chvno = atoi (chvnostr); char *pinvalue; if (reset_mode && chvno == 3) { rc = gpg_error (GPG_ERR_INV_ID); goto leave; } else if (reset_mode || chvno == 3) { /* we always require that the PIN is entered. */ app->did_chv3 = 0; rc = verify_chv3 (app, pincb, pincb_arg); if (rc) goto leave; } else if (chvno == 1 || chvno == 2) { /* CHV1 and CVH2 should always have the same value, thus we enforce it here. */ int save_force = app->force_chv1; app->force_chv1 = 0; app->did_chv1 = 0; app->did_chv2 = 0; rc = verify_chv2 (app, pincb, pincb_arg); app->force_chv1 = save_force; if (rc) goto leave; } else { rc = gpg_error (GPG_ERR_INV_ID); goto leave; } if (chvno == 3) app->did_chv3 = 0; else app->did_chv1 = app->did_chv2 = 0; /* TRANSLATORS: Do not translate the "|*|" prefixes but keep it at the start of the string. We need this elsewhere to get some infos on the string. */ rc = pincb (pincb_arg, chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"), &pinvalue); if (rc) { log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc)); goto leave; } if (reset_mode) { rc = iso7816_reset_retry_counter (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (!rc) rc = iso7816_reset_retry_counter (app->slot, 0x82, pinvalue, strlen (pinvalue)); } else { if (chvno == 1 || chvno == 2) { rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0, pinvalue, strlen (pinvalue)); if (!rc) rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0, pinvalue, strlen (pinvalue)); } else rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0, pinvalue, strlen (pinvalue)); } xfree (pinvalue); if (rc) flush_cache_after_error (app); leave: return rc; } /* Check whether a key already exists. KEYIDX is the index of the key (0..2). If FORCE is TRUE a diagnositic will be printed but no error returned if the key already exists. */ static gpg_error_t does_key_exist (app_t app, int keyidx, int force) { const unsigned char *fpr; unsigned char *buffer; size_t buflen, n; int i; assert (keyidx >=0 && keyidx <= 2); if (iso7816_get_data (app->slot, 0x006E, &buffer, &buflen)) { log_error (_("error reading application data\n")); return gpg_error (GPG_ERR_GENERAL); } fpr = find_tlv (buffer, buflen, 0x00C5, &n); if (!fpr || n < 60) { log_error (_("error reading fingerprint DO\n")); xfree (buffer); return gpg_error (GPG_ERR_GENERAL); } fpr += 20*keyidx; for (i=0; i < 20 && !fpr[i]; i++) ; xfree (buffer); if (i!=20 && !force) { log_error (_("key already exists\n")); return gpg_error (GPG_ERR_EEXIST); } else if (i!=20) log_info (_("existing key will be replaced\n")); else log_info (_("generating new key\n")); return 0; } /* Handle the WRITEKEY command for OpenPGP. This function expects a canonical encoded S-expression with the secret key in KEYDATA and its length (for assertions) in KEYDATALEN. KEYID needs to be the usual keyid which for OpenPGP is the string "OPENPGP.n" with n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall get overwritten. PINCB and PINCB_ARG are the usual arguments for the pinentry callback. */ static gpg_error_t do_writekey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; int force = (flags & 1); int keyno; const unsigned char *buf, *tok; size_t buflen, toklen; int depth, last_depth1, last_depth2; const unsigned char *rsa_n = NULL; const unsigned char *rsa_e = NULL; const unsigned char *rsa_p = NULL; const unsigned char *rsa_q = NULL; size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; unsigned int nbits; unsigned char *template = NULL; unsigned char *tp; size_t template_len; unsigned char fprbuf[20]; u32 created_at = 0; if (!strcmp (keyid, "OPENPGP.1")) keyno = 0; else if (!strcmp (keyid, "OPENPGP.2")) keyno = 1; else if (!strcmp (keyid, "OPENPGP.3")) keyno = 2; else return gpg_error (GPG_ERR_INV_ID); err = does_key_exist (app, keyno, force); if (err) return err; /* Parse the S-expression */ buf = keydata; buflen = keydatalen; depth = 0; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen)) { if (!tok) ; else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen)) log_info ("protected-private-key passed to writekey\n"); else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) log_info ("shadowed-private-key passed to writekey\n"); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen)) { err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); goto leave; } last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) { err = gpg_error (GPG_ERR_UNKNOWN_SEXP); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 1) { const unsigned char **mpi; size_t *mpi_len; switch (*tok) { case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break; case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len;break; default: mpi = NULL; mpi_len = NULL; break; } if (mpi && *mpi) { err = gpg_error (GPG_ERR_DUP_VALUE); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && mpi) { /* Strip off leading zero bytes and save. */ for (;toklen && !*tok; toklen--, tok++) ; *mpi = tok; *mpi_len = toklen; } } /* Skip until end of list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) goto leave; } /* Parse other attributes. */ last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) { err = gpg_error (GPG_ERR_UNKNOWN_SEXP); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen)) { if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen))) goto leave; if (tok) { for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9'; tok++, toklen--) created_at = created_at*10 + (*tok - '0'); } } /* Skip until end of list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) goto leave; } /* Check that we have all parameters and that they match the card description. */ if (!created_at) { log_error (_("creation timestamp missing\n")); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0; if (nbits != 1024) { log_error (_("RSA modulus missing or not of size %d bits\n"), 1024); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0; if (nbits < 2 || nbits > 32) { log_error (_("RSA public exponent missing or larger than %d bits\n"), 32); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0; if (nbits != 512) { log_error (_("RSA prime %s missing or not of size %d bits\n"), "P", 512); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0; if (nbits != 512) { log_error (_("RSA prime %s missing or not of size %d bits\n"), "Q", 512); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } /* Build the private key template as described in section 4.3.3.6 of the OpenPGP card specs: 0xC0 public exponent 0xC1 prime p 0xC2 prime q */ assert (rsa_e_len <= 4); template_len = (1 + 1 + 4 + 1 + 1 + rsa_p_len + 1 + 1 + rsa_q_len); template = tp = xtrymalloc_secure (template_len); if (!template) { err = gpg_error_from_errno (errno); goto leave; } *tp++ = 0xC0; *tp++ = 4; memcpy (tp, rsa_e, rsa_e_len); if (rsa_e_len < 4) { /* Right justify E. */ memmove (tp+4-rsa_e_len, tp, 4-rsa_e_len); memset (tp, 0, 4-rsa_e_len); } tp += 4; *tp++ = 0xC1; *tp++ = rsa_p_len; memcpy (tp, rsa_p, rsa_p_len); tp += rsa_p_len; *tp++ = 0xC2; *tp++ = rsa_q_len; memcpy (tp, rsa_q, rsa_q_len); tp += rsa_q_len; assert (tp - template == template_len); /* Obviously we need to remove the cached public key. */ xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; app->app_local->pk[keyno].read_done = 0; /* Prepare for storing the key. */ err = verify_chv3 (app, pincb, pincb_arg); if (err) goto leave; /* Store the key. */ err = iso7816_put_data (app->slot, (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno, template, template_len); if (err) { log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); goto leave; } err = store_fpr (app->slot, keyno, created_at, rsa_n, rsa_n_len, rsa_e, rsa_e_len, fprbuf, app->card_version); if (err) goto leave; leave: xfree (template); return err; } /* Handle the GENKEY command. */ static gpg_error_t do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc; char numbuf[30]; unsigned char fprbuf[20]; const unsigned char *keydata, *m, *e; unsigned char *buffer = NULL; size_t buflen, keydatalen, mlen, elen; time_t created_at; int keyno = atoi (keynostr); int force = (flags & 1); time_t start_at; if (keyno < 1 || keyno > 3) return gpg_error (GPG_ERR_INV_ID); keyno--; /* We flush the cache to increase the traffic before a key generation. This _might_ help a card to gather more entropy. */ flush_cache (app); /* Obviously we need to remove the cached public key. */ xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; app->app_local->pk[keyno].read_done = 0; /* Check whether a key already exists. */ rc = does_key_exist (app, keyno, force); if (rc) return rc; /* Prepare for key generation by verifying the ADmin PIN. */ rc = verify_chv3 (app, pincb, pincb_arg); if (rc) goto leave; #if 1 log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); rc = iso7816_generate_keypair #else #warning key generation temporary replaced by reading an existing key. rc = iso7816_read_public_key #endif (app->slot, (const unsigned char*)(keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, &buffer, &buflen); if (rc) { rc = gpg_error (GPG_ERR_CARD); log_error (_("generating key failed\n")); goto leave; } log_info (_("key generation completed (%d seconds)\n"), (int)(time (NULL) - start_at)); keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); if (!keydata) { rc = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the public key data\n")); goto leave; } m = find_tlv (keydata, keydatalen, 0x0081, &mlen); if (!m) { rc = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the RSA modulus\n")); goto leave; } /* log_printhex ("RSA n:", m, mlen); */ send_key_data (ctrl, "n", m, mlen); e = find_tlv (keydata, keydatalen, 0x0082, &elen); if (!e) { rc = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the RSA public exponent\n")); goto leave; } /* log_printhex ("RSA e:", e, elen); */ send_key_data (ctrl, "e", e, elen); created_at = gnupg_get_time (); sprintf (numbuf, "%lu", (unsigned long)created_at); send_status_info (ctrl, "KEY-CREATED-AT", numbuf, (size_t)strlen(numbuf), NULL, 0); rc = store_fpr (app->slot, keyno, (u32)created_at, m, mlen, e, elen, fprbuf, app->card_version); if (rc) goto leave; send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); leave: xfree (buffer); return rc; } static unsigned long convert_sig_counter_value (const unsigned char *value, size_t valuelen) { unsigned long ul; if (valuelen == 3 ) ul = (value[0] << 16) | (value[1] << 8) | value[2]; else { log_error (_("invalid structure of OpenPGP card (DO 0x93)\n")); ul = 0; } return ul; } static unsigned long get_sig_counter (app_t app) { void *relptr; unsigned char *value; size_t valuelen; unsigned long ul; relptr = get_one_do (app, 0x0093, &value, &valuelen, NULL); if (!relptr) return 0; ul = convert_sig_counter_value (value, valuelen); xfree (relptr); return ul; } static gpg_error_t compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) { const unsigned char *fpr; unsigned char *buffer; size_t buflen, n; int rc, i; assert (keyno >= 1 && keyno <= 3); rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0); if (rc) { log_error (_("error reading application data\n")); return gpg_error (GPG_ERR_GENERAL); } fpr = find_tlv (buffer, buflen, 0x00C5, &n); if (!fpr || n != 60) { xfree (buffer); log_error (_("error reading fingerprint DO\n")); return gpg_error (GPG_ERR_GENERAL); } fpr += (keyno-1)*20; for (i=0; i < 20; i++) if (sha1fpr[i] != fpr[i]) { xfree (buffer); return gpg_error (GPG_ERR_WRONG_SECKEY); } xfree (buffer); return 0; } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint we assume that this is okay. */ static gpg_error_t check_against_given_fingerprint (app_t app, const char *fpr, int keyno) { unsigned char tmp[20]; const char *s; int n; for (s=fpr, n=0; hexdigitp (s); s++, n++) ; if (n != 40) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* okay */ else return gpg_error (GPG_ERR_INV_ID); for (s=fpr, n=0; n < 20; s += 2, n++) tmp[n] = xtoi_2 (s); return compare_fingerprint (app, keyno, tmp); } /* Compute a digital signature on INDATA which is expected to be the raw message digest. For this application the KEYIDSTR consists of the serialnumber and the fingerprint delimited by a slash. Note that this function may return the error code GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). */ static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; int rc; unsigned char data[35]; unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ const char *s; int n; const char *fpr = NULL; unsigned long sigcount; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (indatalen == 20) ; else if (indatalen == (15 + 20) && hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) ; else if (indatalen == (15 + 20) && hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix, 15)) ; else { log_error (_("card does not support digest algorithm %s\n"), gcry_md_algo_name (hashalgo)); return gpg_error (GPG_ERR_INV_VALUE); } /* Check whether an OpenPGP card of any version has been requested. */ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* no fingerprint given: we allow this for now. */ else if (*s == '/') fpr = s + 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, gpg will detect a bogus signature anyway due to the verify-after-signing feature. */ rc = fpr? check_against_given_fingerprint (app, fpr, 1) : 0; if (rc) return rc; if (hashalgo == GCRY_MD_SHA1) memcpy (data, sha1_prefix, 15); else if (hashalgo == GCRY_MD_RMD160) memcpy (data, rmd160_prefix, 15); else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data+15, indata, indatalen); sigcount = get_sig_counter (app); log_info (_("signatures created so far: %lu\n"), sigcount); if (!app->did_chv1 || app->force_chv1 ) { char *pinvalue; { char *prompt; #define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]") prompt = malloc (strlen (PROMPTSTRING) + 50); if (!prompt) return gpg_error_from_errno (errno); sprintf (prompt, PROMPTSTRING, sigcount); rc = pincb (pincb_arg, prompt, &pinvalue); free (prompt); #undef PROMPTSTRING } if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } if (strlen (pinvalue) < 6) { log_error (_("PIN for CHV%d is too short;" " minimum length is %d\n"), 1, 6); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); xfree (pinvalue); flush_cache_after_error (app); return rc; } app->did_chv1 = 1; if (!app->did_chv2) { /* We should also verify CHV2. */ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc)); xfree (pinvalue); flush_cache_after_error (app); return rc; } app->did_chv2 = 1; } xfree (pinvalue); } rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); return rc; } /* Compute a digital signature using the INTERNAL AUTHENTICATE command on INDATA which is expected to be the raw message digest. For this application the KEYIDSTR consists of the serialnumber and the fingerprint delimited by a slash. Optionally the id OPENPGP.3 may be given. Note that this function may return the error code GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). */ static gpg_error_t do_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { int rc; unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ const char *s; int n; const char *fpr = NULL; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (indatalen > 50) /* For a 1024 bit key. */ return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ if (!strcmp (keyidstr, "OPENPGP.3")) ; else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); else { for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* no fingerprint given: we allow this for now. */ else if (*s == '/') fpr = s + 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, gpg will detect a bogus signature anyway due to the verify-after-signing feature. */ rc = fpr? check_against_given_fingerprint (app, fpr, 3) : 0; if (rc) return rc; rc = verify_chv2 (app, pincb, pincb_arg); if (!rc) rc = iso7816_internal_authenticate (app->slot, indata, indatalen, outdata, outdatalen); return rc; } static gpg_error_t do_decipher (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { int rc; unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ const char *s; int n; const char *fpr = NULL; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* no fingerprint given: we allow this for now. */ else if (*s == '/') fpr = s + 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, the decryption will won't produce the right plaintext anyway. */ rc = fpr? check_against_given_fingerprint (app, fpr, 2) : 0; if (rc) return rc; rc = verify_chv2 (app, pincb, pincb_arg); if (!rc) rc = iso7816_decipher (app->slot, indata, indatalen, 0, outdata, outdatalen); return rc; } /* Perform a simple verify operation for CHV1 and CHV2, so that further operations won't ask for CHV2 and it is possible to do a cheap check on the PIN: If there is something wrong with the PIN entry system, only the regular CHV will get blocked and not the dangerous CHV3. KEYIDSTR is the usual card's serial number; an optional fingerprint part will be ignored. There is a special mode if the keyidstr is "[CHV3]" with the "[CHV3]" being a literal string: The Admin Pin is checked if and only if the retry counter is still at 3. */ static gpg_error_t do_check_pin (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { unsigned char tmp_sn[20]; const char *s; int n; int admin_pin = 0; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* No fingerprint given: we allow this for now. */ else if (*s == '/') ; /* We ignore a fingerprint. */ else if (!strcmp (s, "[CHV3]") ) admin_pin = 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); /* Yes, there is a race conditions: The user might pull the card right here and we won't notice that. However this is not a problem and the check above is merely for a graceful failure between operations. */ if (admin_pin) { void *relptr; unsigned char *value; size_t valuelen; int count; relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); if (!relptr || valuelen < 7) { log_error (_("error retrieving CHV status from card\n")); xfree (relptr); return gpg_error (GPG_ERR_CARD); } count = value[6]; xfree (relptr); if (!count) { log_info (_("card is permanently locked!\n")); return gpg_error (GPG_ERR_BAD_PIN); } else if (value[6] < 3) { log_info (_("verification of Admin PIN is currently prohibited " "through this command\n")); return gpg_error (GPG_ERR_GENERAL); } app->did_chv3 = 0; /* Force verification. */ return verify_chv3 (app, pincb, pincb_arg); } else return verify_chv2 (app, pincb, pincb_arg); } /* Select the OpenPGP application on the card in SLOT. This function must be used before any other OpenPGP application functions. */ gpg_error_t app_select_openpgp (app_t app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; int slot = app->slot; int rc; unsigned char *buffer; size_t buflen; void *relptr; /* Note that the card can't cope with P2=0xCO, thus we need to pass a special flag value. */ rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001); if (!rc) { unsigned int manufacturer; app->apptype = "OPENPGP"; app->did_chv1 = 0; app->did_chv2 = 0; app->did_chv3 = 0; app->app_local = NULL; /* The OpenPGP card returns the serial number as part of the AID; because we prefer to use OpenPGP serial numbers, we replace a possibly already set one from a EF.GDO with this one. Note, that for current OpenPGP cards, no EF.GDO exists and thus it won't matter at all. */ rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen); if (rc) goto leave; if (opt.verbose) { log_info ("AID: "); log_printhex ("", buffer, buflen); } app->card_version = buffer[6] << 8; app->card_version |= buffer[7]; manufacturer = (buffer[8]<<8 | buffer[9]); xfree (app->serialno); app->serialno = buffer; app->serialnolen = buflen; buffer = NULL; app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL); if (!relptr) { log_error (_("can't access %s - invalid OpenPGP card?\n"), "CHV Status Bytes"); goto leave; } app->force_chv1 = (buflen && *buffer == 0); xfree (relptr); relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL); if (!relptr) { log_error (_("can't access %s - invalid OpenPGP card?\n"), "Extended Capability Flags" ); goto leave; } if (buflen) { app->app_local->extcap.get_challenge = !!(*buffer & 0x40); app->app_local->extcap.key_import = !!(*buffer & 0x20); app->app_local->extcap.change_force_chv = !!(*buffer & 0x10); app->app_local->extcap.private_dos = !!(*buffer & 0x08); } xfree (relptr); /* Some of the first cards accidently don't set the CHANGE_FORCE_CHV bit but allow it anyway. */ if (app->card_version <= 0x0100 && manufacturer == 1) app->app_local->extcap.change_force_chv = 1; parse_login_data (app); if (opt.verbose > 1) dump_all_do (slot); app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; app->fnc.writekey = do_writekey; app->fnc.genkey = do_genkey; app->fnc.sign = do_sign; app->fnc.auth = do_auth; app->fnc.decipher = do_decipher; app->fnc.change_pin = do_change_pin; app->fnc.check_pin = do_check_pin; } leave: if (rc) do_deinit (app); return rc; } diff --git a/scd/app-p15.c b/scd/app-p15.c index 4203a6840..8a7732c85 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -1,3415 +1,3416 @@ /* app-p15.c - The pkcs#15 card application. * Copyright (C) 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* Information pertaining to the BELPIC developer card samples: Unblock PUK: "222222111111" Reset PIN: "333333111111") e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF should change the PIN into 1234. */ #include #include #include #include #include #include #include #include "scdaemon.h" #include "iso7816.h" #include "app-common.h" #include "tlv.h" #include "apdu.h" /* fixme: we should move the card detection to a separate file */ /* Types of cards we know and which needs special treatment. */ typedef enum { CARD_TYPE_UNKNOWN, CARD_TYPE_TCOS, CARD_TYPE_MICARDO, CARD_TYPE_BELPIC /* Belgian eID card specs. */ } card_type_t; /* A list card types with ATRs noticed with these cards. */ #define X(a) ((unsigned char const *)(a)) static struct { size_t atrlen; unsigned char const *atr; card_type_t type; } card_atr_list[] = { { 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80" "\x90\x00\x8B"), CARD_TYPE_TCOS }, /* SLE44 */ { 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80" "\x90\x00\x91"), CARD_TYPE_TCOS }, /* SLE66S */ { 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80" "\x90\x00\x66"), CARD_TYPE_TCOS }, /* SLE66P */ { 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00" "\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"), CARD_TYPE_MICARDO }, /* German BMI card */ { 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80" "\x00\x90\x00"), CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */ { 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49" "\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"), CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */ { 0 } }; #undef X /* The AID of PKCS15. */ static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63, 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; /* The Belgian eID variant - they didn't understood why a shared AID is useful for a standard. Oh well. */ static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77, 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; /* The PIN types as defined in pkcs#15 v1.1 */ typedef enum { PIN_TYPE_BCD = 0, PIN_TYPE_ASCII_NUMERIC = 1, PIN_TYPE_UTF8 = 2, PIN_TYPE_HALF_NIBBLE_BCD = 3, PIN_TYPE_ISO9564_1 = 4 } pin_type_t; /* A bit array with for the key usage flags from the commonKeyAttributes. */ struct keyusage_flags_s { unsigned int encrypt: 1; unsigned int decrypt: 1; unsigned int sign: 1; unsigned int sign_recover: 1; unsigned int wrap: 1; unsigned int unwrap: 1; unsigned int verify: 1; unsigned int verify_recover: 1; unsigned int derive: 1; unsigned int non_repudiation: 1; }; typedef struct keyusage_flags_s keyusage_flags_t; /* This is an object to store information about a Certificate Directory File (CDF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one certificate and the list the entire CDF. */ struct cdf_object_s { /* Link to next item when used in a linked list. */ struct cdf_object_s *next; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* To avoid reading a certificate more than once, we cache it in an allocated memory IMAGE of IMAGELEN. */ size_t imagelen; unsigned char *image; /* Set to true if a length and offset is available. */ int have_off; /* The offset and length of the object. They are only valid if HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ unsigned long off, len; /* The length of the path as given in the CDF and the path itself. path[0] is the top DF (usually 0x3f00). The path will never be empty. */ size_t pathlen; unsigned short path[1]; }; typedef struct cdf_object_s *cdf_object_t; /* This is an object to store information about a Private Key Directory File (PrKDF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one certificate and the list the entire PrKDF. */ struct prkdf_object_s { /* Link to next item when used in a linked list. */ struct prkdf_object_s *next; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* Length and allocated buffer with the authId of this object or NULL if no authID is known. */ size_t authidlen; unsigned char *authid; /* The key's usage flags. */ keyusage_flags_t usageflags; /* The keyReference and a flag telling whether it is valid. */ unsigned long key_reference; int key_reference_valid; /* Set to true if a length and offset is available. */ int have_off; /* The offset and length of the object. They are only valid if HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ unsigned long off, len; /* The length of the path as given in the PrKDF and the path itself. path[0] is the top DF (usually 0x3f00). */ size_t pathlen; unsigned short path[1]; }; typedef struct prkdf_object_s *prkdf_object_t; /* This is an object to store information about a Authentication Object Directory File (AODF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one authentication object and the list the entire AOKDF. */ struct aodf_object_s { /* Link to next item when used in a linked list. */ struct aodf_object_s *next; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* Length and allocated buffer with the authId of this object or NULL if no authID is known. */ size_t authidlen; unsigned char *authid; /* The PIN Flags. */ struct { unsigned int case_sensitive: 1; unsigned int local: 1; unsigned int change_disabled: 1; unsigned int unblock_disabled: 1; unsigned int initialized: 1; unsigned int needs_padding: 1; unsigned int unblocking_pin: 1; unsigned int so_pin: 1; unsigned int disable_allowed: 1; unsigned int integrity_protected: 1; unsigned int confidentiality_protected: 1; unsigned int exchange_ref_data: 1; } pinflags; /* The PIN Type. */ pin_type_t pintype; /* The minimum length of a PIN. */ unsigned long min_length; /* The stored length of a PIN. */ unsigned long stored_length; /* The maximum length of a PIN and a flag telling whether it is valid. */ unsigned long max_length; int max_length_valid; /* The pinReference and a flag telling whether it is valid. */ unsigned long pin_reference; int pin_reference_valid; /* The padChar and a flag telling whether it is valid. */ char pad_char; int pad_char_valid; /* Set to true if a length and offset is available. */ int have_off; /* The offset and length of the object. They are only valid if HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ unsigned long off, len; /* The length of the path as given in the Aodf and the path itself. path[0] is the top DF (usually 0x3f00). PATH is optional and thus may be NULL. Malloced.*/ size_t pathlen; unsigned short *path; }; typedef struct aodf_object_s *aodf_object_t; /* Context local to this application. */ struct app_local_s { /* The home DF. Note, that we don't yet support a multilevel hierachy. Thus we assume this is directly below the MF. */ unsigned short home_df; /* The type of the card. */ card_type_t card_type; /* Flag indicating whether we may use direct path selection. */ int direct_path_selection; /* Structure with the EFIDs of the objects described in the ODF file. */ struct { unsigned short private_keys; unsigned short public_keys; unsigned short trusted_public_keys; unsigned short secret_keys; unsigned short certificates; unsigned short trusted_certificates; unsigned short useful_certificates; unsigned short data_objects; unsigned short auth_objects; } odf; /* The PKCS#15 serialnumber from EF(TokeiNFo) or NULL. Malloced. */ unsigned char *serialno; size_t serialnolen; /* Information on all certificates. */ cdf_object_t certificate_info; /* Information on all trusted certificates. */ cdf_object_t trusted_certificate_info; /* Information on all useful certificates. */ cdf_object_t useful_certificate_info; /* Information on all private keys. */ prkdf_object_t private_key_info; /* Information on all authentication objects. */ aodf_object_t auth_object_info; }; /*** Local prototypes. ***/ static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, unsigned char **r_cert, size_t *r_certlen); /* Release the CDF object A */ static void release_cdflist (cdf_object_t a) { while (a) { cdf_object_t tmp = a->next; xfree (a->image); xfree (a->objid); xfree (a); a = tmp; } } /* Release the PrKDF object A. */ static void release_prkdflist (prkdf_object_t a) { while (a) { prkdf_object_t tmp = a->next; xfree (a->objid); xfree (a->authid); xfree (a); a = tmp; } } /* Release just one aodf object. */ void release_aodf_object (aodf_object_t a) { if (a) { xfree (a->objid); xfree (a->authid); xfree (a->path); xfree (a); } } /* Release the AODF list A. */ static void release_aodflist (aodf_object_t a) { while (a) { aodf_object_t tmp = a->next; release_aodf_object (a); a = tmp; } } /* Release all local resources. */ static void do_deinit (app_t app) { if (app && app->app_local) { release_cdflist (app->app_local->certificate_info); release_cdflist (app->app_local->trusted_certificate_info); release_cdflist (app->app_local->useful_certificate_info); release_prkdflist (app->app_local->private_key_info); release_aodflist (app->app_local->auth_object_info); xfree (app->app_local->serialno); xfree (app->app_local); app->app_local = NULL; } } /* Do a select and a read for the file with EFID. EFID_DESC is a desctription of the EF to be used with error messages. On success BUFFER and BUFLEN contain the entire content of the EF. The caller must free BUFFER only on success. */ static gpg_error_t select_and_read_binary (int slot, unsigned short efid, const char *efid_desc, unsigned char **buffer, size_t *buflen) { gpg_error_t err; err = iso7816_select_file (slot, efid, 0, NULL, NULL); if (err) { log_error ("error selecting %s (0x%04X): %s\n", efid_desc, efid, gpg_strerror (err)); return err; } err = iso7816_read_binary (slot, 0, 0, buffer, buflen); if (err) { log_error ("error reading %s (0x%04X): %s\n", efid_desc, efid, gpg_strerror (err)); return err; } return 0; } /* This function calls select file to read a file using a complete path which may or may not start at the master file (MF). */ static gpg_error_t select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen) { gpg_error_t err; int i, j; if (!pathlen) return gpg_error (GPG_ERR_INV_VALUE); if (pathlen && *path != 0x3f00 ) log_debug ("WARNING: relative path selection not yet implemented\n"); if (app->app_local->direct_path_selection) { err = iso7816_select_path (app->slot, path+1, pathlen-1, NULL, NULL); if (err) { log_error ("error selecting path "); for (j=0; j < pathlen; j++) log_printf ("%04hX", path[j]); log_printf (": %s\n", gpg_strerror (err)); return err; } } else { /* FIXME: Need code to remember the last PATH so that we can decide what select commands to send in case the path does not start off with 3F00. We might also want to use direct path selection if supported by the card. */ for (i=0; i < pathlen; i++) { err = iso7816_select_file (app->slot, path[i], !(i+1 == pathlen), NULL, NULL); if (err) { log_error ("error selecting part %d from path ", i); for (j=0; j < pathlen; j++) log_printf ("%04hX", path[j]); log_printf (": %s\n", gpg_strerror (err)); return err; } } } return 0; } /* Parse a cert Id string (or a key Id string) and return the binary object Id string in a newly allocated buffer stored at R_OBJID and R_OBJIDLEN. On Error NULL will be stored there and an error code returned. On success caller needs to free the buffer at R_OBJID. */ static gpg_error_t parse_certid (app_t app, const char *certid, unsigned char **r_objid, size_t *r_objidlen) { char tmpbuf[10]; const char *s; size_t objidlen; unsigned char *objid; int i; *r_objid = NULL; *r_objidlen = 0; if (app->app_local->home_df) sprintf (tmpbuf, "P15-%04hX.", (app->app_local->home_df & 0xffff)); else strcpy (tmpbuf, "P15."); if (strncmp (certid, tmpbuf, strlen (tmpbuf)) ) { if (!strncmp (certid, "P15.", 4) || (!strncmp (certid, "P15-", 4) && hexdigitp (certid+4) && hexdigitp (certid+5) && hexdigitp (certid+6) && hexdigitp (certid+7) && certid[8] == '.')) return gpg_error (GPG_ERR_NOT_FOUND); return gpg_error (GPG_ERR_INV_ID); } certid += strlen (tmpbuf); for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++) ; if (*s || !objidlen || (objidlen%2)) return gpg_error (GPG_ERR_INV_ID); objidlen /= 2; objid = xtrymalloc (objidlen); if (!objid) return gpg_error_from_errno (errno); for (s=certid, i=0; i < objidlen; i++, s+=2) objid[i] = xtoi_2 (s); *r_objid = objid; *r_objidlen = objidlen; return 0; } /* Find a certificate object by the certificate ID CERTID and store a pointer to it at R_CDF. */ static gpg_error_t cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf) { gpg_error_t err; size_t objidlen; unsigned char *objid; cdf_object_t cdf; err = parse_certid (app, certid, &objid, &objidlen); if (err) return err; for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; if (!cdf) for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; if (!cdf) for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; xfree (objid); if (!cdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_cdf = cdf; return 0; } /* Find a private key object by the key Id string KEYIDSTR and store a pointer to it at R_PRKDF. */ static gpg_error_t prkdf_object_from_keyidstr (app_t app, const char *keyidstr, prkdf_object_t *r_prkdf) { gpg_error_t err; size_t objidlen; unsigned char *objid; prkdf_object_t prkdf; err = parse_certid (app, keyidstr, &objid, &objidlen); if (err) return err; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) break; xfree (objid); if (!prkdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_prkdf = prkdf; return 0; } /* Read and parse the Object Directory File and store away the pointers. ODF_FID shall contain the FID of the ODF. Example of such a file: A0 06 30 04 04 02 60 34 = Private Keys A4 06 30 04 04 02 60 35 = Certificates A5 06 30 04 04 02 60 36 = TrustedCertificates A7 06 30 04 04 02 60 37 = DataObjects A8 06 30 04 04 02 60 38 = AuthObjects These are all PathOrObjects using the path CHOICE element. The paths are octet strings of length 2. Using this Path CHOICE element is recommended, so we only implement that for now. */ static gpg_error_t read_ef_odf (app_t app, unsigned short odf_fid) { gpg_error_t err; unsigned char *buffer, *p; size_t buflen; unsigned short value; size_t offset; err = select_and_read_binary (app->slot, odf_fid, "ODF", &buffer, &buflen); if (err) return err; if (buflen < 8) { log_error ("error: ODF too short\n"); xfree (buffer); return gpg_error (GPG_ERR_INV_OBJ); } p = buffer; while (buflen && *p && *p != 0xff) { if ( buflen >= 8 && (p[0] & 0xf0) == 0xA0 && !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) ) { offset = 6; } else if ( buflen >= 12 && (p[0] & 0xf0) == 0xA0 && !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7) && app->app_local->home_df == ((p[8]<<8)|p[9]) ) { /* We only allow a full path if all files are at the same level and below the home directory. The extend this we would need to make use of new data type capable of keeping a full path. */ offset = 10; } else { log_error ("ODF format is not supported by us\n"); xfree (buffer); return gpg_error (GPG_ERR_INV_OBJ); } switch ((p[0] & 0x0f)) { case 0: value = app->app_local->odf.private_keys; break; case 1: value = app->app_local->odf.public_keys; break; case 2: value = app->app_local->odf.trusted_public_keys; break; case 3: value = app->app_local->odf.secret_keys; break; case 4: value = app->app_local->odf.certificates; break; case 5: value = app->app_local->odf.trusted_certificates; break; case 6: value = app->app_local->odf.useful_certificates; break; case 7: value = app->app_local->odf.data_objects; break; case 8: value = app->app_local->odf.auth_objects; break; default: value = 0; break; } if (value) { log_error ("duplicate object type %d in ODF ignored\n",(p[0]&0x0f)); continue; } value = ((p[offset] << 8) | p[offset+1]); switch ((p[0] & 0x0f)) { case 0: app->app_local->odf.private_keys = value; break; case 1: app->app_local->odf.public_keys = value; break; case 2: app->app_local->odf.trusted_public_keys = value; break; case 3: app->app_local->odf.secret_keys = value; break; case 4: app->app_local->odf.certificates = value; break; case 5: app->app_local->odf.trusted_certificates = value; break; case 6: app->app_local->odf.useful_certificates = value; break; case 7: app->app_local->odf.data_objects = value; break; case 8: app->app_local->odf.auth_objects = value; break; default: log_error ("unknown object type %d in ODF ignored\n", (p[0]&0x0f)); } offset += 2; if (buflen < offset) break; p += offset; buflen -= offset; } if (buflen) log_info ("warning: %u bytes of garbage detected at end of ODF\n", buflen); xfree (buffer); return 0; } /* Parse the BIT STRING with the keyUsageFlags from teh CommonKeyAttributes. */ static gpg_error_t parse_keyusage_flags (const unsigned char *der, size_t derlen, keyusage_flags_t *usageflags) { unsigned int bits, mask; int i, unused, full; memset (usageflags, 0, sizeof *usageflags); if (!derlen) return gpg_error (GPG_ERR_INV_OBJ); unused = *der++; derlen--; if ((!derlen && unused) || unused/8 > derlen) return gpg_error (GPG_ERR_ENCODING_PROBLEM); full = derlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* First octet */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } else bits = 0; if ((bits & 0x80)) usageflags->encrypt = 1; if ((bits & 0x40)) usageflags->decrypt = 1; if ((bits & 0x20)) usageflags->sign = 1; if ((bits & 0x10)) usageflags->sign_recover = 1; if ((bits & 0x08)) usageflags->wrap = 1; if ((bits & 0x04)) usageflags->unwrap = 1; if ((bits & 0x02)) usageflags->verify = 1; if ((bits & 0x01)) usageflags->verify_recover = 1; /* Second octet. */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } else bits = 0; if ((bits & 0x80)) usageflags->derive = 1; if ((bits & 0x40)) usageflags->non_repudiation = 1; return 0; } /* Read and parse the Private Key Directory Files. */ /* 6034 (privatekeys) 30 33 30 11 0C 08 53 4B 2E 43 48 2E 44 53 03 02 030...SK.CH.DS.. 06 80 04 01 07 30 0C 04 01 01 03 03 06 00 40 02 .....0........@. 02 00 50 A1 10 30 0E 30 08 04 06 3F 00 40 16 00 ..P..0.0...?.@.. 50 02 02 04 00 30 33 30 11 0C 08 53 4B 2E 43 48 P....030...SK.CH 2E 4B 45 03 02 06 80 04 01 0A 30 0C 04 01 0C 03 .KE.......0..... 03 06 44 00 02 02 00 52 A1 10 30 0E 30 08 04 06 ..D....R..0.0... 3F 00 40 16 00 52 02 02 04 00 30 34 30 12 0C 09 ?.@..R....040... 53 4B 2E 43 48 2E 41 55 54 03 02 06 80 04 01 0A SK.CH.AUT....... 30 0C 04 01 0D 03 03 06 20 00 02 02 00 51 A1 10 0....... ....Q.. 30 0E 30 08 04 06 3F 00 40 16 00 51 02 02 04 00 0.0...?.@..Q.... 30 37 30 15 0C 0C 53 4B 2E 43 48 2E 44 53 2D 53 070...SK.CH.DS-S 50 58 03 02 06 80 04 01 0A 30 0C 04 01 02 03 03 PX.......0...... 06 20 00 02 02 00 53 A1 10 30 0E 30 08 04 06 3F . ....S..0.0...? 00 40 16 00 53 02 02 04 00 00 00 00 00 00 00 00 .@..S........... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0 30 51: SEQUENCE { 2 30 17: SEQUENCE { -- commonObjectAttributes 4 0C 8: UTF8String 'SK.CH.DS' 14 03 2: BIT STRING 6 unused bits : '01'B (bit 0) 18 04 1: OCTET STRING --authid : 07 : } 21 30 12: SEQUENCE { -- commonKeyAttributes 23 04 1: OCTET STRING : 01 26 03 3: BIT STRING 6 unused bits : '1000000000'B (bit 9) 31 02 2: INTEGER 80 -- keyReference (optional) : } 35 A1 16: [1] { -- keyAttributes 37 30 14: SEQUENCE { -- privateRSAKeyAttributes 39 30 8: SEQUENCE { -- objectValue 41 04 6: OCTET STRING --path : 3F 00 40 16 00 50 : } 49 02 2: INTEGER 1024 -- modulus : } : } : } */ static gpg_error_t read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; prkdf_object_t prkdflist = NULL; int i; if (!fid) return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */ err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen); if (err) return err; p = buffer; n = buflen; /* FIXME: This shares a LOT of code with read_ef_cdf! */ /* Loop over the records. We stop as soon as we detect a new record starting with 0x00 or 0xff as these values are commonly used to pad data blocks and are no valid ASN.1 encoding. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; prkdf_object_t prkdf = NULL; unsigned long ul; const unsigned char *objid; size_t objidlen; const unsigned char *authid = NULL; size_t authidlen = 0; keyusage_flags_t usageflags; unsigned long key_reference = 0; int key_reference_valid = 0; const char *s; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Parse the commonObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Search the optional AuthId. We need to skip the optional Label (UTF8STRING) and the optional CommonObjectFlags (BITSTRING). */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; if (tag == TAG_UTF8_STRING) { ppp += objlen; /* Skip the Label. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; } if (tag == TAG_BIT_STRING) { ppp += objlen; /* Skip the CommonObjectFlags. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; } if (tag == TAG_OCTET_STRING && objlen) { authid = ppp; authidlen = objlen; } no_authid: ; } /* Parse the commonKeyAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; objid = ppp; objidlen = objlen; ppp += objlen; nnn -= objlen; /* Get the KeyUsageFlags. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; err = parse_keyusage_flags (ppp, objlen, &usageflags); if (err) goto parse_error; ppp += objlen; nnn -= objlen; /* Find the keyReference */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto leave_cki; if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN) { /* Skip the native element. */ ppp += objlen; nnn -= objlen; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto leave_cki; if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) { /* Skip the accessFlags. */ ppp += objlen; nnn -= objlen; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto leave_cki; if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) { /* Yep, this is the keyReference. */ for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } key_reference = ul; key_reference_valid = 1; } leave_cki: ; } /* Skip subClassAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_CONTEXT && tag == 0) { pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); } /* Parse the keyAttributes. */ if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* RSA */ else if (class == CLASS_CONTEXT) { switch (tag) { case 0: errstr = "EC key objects are not supported"; break; case 1: errstr = "DH key objects are not supported"; break; case 2: errstr = "DSA key objects are not supported"; break; case 3: errstr = "KEA key objects are not supported"; break; default: errstr = "unknown privateKeyObject"; break; } goto parse_error; } else { err = gpg_error (GPG_ERR_INV_OBJ); goto parse_error; } nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { errstr = "unsupported reference type"; goto parse_error; } nn = objlen; /* Parse the Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element is a non zero path and of even length (FID are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || !objlen || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } /* Create a new PrKDF list item. */ prkdf = xtrycalloc (1, (sizeof *prkdf - sizeof(unsigned short) + objlen/2 * sizeof(unsigned short))); if (!prkdf) { err = gpg_error_from_errno (errno); goto leave; } prkdf->objidlen = objidlen; prkdf->objid = xtrymalloc (objidlen); if (!prkdf->objid) { err = gpg_error_from_errno (errno); xfree (prkdf); goto leave; } memcpy (prkdf->objid, objid, objidlen); if (authid) { prkdf->authidlen = authidlen; prkdf->authid = xtrymalloc (authidlen); if (!prkdf->authid) { err = gpg_error_from_errno (errno); xfree (prkdf->objid); xfree (prkdf); goto leave; } memcpy (prkdf->authid, authid, authidlen); } prkdf->pathlen = objlen/2; for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2) prkdf->path[i] = ((pp[0] << 8) | pp[1]); prkdf->usageflags = usageflags; prkdf->key_reference = key_reference; prkdf->key_reference_valid = key_reference_valid; if (nn) { /* An index and length follows. */ prkdf->have_off = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } prkdf->off = ul; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } prkdf->len = ul; } log_debug ("PrKDF %04hX: id=", fid); for (i=0; i < prkdf->objidlen; i++) log_printf ("%02X", prkdf->objid[i]); log_printf (" path="); for (i=0; i < prkdf->pathlen; i++) log_printf ("%04hX", prkdf->path[i]); if (prkdf->have_off) log_printf ("[%lu/%lu]", prkdf->off, prkdf->len); if (prkdf->authid) { log_printf (" authid="); for (i=0; i < prkdf->authidlen; i++) log_printf ("%02X", prkdf->authid[i]); } if (prkdf->key_reference_valid) log_printf (" keyref=0x%02lX", prkdf->key_reference); log_printf (" usage="); s = ""; if (prkdf->usageflags.encrypt) log_printf ("%sencrypt", s), s = ","; if (prkdf->usageflags.decrypt) log_printf ("%sdecrypt", s), s = ","; if (prkdf->usageflags.sign ) log_printf ("%ssign", s), s = ","; if (prkdf->usageflags.sign_recover) log_printf ("%ssign_recover", s), s = ","; if (prkdf->usageflags.wrap ) log_printf ("%swrap", s), s = ","; if (prkdf->usageflags.unwrap ) log_printf ("%sunwrap", s), s = ","; if (prkdf->usageflags.verify ) log_printf ("%sverify", s), s = ","; if (prkdf->usageflags.verify_recover) log_printf ("%sverify_recover", s), s = ","; if (prkdf->usageflags.derive ) log_printf ("%sderive", s), s = ","; if (prkdf->usageflags.non_repudiation) log_printf ("%snon_repudiation", s), s = ","; log_printf ("\n"); /* Put it into the list. */ prkdf->next = prkdflist; prkdflist = prkdf; prkdf = NULL; continue; /* Ready. */ parse_error: log_error ("error parsing PrKDF record (%d): %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); if (prkdf) { xfree (prkdf->objid); xfree (prkdf->authid); xfree (prkdf); } err = 0; } /* End looping over all records. */ leave: xfree (buffer); if (err) release_prkdflist (prkdflist); else *result = prkdflist; return err; } /* Read and parse the Certificate Directory Files identified by FID. On success a newlist of CDF object gets stored at RESULT and the caller is then responsible of releasing this list. On error a error code is returned and RESULT won't get changed. */ static gpg_error_t read_ef_cdf (app_t app, unsigned short fid, cdf_object_t *result) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; cdf_object_t cdflist = NULL; int i; if (!fid) return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */ err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen); if (err) return err; p = buffer; n = buflen; /* Loop over the records. We stop as soon as we detect a new record starting with 0x00 or 0xff as these values are commonly used to pad data blocks and are no valid ASN.1 encoding. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; cdf_object_t cdf = NULL; unsigned long ul; const unsigned char *objid; size_t objidlen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing CDF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Skip the commonObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; pp += objlen; nn -= objlen; /* Parse the commonCertificateAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; objid = ppp; objidlen = objlen; } /* Parse the certAttribute. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { errstr = "unsupported reference type"; continue; } nn = objlen; /* Parse the Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element is a non zero path and of even length (FID are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || !objlen || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } /* Create a new CDF list item. */ cdf = xtrycalloc (1, (sizeof *cdf - sizeof(unsigned short) + objlen/2 * sizeof(unsigned short))); if (!cdf) { err = gpg_error_from_errno (errno); goto leave; } cdf->objidlen = objidlen; cdf->objid = xtrymalloc (objidlen); if (!cdf->objid) { err = gpg_error_from_errno (errno); xfree (cdf); goto leave; } memcpy (cdf->objid, objid, objidlen); cdf->pathlen = objlen/2; for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2) cdf->path[i] = ((pp[0] << 8) | pp[1]); if (nn) { /* An index and length follows. */ cdf->have_off = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } cdf->off = ul; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } cdf->len = ul; } log_debug ("CDF %04hX: id=", fid); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); log_printf (" path="); for (i=0; i < cdf->pathlen; i++) log_printf ("%04hX", cdf->path[i]); if (cdf->have_off) log_printf ("[%lu/%lu]", cdf->off, cdf->len); log_printf ("\n"); /* Put it into the list. */ cdf->next = cdflist; cdflist = cdf; cdf = NULL; continue; /* Ready. */ parse_error: log_error ("error parsing CDF record (%d): %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); xfree (cdf); err = 0; } /* End looping over all records. */ leave: xfree (buffer); if (err) release_cdflist (cdflist); else *result = cdflist; return err; } /* SEQUENCE { SEQUENCE { -- CommonObjectAttributes UTF8String 'specific PIN for DS' BIT STRING 0 unused bits '00000011'B } SEQUENCE { -- CommonAuthenticationObjectAttributes OCTET STRING 07 -- iD } [1] { -- typeAttributes SEQUENCE { -- PinAttributes BIT STRING 0 unused bits '0000100000110010'B -- local,initialized,needs-padding -- exchangeRefData ENUMERATED 1 -- ascii-numeric INTEGER 6 -- minLength INTEGER 6 -- storedLength INTEGER 8 -- maxLength [0] 02 -- pinReference GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange SEQUENCE { OCTET STRING 3F 00 40 16 -- path to DF of PIN } } } } */ /* Read and parse an Authentication Object Directory File identified by FID. On success a newlist of AODF objects gets stored at RESULT and the caller is responsible of releasing this list. On error a error code is returned and RESULT won't get changed. */ static gpg_error_t read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; aodf_object_t aodflist = NULL; int i; if (!fid) return gpg_error (GPG_ERR_NO_DATA); /* No authentication objects. */ err = select_and_read_binary (app->slot, fid, "AODF", &buffer, &buflen); if (err) return err; p = buffer; n = buflen; /* FIXME: This shares a LOT of code with read_ef_prkdf! */ /* Loop over the records. We stop as soon as we detect a new record starting with 0x00 or 0xff as these values are commonly used to pad data blocks and are no valid ASN.1 encoding. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; aodf_object_t aodf = NULL; unsigned long ul; const char *s; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing AODF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Allocate memory for a new AODF list item. */ aodf = xtrycalloc (1, sizeof *aodf); if (!aodf) goto no_core; /* Parse the commonObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Search the optional AuthId. We need to skip the optional Label (UTF8STRING) and the optional CommonObjectFlags (BITSTRING). */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; if (tag == TAG_UTF8_STRING) { ppp += objlen; /* Skip the Label. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; } if (tag == TAG_BIT_STRING) { ppp += objlen; /* Skip the CommonObjectFlags. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; } if (tag == TAG_OCTET_STRING && objlen) { aodf->authidlen = objlen; aodf->authid = xtrymalloc (objlen); if (!aodf->authid) goto no_core; memcpy (aodf->authid, ppp, objlen); } no_authid: ; } /* Parse the CommonAuthenticationObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; aodf->objidlen = objlen; aodf->objid = xtrymalloc (objlen); if (!aodf->objid) goto no_core; memcpy (aodf->objid, ppp, objlen); } /* Parse the typeAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* PinAttributes */ else if (class == CLASS_CONTEXT) { switch (tag) { case 0: errstr = "biometric auth types are not supported"; break; case 1: errstr = "authKey auth types are not supported"; break; case 2: errstr = "external auth type are not supported"; break; default: errstr = "unknown privateKeyObject"; break; } goto parse_error; } else { err = gpg_error (GPG_ERR_INV_OBJ); goto parse_error; } nn = objlen; /* PinFlags */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || !objlen || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { unsigned int bits, mask; int unused, full; unused = *pp++; nn--; objlen--; if ((!objlen && unused) || unused/8 > objlen) { err = gpg_error (GPG_ERR_ENCODING_PROBLEM); goto parse_error; } full = objlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* The first octet */ bits = 0; if (objlen) { bits = *pp++; nn--; objlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } if ((bits & 0x80)) /* ASN.1 bit 0. */ aodf->pinflags.case_sensitive = 1; if ((bits & 0x40)) /* ASN.1 bit 1. */ aodf->pinflags.local = 1; if ((bits & 0x20)) aodf->pinflags.change_disabled = 1; if ((bits & 0x10)) aodf->pinflags.unblock_disabled = 1; if ((bits & 0x08)) aodf->pinflags.initialized = 1; if ((bits & 0x04)) aodf->pinflags.needs_padding = 1; if ((bits & 0x02)) aodf->pinflags.unblocking_pin = 1; if ((bits & 0x01)) aodf->pinflags.so_pin = 1; /* The second octet. */ bits = 0; if (objlen) { bits = *pp++; nn--; objlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } if ((bits & 0x80)) aodf->pinflags.disable_allowed = 1; if ((bits & 0x40)) aodf->pinflags.integrity_protected = 1; if ((bits & 0x20)) aodf->pinflags.confidentiality_protected = 1; if ((bits & 0x10)) aodf->pinflags.exchange_ref_data = 1; /* Skip remaining bits. */ pp += objlen; nn -= objlen; } /* PinType */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err && (objlen > sizeof (pin_type_t) || objlen > sizeof (ul))) err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->pintype = ul; /* minLength */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err && objlen > sizeof (ul)) err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->min_length = ul; /* storedLength */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err && objlen > sizeof (ul)) err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->stored_length = ul; /* optional maxLength */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) { if (objlen > sizeof (ul)) { err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); goto parse_error; } for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->max_length = ul; aodf->max_length_valid = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Optional pinReference. */ if (class == CLASS_CONTEXT && tag == 0) { if (objlen > sizeof (ul)) { err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); goto parse_error; } for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->pin_reference = ul; aodf->pin_reference_valid = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Optional padChar. */ if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING) { if (objlen != 1) { errstr = "padChar is not of size(1)"; goto parse_error; } aodf->pad_char = *pp++; nn--; aodf->pad_char_valid = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Skip optional lastPinChange. */ if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME) { pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Optional Path object. */ if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE) { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element is a non zero FID and of even length (FID are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || !objlen || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } aodf->pathlen = objlen/2; aodf->path = xtrymalloc (aodf->pathlen); if (!aodf->path) goto no_core; for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2) aodf->path[i] = ((ppp[0] << 8) | ppp[1]); if (nnn) { /* An index and length follows. */ aodf->have_off = 1; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } aodf->off = ul; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } aodf->len = ul; } } /* Igonore further objects which might be there due to future extensions of pkcs#15. */ ready: log_debug ("AODF %04hX: id=", fid); for (i=0; i < aodf->objidlen; i++) log_printf ("%02X", aodf->objid[i]); if (aodf->authid) { log_printf (" authid="); for (i=0; i < aodf->authidlen; i++) log_printf ("%02X", aodf->authid[i]); } log_printf (" flags="); s = ""; if (aodf->pinflags.case_sensitive) log_printf ("%scase_sensitive", s), s = ","; if (aodf->pinflags.local) log_printf ("%slocal", s), s = ","; if (aodf->pinflags.change_disabled) log_printf ("%schange_disabled", s), s = ","; if (aodf->pinflags.unblock_disabled) log_printf ("%sunblock_disabled", s), s = ","; if (aodf->pinflags.initialized) log_printf ("%sinitialized", s), s = ","; if (aodf->pinflags.needs_padding) log_printf ("%sneeds_padding", s), s = ","; if (aodf->pinflags.unblocking_pin) log_printf ("%sunblocking_pin", s), s = ","; if (aodf->pinflags.so_pin) log_printf ("%sso_pin", s), s = ","; if (aodf->pinflags.disable_allowed) log_printf ("%sdisable_allowed", s), s = ","; if (aodf->pinflags.integrity_protected) log_printf ("%sintegrity_protected", s), s = ","; if (aodf->pinflags.confidentiality_protected) log_printf ("%sconfidentiality_protected", s), s = ","; if (aodf->pinflags.exchange_ref_data) log_printf ("%sexchange_ref_data", s), s = ","; { char numbuf[50]; switch (aodf->pintype) { case PIN_TYPE_BCD: s = "bcd"; break; case PIN_TYPE_ASCII_NUMERIC: s = "ascii-numeric"; break; case PIN_TYPE_UTF8: s = "utf8"; break; case PIN_TYPE_HALF_NIBBLE_BCD: s = "half-nibble-bcd"; break; case PIN_TYPE_ISO9564_1: s = "iso9564-1"; break; default: sprintf (numbuf, "%lu", (unsigned long)aodf->pintype); s = numbuf; } log_printf (" type=%s", s); } log_printf (" min=%lu", aodf->min_length); log_printf (" stored=%lu", aodf->stored_length); if (aodf->max_length_valid) log_printf (" max=%lu", aodf->max_length); if (aodf->pad_char_valid) log_printf (" pad=0x%02x", aodf->pad_char); if (aodf->pin_reference_valid) log_printf (" pinref=0x%02lX", aodf->pin_reference); if (aodf->pathlen) { log_printf (" path="); for (i=0; i < aodf->pathlen; i++) log_printf ("%04hX", aodf->path[i]); if (aodf->have_off) log_printf ("[%lu/%lu]", aodf->off, aodf->len); } log_printf ("\n"); /* Put it into the list. */ aodf->next = aodflist; aodflist = aodf; aodf = NULL; continue; /* Ready. */ no_core: err = gpg_error_from_errno (errno); release_aodf_object (aodf); goto leave; parse_error: log_error ("error parsing AODF record (%d): %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); err = 0; release_aodf_object (aodf); } /* End looping over all records. */ leave: xfree (buffer); if (err) release_aodflist (aodflist); else *result = aodflist; return err; } /* Read and parse the EF(TokenInfo). TokenInfo ::= SEQUENCE { version INTEGER {v1(0)} (v1,...), serialNumber OCTET STRING, manufacturerID Label OPTIONAL, label [0] Label OPTIONAL, tokenflags TokenFlags, seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL, recordInfo [1] RecordInfo OPTIONAL, supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL, ..., issuerId [3] Label OPTIONAL, holderId [4] Label OPTIONAL, lastUpdate [5] LastUpdate OPTIONAL, preferredLanguage PrintableString OPTIONAL -- In accordance with -- IETF RFC 1766 } (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --}) TokenFlags ::= BIT STRING { readonly (0), loginRequired (1), prnGeneration (2), eidCompliant (3) } 5032: 30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T 72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic 65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card. 02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@.... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0 49: SEQUENCE { 2 1: INTEGER 0 5 4: OCTET STRING 05 45 36 9F 11 12: UTF8String 'D-Trust GmbH' 25 20: [0] 'Office identity card' 47 2: BIT STRING : '00000010'B (bit 1) : Error: Spurious zero bits in bitstring. : } */ static gpg_error_t read_ef_tokeninfo (app_t app) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; unsigned long ul; err = select_and_read_binary (app->slot, 0x5032, "TokenInfo", &buffer, &buflen); if (err) return err; p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing TokenInfo: %s\n", gpg_strerror (err)); goto leave; } n = objlen; /* Version. */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*p++) & 0xff; n--; } if (ul) { log_error ("invalid version %lu in TokenInfo\n", ul); err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } /* serialNumber. */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; xfree (app->app_local->serialno); app->app_local->serialno = xtrymalloc (objlen); if (!app->app_local->serialno) { err = gpg_error_from_errno (errno); goto leave; } memcpy (app->app_local->serialno, p, objlen); app->app_local->serialnolen = objlen; log_printhex ("Serialnumber from EF(TokenInfo) is:", p, objlen); leave: xfree (buffer); return err; } /* Get all the basic information from the pkcs#15 card, check the structure and initialize our local context. This is used once at application initialization. */ static gpg_error_t read_p15_info (app_t app) { gpg_error_t err; if (!read_ef_tokeninfo (app)) { /* If we don't have a serial number yet but the TokenInfo provides one, use that. */ if (!app->serialno && app->app_local->serialno) { app->serialno = app->app_local->serialno; app->serialnolen = app->app_local->serialnolen; app->app_local->serialno = NULL; app->app_local->serialnolen = 0; err = app_munge_serialno (app); if (err) return err; } } /* Read the ODF so that we know the location of all directory files. */ /* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */ err = read_ef_odf (app, 0x5031); if (err) return err; /* Read certificate information. */ assert (!app->app_local->certificate_info); assert (!app->app_local->trusted_certificate_info); assert (!app->app_local->useful_certificate_info); err = read_ef_cdf (app, app->app_local->odf.certificates, &app->app_local->certificate_info); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) err = read_ef_cdf (app, app->app_local->odf.trusted_certificates, &app->app_local->trusted_certificate_info); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) err = read_ef_cdf (app, app->app_local->odf.useful_certificates, &app->app_local->useful_certificate_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; /* Read information about private keys. */ assert (!app->app_local->private_key_info); err = read_ef_prkdf (app, app->app_local->odf.private_keys, &app->app_local->private_key_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; /* Read information about authentication objects. */ assert (!app->app_local->auth_object_info); err = read_ef_aodf (app, app->app_local->odf.auth_objects, &app->app_local->auth_object_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; return err; } /* Helper to do_learn_status: Send information about all certificates listed in CERTINFO back. Use CERTTYPE as type of the certificate. */ static gpg_error_t send_certinfo (app_t app, ctrl_t ctrl, const char *certtype, cdf_object_t certinfo) { for (; certinfo; certinfo = certinfo->next) { char *buf, *p; int i; buf = xtrymalloc (9 + certinfo->objidlen*2 + 1); if (!buf) return gpg_error_from_errno (errno); p = stpcpy (buf, "P15"); if (app->app_local->home_df) { sprintf (p, "-%04hX", (app->app_local->home_df & 0xffff)); p += 5; } p = stpcpy (p, "."); for (i=0; i < certinfo->objidlen; i++) { sprintf (p, "%02X", certinfo->objid[i]); p += 2; } send_status_info (ctrl, "CERTINFO", certtype, strlen (certtype), buf, strlen (buf), NULL, (size_t)0); xfree (buf); } return 0; } /* Get the keygrip of the private key object PRKDF. On success the keygrip gets returned in the caller provided 41 byte buffer R_GRIPSTR. */ static gpg_error_t keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr) { gpg_error_t err; cdf_object_t cdf; unsigned char *der; size_t derlen; ksba_cert_t cert; /* FIXME: We should check whether a public key directory file and a matching public key for PRKDF is available. This should make extraction of the key much easier. My current test card doesn't have one, so we can only use the fallback solution bu looking for a matching certificate and extract the key from there. */ /* Look for a matching certificate. A certificate matches if the Id matches the obne of the private key info. */ for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == prkdf->objidlen && !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen)) break; if (!cdf) for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == prkdf->objidlen && !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen)) break; if (!cdf) for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == prkdf->objidlen && !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen)) break; if (!cdf) return gpg_error (GPG_ERR_NOT_FOUND); err = readcert_by_cdf (app, cdf, &der, &derlen); if (err) return err; err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); if (!err) err = app_help_get_keygrip_string (cert, r_gripstr); ksba_cert_release (cert); return err; } /* Helper to do_learn_status: Send information about all known keypairs back. FIXME: much code duplication from send_sertinfo(). */ static gpg_error_t send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo) { gpg_error_t err; for (; keyinfo; keyinfo = keyinfo->next) { char gripstr[40+1]; char *buf, *p; int i, j; buf = xtrymalloc (9 + keyinfo->objidlen*2 + 1); if (!buf) return gpg_error_from_errno (errno); p = stpcpy (buf, "P15"); if (app->app_local->home_df) { sprintf (p, "-%04hX", (app->app_local->home_df & 0xffff)); p += 5; } p = stpcpy (p, "."); for (i=0; i < keyinfo->objidlen; i++) { sprintf (p, "%02X", keyinfo->objid[i]); p += 2; } err = keygripstr_from_prkdf (app, keyinfo, gripstr); if (err) { log_error ("can't get keygrip from "); for (j=0; j < keyinfo->pathlen; j++) log_printf ("%04hX", keyinfo->path[j]); log_printf (": %s\n", gpg_strerror (err)); } else { assert (strlen (gripstr) == 40); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, buf, strlen (buf), NULL, (size_t)0); } xfree (buf); } return 0; } /* This is the handler for the LEARN command. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) { gpg_error_t err; err = send_certinfo (app, ctrl, "100", app->app_local->certificate_info); if (!err) err = send_certinfo (app, ctrl, "101", app->app_local->trusted_certificate_info); if (!err) err = send_certinfo (app, ctrl, "102", app->app_local->useful_certificate_info); if (!err) err = send_keypairinfo (app, ctrl, app->app_local->private_key_info); return err; } /* Read a certifciate using the information in CDF and return the certificate in a newly llocated buffer R_CERT and its length R_CERTLEN. */ static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; unsigned char *buffer = NULL; const unsigned char *p, *save_p; size_t buflen, n; int class, tag, constructed, ndef; size_t totobjlen, objlen, hdrlen; int rootca; int i; *r_cert = NULL; *r_certlen = 0; /* First check whether it has been cached. */ if (cdf->image) { *r_cert = xtrymalloc (cdf->imagelen); if (!*r_cert) return gpg_error_from_errno (errno); memcpy (*r_cert, cdf->image, cdf->imagelen); *r_certlen = cdf->imagelen; return 0; } /* Read the entire file. fixme: This could be optimized by first reading the header to figure out how long the certificate actually is. */ err = select_ef_by_path (app, cdf->path, cdf->pathlen); if (err) goto leave; err = iso7816_read_binary (app->slot, cdf->off, cdf->len, &buffer, &buflen); if (!err && (!buflen || *buffer == 0xff)) err = gpg_error (GPG_ERR_NOT_FOUND); if (err) { log_error ("error reading certificate with Id "); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); log_printf (": %s\n", gpg_strerror (err)); goto leave; } /* Check whether this is really a certificate. */ p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) rootca = 0; else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) rootca = 1; else { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } totobjlen = objlen + hdrlen; assert (totobjlen <= buflen); err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (!rootca && class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) { /* The certificate seems to be contained in a userCertificate container. Skip this and assume the following sequence is the certificate. */ if (n < objlen) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } p += objlen; n -= objlen; save_p = p; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } totobjlen = objlen + hdrlen; assert (save_p + totobjlen <= buffer + buflen); memmove (buffer, save_p, totobjlen); } *r_cert = buffer; buffer = NULL; *r_certlen = totobjlen; /* Try to cache it. */ if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen))) { memcpy (cdf->image, *r_cert, *r_certlen); cdf->imagelen = *r_certlen; } leave: xfree (buffer); return err; } /* Handler for the READCERT command. Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer to be stored at R_CERT and its length at R_CERTLEN. A error code will be returned on failure and R_CERT and R_CERTLEN will be set to NULL/0. */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; cdf_object_t cdf; *r_cert = NULL; *r_certlen = 0; err = cdf_object_from_certid (app, certid, &cdf); if (!err) err =readcert_by_cdf (app, cdf, r_cert, r_certlen); return err; } /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { gpg_error_t err; int i; if (!strcmp (name, "$AUTHKEYID")) { char *buf, *p; prkdf_object_t prkdf; /* We return the ID of the first private keycapable of signing. */ for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->usageflags.sign) break; if (prkdf) { buf = xtrymalloc (9 + prkdf->objidlen*2 + 1); if (!buf) return gpg_error_from_errno (errno); p = stpcpy (buf, "P15"); if (app->app_local->home_df) { sprintf (p, "-%04hX", (app->app_local->home_df & 0xffff)); p += 5; } p = stpcpy (p, "."); for (i=0; i < prkdf->objidlen; i++) { sprintf (p, "%02X", prkdf->objid[i]); p += 2; } send_status_info (ctrl, name, buf, strlen (buf), NULL, 0); xfree (buf); return 0; } } else if (!strcmp (name, "$DISPSERIALNO")) { /* For certain cards we return special IDs. There is no general rule for it so we need to decide case by case. */ if (app->app_local->card_type == CARD_TYPE_BELPIC) { /* The eID card has a card number printed on the fron matter which seems to be a good indication. */ unsigned char *buffer; const unsigned char *p; size_t buflen, n; unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 }; err = select_ef_by_path (app, path, DIM(path) ); if (!err) err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen); if (err) { log_error ("error accessing EF(ID): %s\n", gpg_strerror (err)); return err; } p = find_tlv (buffer, buflen, 1, &n); if (p && n == 12) { char tmp[12+2+1]; memcpy (tmp, p, 3); tmp[3] = '-'; memcpy (tmp+4, p+3, 7); tmp[11] = '-'; memcpy (tmp+12, p+10, 2); tmp[14] = 0; send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0); xfree (buffer); return 0; } xfree (buffer); } } return gpg_error (GPG_ERR_INV_NAME); } /* Micardo cards require special treatment. This is a helper for the crypto functions to manage the security environment. We expect that the key file has already been selected. FID is the one of the selected key. */ static gpg_error_t micardo_mse (app_t app, unsigned short fid) { gpg_error_t err; int recno; unsigned short refdata = 0; int se_num; unsigned char msebuf[10]; /* Read the KeyD file containing extra information on keys. */ err = iso7816_select_file (app->slot, 0x0013, 0, NULL, NULL); if (err) { log_error ("error reading EF_keyD: %s\n", gpg_strerror (err)); return err; } for (recno = 1, se_num = -1; ; recno++) { unsigned char *buffer; size_t buflen; size_t n, nn; const unsigned char *p, *pp; err = iso7816_read_record (app->slot, recno, 1, 0, &buffer, &buflen); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) break; /* ready */ if (err) { log_error ("error reading EF_keyD record: %s\n", gpg_strerror (err)); return err; } log_printhex ("keyD record:", buffer, buflen); p = find_tlv (buffer, buflen, 0x83, &n); if (p && n == 4 && ((p[2]<<8)|p[3]) == fid) { refdata = ((p[0]<<8)|p[1]); /* Locate the SE DO and the there included sec env number. */ p = find_tlv (buffer, buflen, 0x7b, &n); if (p && n) { pp = find_tlv (p, n, 0x80, &nn); if (pp && nn == 1) { se_num = *pp; xfree (buffer); break; /* found. */ } } } xfree (buffer); } if (se_num == -1) { log_error ("CRT for keyfile %04hX not found\n", fid); return gpg_error (GPG_ERR_NOT_FOUND); } /* Restore the security environment to SE_NUM if needed */ if (se_num) { err = iso7816_manage_security_env (app->slot, 0xf3, se_num, NULL, 0); if (err) { log_error ("restoring SE to %d failed: %s\n", se_num, gpg_strerror (err)); return err; } } /* Set the DST reference data. */ msebuf[0] = 0x83; msebuf[1] = 0x03; msebuf[2] = 0x80; msebuf[3] = (refdata >> 8); msebuf[4] = refdata; err = iso7816_manage_security_env (app->slot, 0x41, 0xb6, msebuf, 5); if (err) { log_error ("setting SE to reference file %04hX failed: %s\n", refdata, gpg_strerror (err)); return err; } return 0; } /* Handler for the PKSIGN command. Create the signature and return the allocated result in OUTDATA. If a PIN is required, the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that as the 3rd argument. */ static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; gpg_error_t err; int i; unsigned char data[35]; /* Must be large enough for a SHA-1 digest + the largest OID prefix above. */ prkdf_object_t prkdf; /* The private key object. */ aodf_object_t aodf; /* The associated authentication object. */ int no_data_padding = 0; /* True if the card want the data without padding.*/ int mse_done = 0; /* Set to true if the MSE has been done. */ if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (indatalen != 20 && indatalen != 16 && indatalen != 35) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover ||prkdf->usageflags.non_repudiation)) { log_error ("key %s may not be used for signing\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (!prkdf->authid) { log_error ("no authentication object defined for %s\n", keyidstr); /* fixme: we might want to go ahead and do without PIN verification. */ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); } /* Find the authentication object to this private key object. */ for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) if (aodf->objidlen == prkdf->authidlen && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) break; if (!aodf) { log_error ("authentication object for %s missing\n", keyidstr); return gpg_error (GPG_ERR_INV_CARD); } if (aodf->authid) { log_error ("PIN verification is protected by an " "additional authentication token\n"); return gpg_error (GPG_ERR_BAD_PIN_METHOD); } if (aodf->pinflags.integrity_protected || aodf->pinflags.confidentiality_protected) { log_error ("PIN verification requires unsupported protecion method\n"); return gpg_error (GPG_ERR_BAD_PIN_METHOD); } if (!aodf->stored_length && aodf->pinflags.needs_padding) { log_error ("PIN verification requires padding but no length known\n"); return gpg_error (GPG_ERR_INV_CARD); } /* Select the key file. Note that this may change the security environment thus we do it before PIN verification. */ err = select_ef_by_path (app, prkdf->path, prkdf->pathlen); if (err) { log_error ("error selecting file for key %s: %s\n", keyidstr, gpg_strerror (errno)); return err; } /* Due to the fact that the non-repudiation signature on a BELPIC card requires a ver verify immediately before the DSO we set the MSE before we do the verification. Other cards might allow to do this also but I don't want to break anything, thus we do it only for the BELPIC card here. */ if (app->app_local->card_type == CARD_TYPE_BELPIC) { unsigned char mse[5]; mse[0] = 4; /* Length of the template. */ mse[1] = 0x80; /* Algorithm reference tag. */ mse[2] = 0x02; /* Algorithm: RSASSA-PKCS1-v1.5 using SHA1. */ mse[3] = 0x84; /* Private key reference tag. */ mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82; err = iso7816_manage_security_env (app->slot, 0x41, 0xB6, mse, sizeof mse); no_data_padding = 1; mse_done = 1; } if (err) { log_error ("MSE failed: %s\n", gpg_strerror (err)); return err; } /* Now that we have all the information available, prepare and run the PIN verification.*/ if (1) { char *pinvalue; size_t pinvaluelen; const char *errstr; const char *s; if (prkdf->usageflags.non_repudiation && app->app_local->card_type == CARD_TYPE_BELPIC) err = pincb (pincb_arg, "PIN (qualified signature!)", &pinvalue); else err = pincb (pincb_arg, "PIN", &pinvalue); if (err) { log_info ("PIN callback returned error: %s\n", gpg_strerror (err)); return err; } /* We might need to cope with UTF8 things here. Not sure how min_length etc. are exactly defined, for now we take them as a plain octet count. */ if (strlen (pinvalue) < aodf->min_length) { log_error ("PIN is too short; minimum length is %lu\n", aodf->min_length); err = gpg_error (GPG_ERR_BAD_PIN); } else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length) { /* This would otherwise truncate the PIN silently. */ log_error ("PIN is too large; maximum length is %lu\n", aodf->stored_length); err = gpg_error (GPG_ERR_BAD_PIN); } else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length) { log_error ("PIN is too large; maximum length is %lu\n", aodf->max_length); err = gpg_error (GPG_ERR_BAD_PIN); } if (err) { xfree (pinvalue); return err; } errstr = NULL; err = 0; switch (aodf->pintype) { case PIN_TYPE_BCD: case PIN_TYPE_ASCII_NUMERIC: for (s=pinvalue; digitp (s); s++) ; if (*s) { errstr = "Non-numeric digits found in PIN"; err = gpg_error (GPG_ERR_BAD_PIN); } break; case PIN_TYPE_UTF8: break; case PIN_TYPE_HALF_NIBBLE_BCD: errstr = "PIN type Half-Nibble-BCD is not supported"; break; case PIN_TYPE_ISO9564_1: errstr = "PIN type ISO9564-1 is not supported"; break; default: errstr = "Unknown PIN type"; break; } if (errstr) { log_error ("can't verify PIN: %s\n", errstr); xfree (pinvalue); return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD); } if (aodf->pintype == PIN_TYPE_BCD ) { char *paddedpin; int ndigits; for (ndigits=0, s=pinvalue; *s; ndigits++, s++) ; paddedpin = xtrymalloc (aodf->stored_length+1); if (!paddedpin) { err = gpg_error_from_errno (errno); xfree (pinvalue); return err; } i = 0; paddedpin[i++] = 0x20 | (ndigits & 0x0f); for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 ) paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); if (i < aodf->stored_length && *s) paddedpin[i++] = (((*s - '0') << 4) |((aodf->pad_char_valid?aodf->pad_char:0)&0x0f)); if (aodf->pinflags.needs_padding) while (i < aodf->stored_length) paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0; xfree (pinvalue); pinvalue = paddedpin; pinvaluelen = i; } else if (aodf->pinflags.needs_padding) { char *paddedpin; paddedpin = xtrymalloc (aodf->stored_length+1); if (!paddedpin) { err = gpg_error_from_errno (errno); xfree (pinvalue); return err; } for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++) paddedpin[i] = *s; /* Not sure what padding char to use if none has been set. For now we use 0x00; maybe a space would be better. */ for (; i < aodf->stored_length; i++) paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0; paddedpin[i] = 0; pinvaluelen = i; xfree (pinvalue); pinvalue = paddedpin; } else pinvaluelen = strlen (pinvalue); err = iso7816_verify (app->slot, aodf->pin_reference_valid? aodf->pin_reference : 0, pinvalue, pinvaluelen); xfree (pinvalue); if (err) { log_error ("PIN verification failed: %s\n", gpg_strerror (err)); return err; } log_debug ("PIN verification succeeded\n"); } /* Prepare the DER object from INDATA. */ if (indatalen == 35) { /* Alright, the caller was so kind to send us an already prepared DER object. Check that it is what we want and that it matches the hash algorithm. */ if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) ; else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix, 15)) ; else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data, indata, indatalen); } else { /* Need to prepend the prefix. */ if (hashalgo == GCRY_MD_SHA1) memcpy (data, sha1_prefix, 15); else if (hashalgo == GCRY_MD_RMD160) memcpy (data, rmd160_prefix, 15); else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data+15, indata, indatalen); } /* Manage security environment needs to be weaked for certain cards. */ if (mse_done) err = 0; else if (app->app_local->card_type == CARD_TYPE_TCOS) { /* TCOS creates signatures always using the local key 0. MSE may not be used. */ } else if (app->app_local->card_type == CARD_TYPE_MICARDO) { if (!prkdf->pathlen) err = gpg_error (GPG_ERR_BUG); else err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]); } else if (prkdf->key_reference_valid) { unsigned char mse[3]; mse[0] = 0x84; /* Select asym. key. */ mse[1] = 1; mse[2] = prkdf->key_reference; err = iso7816_manage_security_env (app->slot, 0x41, 0xB6, mse, sizeof mse); } if (err) { log_error ("MSE failed: %s\n", gpg_strerror (err)); return err; } if (no_data_padding) err = iso7816_compute_ds (app->slot, data+15, 20, outdata, outdatalen); else err = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); return err; } /* Handler for the PKAUTH command. This is basically the same as the PKSIGN command but we firstcheck that the requested key is suitable for authentication; that is, it must match the criteria used for the attribute $AUTHKEYID. See do_sign for calling conventions; there is no HASHALGO, though. */ static gpg_error_t do_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; prkdf_object_t prkdf; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!prkdf->usageflags.sign) { log_error ("key %s may not be used for authentication\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } return do_sign (app, keyidstr, GCRY_MD_SHA1, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); } /* Assume that EF(DIR) has been selected. Read its content and figure out the home EF of pkcs#15. Return that home DF or 0 if not found and the value at the address of BELPIC indicates whether it was found by the belpic aid. */ static unsigned short read_home_df (int slot, int *r_belpic) { gpg_error_t err; unsigned char *buffer; const unsigned char *p, *pp; size_t buflen, n, nn; unsigned short result = 0; *r_belpic = 0; err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen); if (err) { log_error ("error reading EF{DIR}: %s\n", gpg_strerror (err)); return 0; } /* FIXME: We need to scan all records. */ p = find_tlv (buffer, buflen, 0x61, &n); if (p && n) { pp = find_tlv (p, n, 0x4f, &nn); if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn)) || (*r_belpic = (nn == sizeof pkcs15be_aid && !memcmp (pp, pkcs15be_aid, nn))))) { pp = find_tlv (p, n, 0x50, &nn); if (pp) /* fixme: Filter log value? */ log_info ("pkcs#15 application label from EF(DIR) is `%.*s'\n", (int)nn, pp); pp = find_tlv (p, n, 0x51, &nn); if (pp && nn == 4 && *pp == 0x3f && !pp[1]) { result = ((pp[2] << 8) | pp[3]); log_info ("pkcs#15 application directory is 0x%04hX\n", result); } } } xfree (buffer); return result; } /* Select the PKCS#15 application on the card in SLOT. */ gpg_error_t app_select_p15 (app_t app) { int slot = app->slot; int rc; unsigned short def_home_df = 0; card_type_t card_type = CARD_TYPE_UNKNOWN; int direct = 0; int is_belpic = 0; rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid, 0); if (rc) { /* Not found: Try to locate it from 2F00. We use direct path selection here because it seems that the Belgian eID card does only allow for that. Many other cards supports this selection method too. Note, that we don't use select_application above for the Belgian card - the call works but it seems that it did not switch to the correct DF. Using the 2f02 just works. */ unsigned short path[1] = { 0x2f00 }; rc = iso7816_select_path (app->slot, path, 1, NULL, NULL); if (!rc) { direct = 1; def_home_df = read_home_df (slot, &is_belpic); if (def_home_df) { path[0] = def_home_df; rc = iso7816_select_path (app->slot, path, 1, NULL, NULL); } } } if (rc) { /* Still not found: Try the default DF. */ def_home_df = 0x5015; rc = iso7816_select_file (slot, def_home_df, 1, NULL, NULL); } if (!rc) { /* Determine the type of the card. The general case is to look it up from the ATR table. For the Belgian eID card we know it instantly from the AID. */ if (is_belpic) { card_type = CARD_TYPE_BELPIC; } else { unsigned char *atr; size_t atrlen; int i; atr = apdu_get_atr (app->slot, &atrlen); if (!atr) rc = gpg_error (GPG_ERR_INV_CARD); else { for (i=0; card_atr_list[i].atrlen; i++) if (card_atr_list[i].atrlen == atrlen && !memcmp (card_atr_list[i].atr, atr, atrlen)) { card_type = card_atr_list[i].type; break; } xfree (atr); } } } if (!rc) { app->apptype = "P15"; app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { rc = gpg_error_from_errno (errno); goto leave; } /* Set the home DF. Note that we currently can't do that if the selection via application ID worked. This will store 0 there instead. FIXME: We either need to figure the home_df via the DIR file or using the return values from the select file APDU. */ app->app_local->home_df = def_home_df; /* Store the card type. FIXME: We might want to put this into the common APP structure. */ app->app_local->card_type = card_type; /* Store whether we may and should use direct path selection. */ app->app_local->direct_path_selection = direct; /* Read basic information and thus check whether this is a real card. */ rc = read_p15_info (app); if (rc) goto leave; /* Special serial number munging. We need to check for a German prototype card right here because we need to access to EF(TokenInfo). We mark such a serial number by the using a prefix of FF0100. */ if (app->serialnolen == 12 && !memcmp (app->serialno, "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12)) { /* This is a German card with a silly serial number. Try to get the serial number from the EF(TokenInfo). . */ unsigned char *p; /* FIXME: actually get it from EF(TokenInfo). */ p = xtrymalloc (3 + app->serialnolen); if (!p) rc = gpg_error (gpg_err_code_from_errno (errno)); else { memcpy (p, "\xff\x01", 3); memcpy (p+3, app->serialno, app->serialnolen); app->serialnolen += 3; xfree (app->serialno); app->serialno = p; } } app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = do_getattr; app->fnc.setattr = NULL; app->fnc.genkey = NULL; app->fnc.sign = do_sign; app->fnc.auth = do_auth; app->fnc.decipher = NULL; app->fnc.change_pin = NULL; app->fnc.check_pin = NULL; leave: if (rc) do_deinit (app); } return rc; } diff --git a/scd/app.c b/scd/app.c index 363e386ce..e3d42054b 100644 --- a/scd/app.c +++ b/scd/app.c @@ -1,861 +1,862 @@ /* app.c - Application selection. * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include # include #include "scdaemon.h" #include "app-common.h" #include "apdu.h" #include "iso7816.h" #include "tlv.h" /* This table is used to keep track of locks on a per reader base. The index into the table is the slot number of the reader. The mutex will be initialized on demand (one of the advantages of a userland threading system). */ static struct { int initialized; pth_mutex_t lock; app_t app; /* Application context in use or NULL. */ app_t last_app; /* Last application object used as this slot or NULL. */ } lock_table[10]; static void deallocate_app (app_t app); /* Lock the reader SLOT. This function shall be used right before calling any of the actual application functions to serialize access to the reader. We do this always even if the reader is not actually used. This allows an actual connection to assume that it never shares a reader (while performing one command). Returns 0 on success; only then the unlock_reader function must be called after returning from the handler. */ static gpg_error_t lock_reader (int slot) { gpg_error_t err; if (slot < 0 || slot >= DIM (lock_table)) return gpg_error (slot<0? GPG_ERR_INV_VALUE : GPG_ERR_RESOURCE_LIMIT); if (!lock_table[slot].initialized) { if (!pth_mutex_init (&lock_table[slot].lock)) { err = gpg_error_from_errno (errno); log_error ("error initializing mutex: %s\n", strerror (errno)); return err; } lock_table[slot].initialized = 1; lock_table[slot].app = NULL; lock_table[slot].last_app = NULL; } if (!pth_mutex_acquire (&lock_table[slot].lock, 0, NULL)) { err = gpg_error_from_errno (errno); log_error ("failed to acquire APP lock for slot %d: %s\n", slot, strerror (errno)); return err; } return 0; } /* Release a lock on the reader. See lock_reader(). */ static void unlock_reader (int slot) { if (slot < 0 || slot >= DIM (lock_table) || !lock_table[slot].initialized) log_bug ("unlock_reader called for invalid slot %d\n", slot); if (!pth_mutex_release (&lock_table[slot].lock)) log_error ("failed to release APP lock for slot %d: %s\n", slot, strerror (errno)); } static void dump_mutex_state (pth_mutex_t *m) { if (!(m->mx_state & PTH_MUTEX_INITIALIZED)) log_printf ("not_initialized"); else if (!(m->mx_state & PTH_MUTEX_LOCKED)) log_printf ("not_locked"); else log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count); } /* This function may be called to print information pertaining to the current state of this module to the log. */ void app_dump_state (void) { int slot; for (slot=0; slot < DIM (lock_table); slot++) if (lock_table[slot].initialized) { log_info ("app_dump_state: slot=%d lock=", slot); dump_mutex_state (&lock_table[slot].lock); if (lock_table[slot].app) { log_printf (" app=%p", lock_table[slot].app); if (lock_table[slot].app->apptype) log_printf (" type=`%s'", lock_table[slot].app->apptype); } if (lock_table[slot].last_app) { log_printf (" lastapp=%p", lock_table[slot].last_app); if (lock_table[slot].last_app->apptype) log_printf (" type=`%s'", lock_table[slot].last_app->apptype); } log_printf ("\n"); } } /* Check wether the application NAME is allowed. This does not mean we have support for it though. */ static int is_app_allowed (const char *name) { strlist_t l; for (l=opt.disabled_applications; l; l = l->next) if (!strcmp (l->d, name)) return 0; /* no */ return 1; /* yes */ } /* This may be called to tell this module about a removed card. */ void application_notify_card_removed (int slot) { app_t app; if (slot < 0 || slot >= DIM (lock_table)) return; /* FIXME: We are ignoring any error value here. */ lock_reader (slot); /* Deallocate a saved application for that slot, so that we won't try to reuse it. If there is no saved application, set a flag so that we won't save the current state. */ app = lock_table[slot].last_app; if (app) { lock_table[slot].last_app = NULL; deallocate_app (app); } unlock_reader (slot); } /* This fucntion is used by the serialno command to check for an application conflict which may appear if the serialno command is used to request a specific application and the connection has already done a select_application. */ gpg_error_t check_application_conflict (ctrl_t ctrl, const char *name) { int slot = ctrl->reader_slot; app_t app; if (slot < 0 || slot >= DIM (lock_table)) return gpg_error (GPG_ERR_INV_VALUE); app = lock_table[slot].initialized ? lock_table[slot].app : NULL; if (app && app->apptype && name) if ( ascii_strcasecmp (app->apptype, name)) return gpg_error (GPG_ERR_CONFLICT); return 0; } /* If called with NAME as NULL, select the best fitting application and return a context; otherwise select the application with NAME and return a context. SLOT identifies the reader device. Returns an error code and stores NULL at R_APP if no application was found or no card is present. */ gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) { gpg_error_t err; app_t app = NULL; unsigned char *result = NULL; size_t resultlen; *r_app = NULL; err = lock_reader (slot); if (err) return err; /* First check whether we already have an application to share. */ app = lock_table[slot].initialized ? lock_table[slot].app : NULL; if (app && name) if (!app->apptype || ascii_strcasecmp (app->apptype, name)) { unlock_reader (slot); if (app->apptype) log_info ("application `%s' in use by reader %d - can't switch\n", app->apptype, slot); return gpg_error (GPG_ERR_CONFLICT); } /* If we don't have an app, check whether we have a saved application for that slot. This is useful so that a card does not get reset even if only one session is using the card - so the PIN cache and other cached data are preserved. */ if (!app && lock_table[slot].initialized && lock_table[slot].last_app) { app = lock_table[slot].last_app; if (!name || (app->apptype && !ascii_strcasecmp (app->apptype, name)) ) { /* Yes, we can reuse this application - either the caller requested an unspecific one or the requested one matches the saved one. */ lock_table[slot].app = app; lock_table[slot].last_app = NULL; } else { /* No, this saved application can't be used - deallocate it. */ lock_table[slot].last_app = NULL; deallocate_app (app); app = NULL; } } /* If we can reuse an application, bump the reference count and return it. */ if (app) { if (app->slot != slot) log_bug ("slot mismatch %d/%d\n", app->slot, slot); app->slot = slot; app->ref_count++; *r_app = app; unlock_reader (slot); return 0; /* Okay: We share that one. */ } /* Need to allocate a new one. */ app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_errno (errno); log_info ("error allocating context: %s\n", gpg_strerror (err)); unlock_reader (slot); return err; } app->slot = slot; /* Fixme: We should now first check whether a card is at all present. */ /* Try to read the GDO file first to get a default serial number. */ err = iso7816_select_file (slot, 0x3F00, 1, NULL, NULL); if (!err) err = iso7816_select_file (slot, 0x2F02, 0, NULL, NULL); if (!err) err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); if (!err) { size_t n; const unsigned char *p; p = find_tlv_unchecked (result, resultlen, 0x5A, &n); if (p) resultlen -= (p-result); if (p && n > resultlen && n == 0x0d && resultlen+1 == n) { /* The object it does not fit into the buffer. This is an invalid encoding (or the buffer is too short. However, I have some test cards with such an invalid encoding and therefore I use this ugly workaround to return something I can further experiment with. */ log_info ("enabling BMI testcard workaround\n"); n--; } if (p && n <= resultlen) { /* The GDO file is pretty short, thus we simply reuse it for storing the serial number. */ memmove (result, p, n); app->serialno = result; app->serialnolen = n; err = app_munge_serialno (app); if (err) goto leave; } else xfree (result); result = NULL; } /* For certain error codes, there is no need to try more. */ if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) goto leave; /* Figure out the application to use. */ err = gpg_error (GPG_ERR_NOT_FOUND); if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) err = app_select_openpgp (app); if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) err = app_select_nks (app); if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) err = app_select_p15 (app); if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) err = app_select_dinsig (app); if (err && name) err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: if (err) { if (name) log_info ("can't select application `%s': %s\n", name, gpg_strerror (err)); else log_info ("no supported card application found: %s\n", gpg_strerror (err)); xfree (app); unlock_reader (slot); return err; } app->initialized = 1; app->ref_count = 1; lock_table[slot].app = app; *r_app = app; unlock_reader (slot); return 0; } /* Deallocate the application. */ static void deallocate_app (app_t app) { if (app->fnc.deinit) { app->fnc.deinit (app); app->fnc.deinit = NULL; } xfree (app->serialno); xfree (app); } /* Free the resources associated with the application APP. APP is allowed to be NULL in which case this is a no-op. Note that we are using reference counting to track the users of the application and actually deferring the deallocation to allow for a later reuse by a new connection. */ void release_application (app_t app) { int slot; if (!app) return; if (app->ref_count < 1) log_bug ("trying to release an already released context\n"); if (--app->ref_count) return; /* Move the reference to the application in the lock table. */ slot = app->slot; /* FIXME: We are ignoring any error value. */ lock_reader (slot); if (lock_table[slot].app != app) { unlock_reader (slot); log_bug ("app mismatch %p/%p\n", app, lock_table[slot].app); deallocate_app (app); return; } if (lock_table[slot].last_app) deallocate_app (lock_table[slot].last_app); lock_table[slot].last_app = lock_table[slot].app; lock_table[slot].app = NULL; unlock_reader (slot); } /* The serial number may need some cosmetics. Do it here. This function shall only be called once after a new serial number has been put into APP->serialno. Prefixes we use: FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. All other serial number not starting with FF are used as they are. */ gpg_error_t app_munge_serialno (app_t app) { if (app->serialnolen && app->serialno[0] == 0xff) { /* The serial number starts with our special prefix. This requires that we put our default prefix "FF0000" in front. */ unsigned char *p = xtrymalloc (app->serialnolen + 3); if (!p) return gpg_error (gpg_err_code_from_errno (errno)); memcpy (p, "\xff\0", 3); memcpy (p+3, app->serialno, app->serialnolen); app->serialnolen += 3; xfree (app->serialno); app->serialno = p; } return 0; } /* Retrieve the serial number and the time of the last update of the card. The serial number is returned as a malloced string (hex encoded) in SERIAL and the time of update is returned in STAMP. If no update time is available the returned value is 0. Caller must free SERIAL unless the function returns an error. If STAMP is not of interest, NULL may be passed. */ gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp) { char *buf, *p; int i; if (!app || !serial) return gpg_error (GPG_ERR_INV_VALUE); *serial = NULL; if (stamp) *stamp = 0; /* not available */ buf = xtrymalloc (app->serialnolen * 2 + 1); if (!buf) return gpg_error_from_errno (errno); for (p=buf, i=0; i < app->serialnolen; p +=2, i++) sprintf (p, "%02X", app->serialno[i]); *p = 0; *serial = buf; return 0; } /* Write out the application specifig status lines for the LEARN command. */ gpg_error_t app_write_learn_status (app_t app, CTRL ctrl) { gpg_error_t err; if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); if (app->apptype) send_status_info (ctrl, "APPTYPE", app->apptype, strlen (app->apptype), NULL, 0); err = lock_reader (app->slot); if (err) return err; err = app->fnc.learn_status (app, ctrl); unlock_reader (app->slot); return err; } /* Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ gpg_error_t app_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { gpg_error_t err; if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readcert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.readcert (app, certid, cert, certlen); unlock_reader (app->slot); return err; } /* Read the key with ID KEYID. On success a canonical encoded S-expression with the public key will get stored at PK and its length (for assertions) at PKLEN; the caller must release that buffer. On error NULL will be stored at PK and PKLEN and an error code returned. This function might not be supported by all applications. */ gpg_error_t app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; if (pk) *pk = NULL; if (pklen) *pklen = 0; if (!app || !keyid || !pk || !pklen) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err= app->fnc.readkey (app, keyid, pk, pklen); unlock_reader (app->slot); return err; } /* Perform a GETATTR operation. */ gpg_error_t app_getattr (app_t app, CTRL ctrl, const char *name) { gpg_error_t err; if (!app || !name || !*name) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_info (ctrl, "APPTYPE", app->apptype, strlen (app->apptype), NULL, 0); return 0; } if (name && !strcmp (name, "SERIALNO")) { char *serial; time_t stamp; int rc; rc = app_get_serial_and_stamp (app, &serial, &stamp); if (rc) return rc; send_status_info (ctrl, "SERIALNO", serial, strlen (serial), NULL, 0); xfree (serial); return 0; } if (!app->fnc.getattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.getattr (app, ctrl, name); unlock_reader (app->slot); return err; } /* Perform a SETATTR operation. */ gpg_error_t app_setattr (app_t app, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { gpg_error_t err; if (!app || !name || !*name || !value) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.setattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); unlock_reader (app->slot); return err; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.sign) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.sign (app, keyidstr, hashalgo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_reader (app->slot); if (opt.verbose) log_info ("operation sign result: %s\n", gpg_strerror (err)); return err; } /* Create the signature using the INTERNAL AUTHENTICATE command and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.auth) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.auth (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_reader (app->slot); if (opt.verbose) log_info ("operation auth result: %s\n", gpg_strerror (err)); return err; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_decipher (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.decipher) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.decipher (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_reader (app->slot); if (opt.verbose) log_info ("operation decipher result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITEKEY operation. */ gpg_error_t app_writekey (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.writekey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.writekey (app, ctrl, keyidstr, flags, pincb, pincb_arg, keydata, keydatalen); unlock_reader (app->slot); if (opt.verbose) log_info ("operation writekey result: %s\n", gpg_strerror (err)); return err; } /* Perform a SETATTR operation. */ gpg_error_t app_genkey (app_t app, CTRL ctrl, const char *keynostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !keynostr || !*keynostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.genkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg); unlock_reader (app->slot); if (opt.verbose) log_info ("operation genkey result: %s\n", gpg_strerror (err)); return err; } /* Perform a GET CHALLENGE operation. This fucntion is special as it directly accesses the card without any application specific wrapper. */ gpg_error_t app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer) { gpg_error_t err; if (!app || !nbytes || !buffer) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); err = lock_reader (app->slot); if (err) return err; err = iso7816_get_challenge (app->slot, nbytes, buffer); unlock_reader (app->slot); return err; } /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t app_change_pin (app_t app, CTRL ctrl, const char *chvnostr, int reset_mode, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !chvnostr || !*chvnostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.change_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg); unlock_reader (app->slot); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); return err; } /* Perform a VERIFY operation without doing anything lese. This may be used to initialze a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t app_check_pin (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.check_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_reader (app->slot); if (err) return err; err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg); unlock_reader (app->slot); if (opt.verbose) log_info ("operation check_pin result: %s\n", gpg_strerror (err)); return err; } diff --git a/scd/atr.c b/scd/atr.c index 6475e83f8..bd5a22621 100644 --- a/scd/atr.c +++ b/scd/atr.c @@ -1,287 +1,288 @@ /* atr.c - ISO 7816 ATR fucntions * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include "scdaemon.h" #include "apdu.h" #include "atr.h" #include "dynload.h" static int const fi_table[16] = { 0, 372, 558, 744, 1116,1488, 1860, -1, -1, 512, 768, 1024, 1536, 2048, -1, -1 }; static int const di_table[16] = { -1, 1, 2, 4, 8, 16, -1, -1, 0, -1, -2, -4, -8, -16, -32, -64}; /* Dump the ATR of the card at SLOT in a human readable format to stream FP. */ int atr_dump (int slot, FILE *fp) { unsigned char *atrbuffer, *atr; size_t atrlen; int have_ta, have_tb, have_tc, have_td; int n_historical; int idx, val; unsigned char chksum; atr = atrbuffer = apdu_get_atr (slot, &atrlen); if (!atr) return gpg_error (GPG_ERR_GENERAL); fprintf (fp, "Info on ATR of length %u at slot %d\n", (unsigned int)atrlen, slot); if (!atrlen) { fprintf (fp, "error: empty ATR\n"); goto bailout; } if (*atr == 0x3b) fputs ("direct convention\n", fp); else if (*atr == 0x3f) fputs ("inverse convention\n", fp); else fprintf (fp,"error: invalid TS character 0x%02x\n", *atr); if (!--atrlen) goto bailout; atr++; chksum = *atr; for (idx=1; idx < atrlen-1; idx++) chksum ^= atr[idx]; have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); n_historical = (*atr & 0x0f); fprintf (fp, "%d historical characters indicated\n", n_historical); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; if (have_ta) { fputs ("TA1: F=", fp); val = fi_table[(*atr >> 4) & 0x0f]; if (!val) fputs ("internal clock", fp); else if (val == -1) fputs ("RFU", fp); else fprintf (fp, "%d", val); fputs (" D=", fp); val = di_table[*atr & 0x0f]; if (!val) fputs ("[impossible value]\n", fp); else if (val == -1) fputs ("RFU\n", fp); else if (val < 0 ) fprintf (fp, "1/%d\n", val); else fprintf (fp, "%d\n", val); if (!--atrlen) goto bailout; atr++; } if (have_tb) { fprintf (fp, "TB1: II=%d PI1=%d%s\n", (*atr >> 5) & 3, *atr & 0x1f, (*atr & 0x80)? " [high bit not cleared]":""); if (!--atrlen) goto bailout; atr++; } if (have_tc) { if (*atr == 255) fputs ("TC1: guard time shortened to 1 etu\n", fp); else fprintf (fp, "TC1: (extra guard time) N=%d\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_td) { have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); fprintf (fp, "TD1: protocol T%d supported\n", *atr & 0x0f); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; } else have_ta = have_tb = have_tc = have_td = 0; if (have_ta) { fprintf (fp, "TA2: (PTS) %stoggle, %splicit, T=%02X\n", (*atr & 0x80)? "no-":"", (*atr & 0x10)? "im": "ex", (*atr & 0x0f)); if ((*atr & 0x60)) fprintf (fp, "note: reserved bits are set (TA2=0x%02X)\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_tb) { fprintf (fp, "TB2: PI2=%d\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_tc) { fprintf (fp, "TC2: PWI=%d\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_td) { have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); fprintf (fp, "TD2: protocol T%d supported\n", *atr & 0x0f); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; } else have_ta = have_tb = have_tc = have_td = 0; for (idx = 3; have_ta || have_tb || have_tc || have_td; idx++) { if (have_ta) { fprintf (fp, "TA%d: IFSC=%d\n", idx, *atr); if (!--atrlen) goto bailout; atr++; } if (have_tb) { fprintf (fp, "TB%d: BWI=%d CWI=%d\n", idx, (*atr >> 4) & 0x0f, *atr & 0x0f); if (!--atrlen) goto bailout; atr++; } if (have_tc) { fprintf (fp, "TC%d: 0x%02X\n", idx, *atr); if (!--atrlen) goto bailout; atr++; } if (have_td) { have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); fprintf (fp, "TD%d: protocol T%d supported\n", idx, *atr & 0x0f); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; } else have_ta = have_tb = have_tc = have_td = 0; } if (n_historical + 1 > atrlen) fputs ("error: ATR shorter than required for historical bytes " "and checksum\n", fp); if (n_historical) { fputs ("Historical:", fp); for (; n_historical && atrlen ; n_historical--, atrlen--, atr++) fprintf (fp, " %02X", *atr); putchar ('\n'); } if (!atrlen) fputs ("error: checksum missing\n", fp); else if (*atr == chksum) fprintf (fp, "TCK: %02X (good)\n", *atr); else fprintf (fp, "TCK: %02X (bad; calculated %02X)\n", *atr, chksum); atrlen--; if (atrlen) fprintf (fp, "error: %u bytes garbage at end of ATR\n", (unsigned int)atrlen ); bailout: xfree (atrbuffer); return 0; } diff --git a/scd/atr.h b/scd/atr.h index 5fdd57457..c70089ca5 100644 --- a/scd/atr.h +++ b/scd/atr.h @@ -1,28 +1,29 @@ /* atr.h - ISO 7816 ATR functions * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef ATR_H #define ATR_H int atr_dump (int slot, FILE *fp); #endif /*ATR_H*/ diff --git a/scd/card-common.h b/scd/card-common.h index cefaf120f..dd7529d5b 100644 --- a/scd/card-common.h +++ b/scd/card-common.h @@ -1,73 +1,74 @@ /* card-common.h - Common declarations for all card types * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef CARD_COMMON_H #define CARD_COMMON_H /* Declaration of private data structure used by card-p15.c */ struct p15private_s; struct card_ctx_s { int reader; /* used reader */ struct sc_context *ctx; struct sc_card *scard; struct sc_pkcs15_card *p15card; /* only if there is a pkcs15 application */ struct p15private_s *p15priv; /* private data used by card-p15.c */ struct { int initialized; /* the card has been initialied and the function pointers may be used. However for unsupported operations the particular function pointer is set to NULL */ int (*enum_keypairs) (CARD card, int idx, unsigned char *keygrip, char **keyid); int (*enum_certs) (CARD card, int idx, char **certid, int *certtype); int (*read_cert) (CARD card, const char *certidstr, unsigned char **cert, size_t *ncert); int (*sign) (CARD card, const char *keyidstr, int hashalgo, int (pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); int (*decipher) (CARD card, const char *keyidstr, int (pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); } fnc; }; /*-- card.c --*/ gpg_error_t map_sc_err (int rc); int card_help_get_keygrip (ksba_cert_t cert, unsigned char *array); /*-- card-15.c --*/ void p15_release_private_data (CARD card); /* constructors */ void card_p15_bind (CARD card); void card_dinsig_bind (CARD card); #endif /*CARD_COMMON_H*/ diff --git a/scd/card-dinsig.c b/scd/card-dinsig.c index df09bfb57..d50d758f2 100644 --- a/scd/card-dinsig.c +++ b/scd/card-dinsig.c @@ -1,258 +1,259 @@ /* card-dinsig.c - German signature law (DINSIG) functions * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* The German signature law and its bylaw (SigG and SigV) is currently used with an interface specification described in DIN V 66291-1. The AID to be used is: 'D27600006601'. The file IDs for certificates utilize the generic format: Cxyz C being the hex digit 'C' (12). x being the service indicator: '0' := SigG conform digital signature. '1' := entity authentication. '2' := key encipherment. '3' := data encipherment. '4' := key agreement. other values are reserved for future use. y being the security environment number using '0' for cards not supporting a SE number. z being the certificate type: '0' := C.CH (base certificate of ard holder) or C.ICC. '1' .. '7' := C.CH (business or professional certificate of card holder. '8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA). 'E' := C.RCA (self certified certificate of the Root-CA). 'F' := reserved. The file IDs used by default are: '1F00' EF.SSD (security service descriptor). [o,o] '2F02' EF.GDO (global data objects) [m,m] 'A000' EF.PROT (signature log). Cyclic file with 20 records of 53 byte. Read and update after user authentication. [o,o] 'B000' EF.PK.RCA.DS (public keys of Root-CA). Size is 512b or size of keys. [m (unless a 'C00E' is present),m] 'B001' EF.PK.CA.DS (public keys of CAs). Size is 512b or size of keys. [o,o] 'C00n' EF.C.CH.DS (digital signature certificate of card holder) with n := 0 .. 7. Size is 2k or size of cert. Read and update allowed after user authentication. [m,m] 'C00m' EF.C.CA.DS (digital signature certificate of CA) with m := 8 .. E. Size is 1k or size of cert. Read always allowed, update after uder authentication. [o,o] 'C100' EF.C.ICC.AUT (AUT certificate of ICC) [o,m] 'C108' EF.C.CA.AUT (AUT certificate of CA) [o,m] 'D000' EF.DM (display message) [-,m] The letters in brackets indicate optional or mandatory files: The first for card terminals under full control and the second for "business" card terminals. FIXME: Needs a lot more explanation. */ #include #include #include #include #include #include #ifdef HAVE_OPENSC #include #include "scdaemon.h" #include #include "card-common.h" static int dinsig_read_cert (CARD card, const char *certidstr, unsigned char **cert, size_t *ncert); /* See card.c for interface description. Frankly we don't do any real enumeration but just check whether the well know files are available. */ static int dinsig_enum_keypairs (CARD card, int idx, unsigned char *keygrip, char **keyid) { int rc; unsigned char *buf; size_t buflen; ksba_cert_t cert; /* fixme: We should locate the application via the EF(DIR) and not assume a Netkey card */ if (!idx) rc = dinsig_read_cert (card, "DINSIG-DF01.C000", &buf, &buflen); else if (idx == 1) rc = dinsig_read_cert (card, "DINSIG-DF01.C200", &buf, &buflen); else rc = -1; if (rc) return rc; rc = ksba_cert_new (&cert); if (rc) { xfree (buf); return rc; } rc = ksba_cert_init_from_mem (cert, buf, buflen); xfree (buf); if (rc) { log_error ("failed to parse the certificate at idx %d: %s\n", idx, gpg_strerror (rc)); ksba_cert_release (cert); return rc; } if (card_help_get_keygrip (cert, keygrip)) { log_error ("failed to calculate the keygrip at index %d\n", idx); ksba_cert_release (cert); return gpg_error (GPG_ERR_CARD); } ksba_cert_release (cert); /* return the iD */ if (keyid) { *keyid = xtrymalloc (17); if (!*keyid) return gpg_error (gpg_err_code_from_errno (errno)); if (!idx) strcpy (*keyid, "DINSIG-DF01.C000"); else strcpy (*keyid, "DINSIG-DF01.C200"); } return 0; } /* See card.c for interface description */ static int dinsig_read_cert (CARD card, const char *certidstr, unsigned char **cert, size_t *ncert) { int rc; struct sc_path path; struct sc_file *file; unsigned char *buf; int buflen; if (!strcmp (certidstr, "DINSIG-DF01.C000")) sc_format_path ("3F00DF01C000", &path); else if (!strcmp (certidstr, "DINSIG-DF01.C200")) sc_format_path ("3F00DF01C200", &path); else return gpg_error (GPG_ERR_INV_ID); rc = sc_select_file (card->scard, &path, &file); if (rc) { log_error ("sc_select_file failed: %s\n", sc_strerror (rc)); return map_sc_err (rc); } if (file->type != SC_FILE_TYPE_WORKING_EF || file->ef_structure != SC_FILE_EF_TRANSPARENT) { log_error ("wrong type or structure of certificate EF\n"); sc_file_free (file); return gpg_error (GPG_ERR_CARD); } if (file->size < 20) /* check against a somewhat arbitrary length */ { log_error ("certificate EF too short\n"); sc_file_free (file); return gpg_error (GPG_ERR_CARD); } buf = xtrymalloc (file->size); if (!buf) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); sc_file_free (file); return tmperr; } rc = sc_read_binary (card->scard, 0, buf, file->size, 0); if (rc >= 0 && rc != file->size) { log_error ("short read on certificate EF\n"); sc_file_free (file); xfree (buf); return gpg_error (GPG_ERR_CARD); } sc_file_free (file); if (rc < 0) { log_error ("error reading certificate EF: %s\n", sc_strerror (rc)); xfree (buf); return map_sc_err (rc); } buflen = rc; /* The object is not a plain certificate but wrapped into id-at userCertificate - fixme: we should check the specs and decided whether libksba should support it */ if (buflen > 9 && buf[0] == 0x30 && buf[4] == 6 && buf[5] == 3 && buf[6] == 0x55 && buf[7] == 4 && buf[8] == 0x24) { /* We have to strip the padding. Although this is a good idea anyway, we have to do it due to a KSBA problem; KSBA does not work correct when the buffer is larger than the ASN.1 structure and the certificates here are padded with FF. So as a workaround we look at the outer structure to get the size of the entire thing and adjust the buflen. We can only do this when there is a 2 byte length field */ size_t seqlen; if (buf[1] == 0x82) { seqlen = ((buf[2] << 8) | buf[3]) + 4; if (seqlen < buflen) buflen = seqlen; } memmove (buf, buf+9, buflen-9); buflen -= 9; } *cert = buf; *ncert = buflen; return 0; } /* Bind our operations to the card */ void card_dinsig_bind (CARD card) { card->fnc.enum_keypairs = dinsig_enum_keypairs; card->fnc.read_cert = dinsig_read_cert; } #endif /*HAVE_OPENSC*/ diff --git a/scd/card-p15.c b/scd/card-p15.c index ae3ef148f..63d537d5a 100644 --- a/scd/card-p15.c +++ b/scd/card-p15.c @@ -1,500 +1,501 @@ /* card-p15.c - PKCS-15 based card access * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #ifdef HAVE_OPENSC #include #include "scdaemon.h" #include #include "card-common.h" struct p15private_s { int n_prkey_rsa_objs; struct sc_pkcs15_object *prkey_rsa_objs[32]; int n_cert_objs; struct sc_pkcs15_object *cert_objs[32]; }; /* Allocate private data. */ static int init_private_data (CARD card) { struct p15private_s *priv; int rc; if (card->p15priv) return 0; /* already done. */ priv = xtrycalloc (1, sizeof *priv); if (!priv) return gpg_error (gpg_err_code_from_errno (errno)); /* OpenSC (0.7.0) is a bit strange in that the get_objects functions tries to be a bit too clever and implicitly does an enumeration which eventually leads to the fact that every call to this fucntion returns one more macthing object. The old code in p15_enum_keypairs assume that it would alwyas return the same numer of objects and used this to figure out what the last object enumerated is. We now do an enum_objects just once and keep it in the private data. */ rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_PRKEY_RSA, priv->prkey_rsa_objs, DIM (priv->prkey_rsa_objs)); if (rc < 0) { log_error ("private keys enumeration failed: %s\n", sc_strerror (rc)); xfree (priv); return gpg_error (GPG_ERR_CARD); } priv->n_prkey_rsa_objs = rc; /* Read all certificate objects. */ rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_CERT_X509, priv->cert_objs, DIM (priv->cert_objs)); if (rc < 0) { log_error ("private keys enumeration failed: %s\n", sc_strerror (rc)); xfree (priv); return gpg_error (GPG_ERR_CARD); } priv->n_cert_objs = rc; card->p15priv = priv; return 0; } /* Release private data used in this module. */ void p15_release_private_data (CARD card) { if (!card->p15priv) return; xfree (card->p15priv); card->p15priv = NULL; } /* See card.c for interface description */ static int p15_enum_keypairs (CARD card, int idx, unsigned char *keygrip, char **keyid) { int rc; struct p15private_s *priv; struct sc_pkcs15_object *tmpobj; int nobjs; struct sc_pkcs15_prkey_info *pinfo; struct sc_pkcs15_cert_info *certinfo; struct sc_pkcs15_cert *certder; ksba_cert_t cert; rc = init_private_data (card); if (rc) return rc; priv = card->p15priv; nobjs = priv->n_prkey_rsa_objs; rc = 0; if (idx >= nobjs) return -1; pinfo = priv->prkey_rsa_objs[idx]->data; /* now we need to read the certificate so that we can calculate the keygrip */ rc = sc_pkcs15_find_cert_by_id (card->p15card, &pinfo->id, &tmpobj); if (rc) { log_info ("certificate for private key %d not found: %s\n", idx, sc_strerror (rc)); /* note, that we return the ID anyway */ rc = gpg_error (GPG_ERR_MISSING_CERT); goto return_keyid; } certinfo = tmpobj->data; rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder); if (rc) { log_info ("failed to read certificate for private key %d: %s\n", idx, sc_strerror (rc)); return gpg_error (GPG_ERR_CARD); } rc = ksba_cert_new (&cert); if (rc) { sc_pkcs15_free_certificate (certder); return rc; } rc = ksba_cert_init_from_mem (cert, certder->data, certder->data_len); sc_pkcs15_free_certificate (certder); if (rc) { log_error ("failed to parse the certificate for private key %d: %s\n", idx, gpg_strerror (rc)); ksba_cert_release (cert); return rc; } if (card_help_get_keygrip (cert, keygrip)) { log_error ("failed to calculate the keygrip of private key %d\n", idx); ksba_cert_release (cert); return gpg_error (GPG_ERR_CARD); } ksba_cert_release (cert); rc = 0; return_keyid: if (keyid) { char *p; int i; *keyid = p = xtrymalloc (9+pinfo->id.len*2+1); if (!*keyid) return gpg_error (gpg_err_code_from_errno (errno)); p = stpcpy (p, "P15-5015."); for (i=0; i < pinfo->id.len; i++, p += 2) sprintf (p, "%02X", pinfo->id.value[i]); *p = 0; } return rc; } /* See card.c for interface description */ static int p15_enum_certs (CARD card, int idx, char **certid, int *type) { int rc; struct p15private_s *priv; struct sc_pkcs15_object *obj; struct sc_pkcs15_cert_info *cinfo; int nobjs; rc = init_private_data (card); if (rc) return rc; priv = card->p15priv; nobjs = priv->n_cert_objs; rc = 0; if (idx >= nobjs) return -1; obj = priv->cert_objs[idx]; cinfo = obj->data; if (certid) { char *p; int i; *certid = p = xtrymalloc (9+cinfo->id.len*2+1); if (!*certid) return gpg_error (gpg_err_code_from_errno (errno)); p = stpcpy (p, "P15-5015."); for (i=0; i < cinfo->id.len; i++, p += 2) sprintf (p, "%02X", cinfo->id.value[i]); *p = 0; } if (type) { if (!obj->df) *type = 0; /* unknown */ else if (obj->df->type == SC_PKCS15_CDF) *type = 100; else if (obj->df->type == SC_PKCS15_CDF_TRUSTED) *type = 101; else if (obj->df->type == SC_PKCS15_CDF_USEFUL) *type = 102; else *type = 0; /* error -> unknown */ } return rc; } static int idstr_to_id (const char *idstr, struct sc_pkcs15_id *id) { const char *s; int n; /* For now we only support the standard DF */ if (strncmp (idstr, "P15-5015.", 9) ) return gpg_error (GPG_ERR_INV_ID); for (s=idstr+9, n=0; hexdigitp (s); s++, n++) ; if (*s || (n&1)) return gpg_error (GPG_ERR_INV_ID); /*invalid or odd number of digits*/ n /= 2; if (!n || n > SC_PKCS15_MAX_ID_SIZE) return gpg_error (GPG_ERR_INV_ID); /* empty or too large */ for (s=idstr+9, n=0; *s; s += 2, n++) id->value[n] = xtoi_2 (s); id->len = n; return 0; } /* See card.c for interface description */ static int p15_read_cert (CARD card, const char *certidstr, unsigned char **cert, size_t *ncert) { struct sc_pkcs15_object *tmpobj; struct sc_pkcs15_id certid; struct sc_pkcs15_cert_info *certinfo; struct sc_pkcs15_cert *certder; int rc; if (!card || !certidstr || !cert || !ncert) return gpg_error (GPG_ERR_INV_VALUE); if (!card->p15card) return gpg_error (GPG_ERR_NO_PKCS15_APP); rc = idstr_to_id (certidstr, &certid); if (rc) return rc; rc = sc_pkcs15_find_cert_by_id (card->p15card, &certid, &tmpobj); if (rc) { log_info ("certificate '%s' not found: %s\n", certidstr, sc_strerror (rc)); return -1; } certinfo = tmpobj->data; rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder); if (rc) { log_info ("failed to read certificate '%s': %s\n", certidstr, sc_strerror (rc)); return gpg_error (GPG_ERR_CARD); } *cert = xtrymalloc (certder->data_len); if (!*cert) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); sc_pkcs15_free_certificate (certder); return tmperr; } memcpy (*cert, certder->data, certder->data_len); *ncert = certder->data_len; sc_pkcs15_free_certificate (certder); return 0; } static int p15_prepare_key (CARD card, const char *keyidstr, int (pincb)(void*, const char *, char **), void *pincb_arg, struct sc_pkcs15_object **r_keyobj) { struct sc_pkcs15_id keyid; struct sc_pkcs15_pin_info *pin; struct sc_pkcs15_object *keyobj, *pinobj; char *pinvalue; int rc; rc = idstr_to_id (keyidstr, &keyid); if (rc) return rc; rc = sc_pkcs15_find_prkey_by_id (card->p15card, &keyid, &keyobj); if (rc < 0) { log_error ("private key not found: %s\n", sc_strerror(rc)); return gpg_error (GPG_ERR_NO_SECKEY); } rc = sc_pkcs15_find_pin_by_auth_id (card->p15card, &keyobj->auth_id, &pinobj); if (rc) { log_error ("failed to find PIN by auth ID: %s\n", sc_strerror (rc)); return gpg_error (GPG_ERR_BAD_PIN_METHOD); } pin = pinobj->data; /* Fixme: pack this into a verification loop */ /* Fixme: we might want to pass pin->min_length and pin->stored_length */ rc = pincb (pincb_arg, pinobj->label, &pinvalue); if (rc) { log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); return rc; } rc = sc_pkcs15_verify_pin (card->p15card, pin, pinvalue, strlen (pinvalue)); xfree (pinvalue); if (rc) { log_info ("PIN verification failed: %s\n", sc_strerror (rc)); return gpg_error (GPG_ERR_BAD_PIN); } /* fixme: check wheter we need to release KEYOBJ in case of an error */ *r_keyobj = keyobj; return 0; } /* See card.c for interface description */ static int p15_sign (CARD card, const char *keyidstr, int hashalgo, int (pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { unsigned int cryptflags; struct sc_pkcs15_object *keyobj; int rc; unsigned char *outbuf = NULL; size_t outbuflen; if (hashalgo != GCRY_MD_SHA1) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj); if (rc) return rc; cryptflags = SC_ALGORITHM_RSA_PAD_PKCS1; outbuflen = 1024; outbuf = xtrymalloc (outbuflen); if (!outbuf) return gpg_error (gpg_err_code_from_errno (errno)); rc = sc_pkcs15_compute_signature (card->p15card, keyobj, cryptflags, indata, indatalen, outbuf, outbuflen ); if (rc < 0) { log_error ("failed to create signature: %s\n", sc_strerror (rc)); rc = gpg_error (GPG_ERR_CARD); } else { *outdatalen = rc; *outdata = outbuf; outbuf = NULL; rc = 0; } xfree (outbuf); return rc; } /* See card.c for description */ static int p15_decipher (CARD card, const char *keyidstr, int (pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { struct sc_pkcs15_object *keyobj; int rc; unsigned char *outbuf = NULL; size_t outbuflen; rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj); if (rc) return rc; if (card && card->scard && card->scard->driver && !strcasecmp (card->scard->driver->short_name, "tcos")) { /* very ugly hack to force the use of a local key. We need this until we have fixed the initialization code for TCOS cards */ struct sc_pkcs15_prkey_info *prkey = keyobj->data; if ( !(prkey->key_reference & 0x80)) { prkey->key_reference |= 0x80; log_debug ("using TCOS hack to force the use of local keys\n"); } if (*keyidstr && keyidstr[strlen(keyidstr)-1] == '6') { prkey->key_reference |= 1; log_debug ("warning: using even more TCOS hacks\n"); } } outbuflen = indatalen < 256? 256 : indatalen; outbuf = xtrymalloc (outbuflen); if (!outbuf) return gpg_error (gpg_err_code_from_errno (errno)); rc = sc_pkcs15_decipher (card->p15card, keyobj, 0, indata, indatalen, outbuf, outbuflen); if (rc < 0) { log_error ("failed to decipher the data: %s\n", sc_strerror (rc)); rc = gpg_error (GPG_ERR_CARD); } else { *outdatalen = rc; *outdata = outbuf; outbuf = NULL; rc = 0; } xfree (outbuf); return rc; } /* Bind our operations to the card */ void card_p15_bind (CARD card) { card->fnc.enum_keypairs = p15_enum_keypairs; card->fnc.enum_certs = p15_enum_certs; card->fnc.read_cert = p15_read_cert; card->fnc.sign = p15_sign; card->fnc.decipher = p15_decipher; } #endif /*HAVE_OPENSC*/ diff --git a/scd/card.c b/scd/card.c index 9ec2a52c5..7a41ab7bb 100644 --- a/scd/card.c +++ b/scd/card.c @@ -1,570 +1,571 @@ /* card.c - SCdaemon card functions * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #ifdef HAVE_OPENSC #include #endif #include "scdaemon.h" #include #include "card-common.h" /* Map the SC error codes to the GNUPG ones */ gpg_error_t map_sc_err (int rc) { gpg_err_code_t e; switch (rc) { case 0: e = 0; break; #ifdef HAVE_OPENSC case SC_ERROR_NOT_SUPPORTED: e = GPG_ERR_NOT_SUPPORTED; break; case SC_ERROR_PKCS15_APP_NOT_FOUND: e = GPG_ERR_NO_PKCS15_APP; break; case SC_ERROR_OUT_OF_MEMORY: e = GPG_ERR_ENOMEM; break; case SC_ERROR_CARD_NOT_PRESENT: e = GPG_ERR_CARD_NOT_PRESENT; break; case SC_ERROR_CARD_REMOVED: e = GPG_ERR_CARD_REMOVED; break; case SC_ERROR_INVALID_CARD: e = GPG_ERR_INV_CARD; break; #endif default: e = GPG_ERR_CARD; break; } /* It does not make much sense to further distingusih the error source between OpenSC and SCD. Thus we use SCD as source here. */ return gpg_err_make (GPG_ERR_SOURCE_SCD, e); } /* Get the keygrip from CERT, return 0 on success */ int card_help_get_keygrip (ksba_cert_t cert, unsigned char *array) { gcry_sexp_t s_pkey; int rc; ksba_sexp_t p; size_t n; p = ksba_cert_get_public_key (cert); if (!p) return -1; /* oops */ n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) return -1; /* libksba did not return a proper S-expression */ rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); xfree (p); if (rc) return -1; /* can't parse that S-expression */ array = gcry_pk_get_keygrip (s_pkey, array); gcry_sexp_release (s_pkey); if (!array) return -1; /* failed to calculate the keygrip */ return 0; } /* Create a new context for the card and figures out some basic information of the card. Detects whether a PKCS_15 application is stored. Common errors: GPG_ERR_CARD_NOT_PRESENT */ int card_open (CARD *rcard) { #ifdef HAVE_OPENSC CARD card; int rc; if (opt.disable_opensc) return gpg_error (GPG_ERR_NOT_SUPPORTED); card = xtrycalloc (1, sizeof *card); if (!card) return gpg_error (gpg_err_code_from_errno (errno)); card->reader = 0; rc = sc_establish_context (&card->ctx, "scdaemon"); if (rc) { log_error ("failed to establish SC context: %s\n", sc_strerror (rc)); rc = map_sc_err (rc); goto leave; } if (card->reader >= card->ctx->reader_count) { log_error ("no card reader available\n"); rc = gpg_error (GPG_ERR_CARD); goto leave; } card->ctx->error_file = log_get_stream (); card->ctx->debug = opt.debug_sc; card->ctx->debug_file = log_get_stream (); if (sc_detect_card_presence (card->ctx->reader[card->reader], 0) != 1) { rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT); goto leave; } rc = sc_connect_card (card->ctx->reader[card->reader], 0, &card->scard); if (rc) { log_error ("failed to connect card in reader %d: %s\n", card->reader, sc_strerror (rc)); rc = map_sc_err (rc); goto leave; } if (opt.verbose) log_info ("connected to card in reader %d using driver `%s'\n", card->reader, card->scard->driver->name); rc = sc_lock (card->scard); if (rc) { log_error ("can't lock card in reader %d: %s\n", card->reader, sc_strerror (rc)); rc = map_sc_err (rc); goto leave; } leave: if (rc) card_close (card); else *rcard = card; return rc; #else return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif } /* Close a card and release all resources */ void card_close (CARD card) { if (card) { #ifdef HAVE_OPENSC if (card->p15card) { sc_pkcs15_unbind (card->p15card); card->p15card = NULL; } if (card->p15priv) p15_release_private_data (card); if (card->scard) { sc_unlock (card->scard); sc_disconnect_card (card->scard, 0); card->scard = NULL; } if (card->ctx) { sc_release_context (card->ctx); card->ctx = NULL; } #endif xfree (card); } } /* Locate a simple TLV encoded data object in BUFFER of LENGTH and return a pointer to value as well as its length in NBYTES. Return NULL if it was not found. Note, that the function does not check whether the value fits into the provided buffer. */ #ifdef HAVE_OPENSC static const char * find_simple_tlv (const unsigned char *buffer, size_t length, int tag, size_t *nbytes) { const char *s = buffer; size_t n = length; size_t len; for (;;) { buffer = s; if (n < 2) return NULL; /* buffer too short for tag and length. */ len = s[1]; s += 2; n -= 2; if (len == 255) { if (n < 2) return NULL; /* we expected 2 more bytes with the length. */ len = (s[0] << 8) | s[1]; s += 2; n -= 2; } if (*buffer == tag) { *nbytes = len; return s; } if (len > n) return NULL; /* buffer too short to skip to the next tag. */ s += len; n -= len; } } #endif /*HAVE_OPENSC*/ /* Find the ICC Serial Number within the provided BUFFER of LENGTH (which should contain the GDO file) and return it as a hex encoded string and allocated string in SERIAL. Return an error code when the ICCSN was not found. */ #ifdef HAVE_OPENSC static int find_iccsn (const unsigned char *buffer, size_t length, char **serial) { size_t n; const unsigned char *s; char *p; s = find_simple_tlv (buffer, length, 0x5A, &n); if (!s) return gpg_error (GPG_ERR_CARD); length -= s - buffer; if (n > length) { /* Oops, it does not fit into the buffer. This is an invalid encoding (or the buffer is too short. However, I have some test cards with such an invalid encoding and therefore I use this ugly workaround to return something I can further experiment with. */ if (n == 0x0D && length+1 == n) { log_debug ("enabling BMI testcard workaround\n"); n--; } else return gpg_error (GPG_ERR_CARD); /* Bad encoding; does not fit into buffer. */ } if (!n) return gpg_error (GPG_ERR_CARD); /* Well, that is too short. */ *serial = p = xtrymalloc (2*n+1); if (!*serial) return gpg_error (gpg_err_code_from_errno (errno)); for (; n; n--, p += 2, s++) sprintf (p, "%02X", *s); *p = 0; return 0; } #endif /*HAVE_OPENSC*/ /* Retrieve the serial number and the time of the last update of the card. The serial number is returned as a malloced string (hex encoded) in SERIAL and the time of update is returned in STAMP. If no update time is available the returned value is 0. The serial is mandatory for a PKCS_15 application and an error will be returned if this value is not availbale. For non-PKCS-15 cards a serial number is constructed by other means. Caller must free SERIAL unless the function returns an error. */ int card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp) { #ifdef HAVE_OPENSC int rc; struct sc_path path; struct sc_file *file; unsigned char buf[256]; int buflen; #endif if (!card || !serial || !stamp) return gpg_error (GPG_ERR_INV_VALUE); *serial = NULL; *stamp = 0; /* not available */ #ifdef HAVE_OPENSC if (!card->fnc.initialized) { card->fnc.initialized = 1; /* The first use of this card tries to figure out the type of the card and sets up the function pointers. */ rc = sc_pkcs15_bind (card->scard, &card->p15card); if (rc) { if (rc != SC_ERROR_PKCS15_APP_NOT_FOUND) log_error ("binding of existing PKCS-15 failed in reader %d: %s\n", card->reader, sc_strerror (rc)); card->p15card = NULL; rc = 0; } if (card->p15card) card_p15_bind (card); card->fnc.initialized = 1; } /* We should lookup the iso 7812-1 and 8583-3 - argh ISO practice is suppressing innovation - IETF rules! So we always get the serialnumber from the 2F02 GDO file. */ /* FIXME: in case we can't parse the 2F02 EF and we have a P15 card, we should get the serial number from the respective P15 file */ sc_format_path ("3F002F02", &path); rc = sc_select_file (card->scard, &path, &file); if (rc) { log_error ("sc_select_file failed: %s\n", sc_strerror (rc)); return gpg_error (GPG_ERR_CARD); } if (file->type != SC_FILE_TYPE_WORKING_EF || file->ef_structure != SC_FILE_EF_TRANSPARENT) { log_error ("wrong type or structure of GDO file\n"); sc_file_free (file); return gpg_error (GPG_ERR_CARD); } if (!file->size || file->size >= DIM(buf) ) { /* FIXME: Use a real parser */ log_error ("unsupported size of GDO file (%d)\n", file->size); sc_file_free (file); return gpg_error (GPG_ERR_CARD); } buflen = file->size; rc = sc_read_binary (card->scard, 0, buf, buflen, 0); sc_file_free (file); if (rc < 0) { log_error ("error reading GDO file: %s\n", sc_strerror (rc)); return gpg_error (GPG_ERR_CARD); } if (rc != buflen) { log_error ("short read on GDO file\n"); return gpg_error (GPG_ERR_CARD); } rc = find_iccsn (buf, buflen, serial); if (gpg_err_code (rc) == GPG_ERR_CARD) log_error ("invalid structure of GDO file\n"); if (!rc && card->p15card && !strcmp (*serial, "D27600000000000000000000")) { /* This is a German card with a silly serial number. Try to get the serial number from the EF(TokenInfo). We indicate such a serial number by the using the prefix: "FF0100". */ const char *efser = card->p15card->serial_number; char *p; if (!efser) efser = ""; xfree (*serial); *serial = NULL; p = xtrymalloc (strlen (efser) + 7); if (!p) rc = gpg_error (gpg_err_code_from_errno (errno)); else { strcpy (p, "FF0100"); strcpy (p+6, efser); *serial = p; } } else if (!rc && **serial == 'F' && (*serial)[1] == 'F') { /* The serial number starts with our special prefix. This requires that we put our default prefix "FF0000" in front. */ char *p = xtrymalloc (strlen (*serial) + 7); if (!p) { xfree (*serial); *serial = NULL; rc = gpg_error (gpg_err_code_from_errno (errno)); } else { strcpy (p, "FF0000"); strcpy (p+6, *serial); xfree (*serial); *serial = p; } } return rc; #else return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif } /* Enumerate all keypairs on the card and return the Keygrip as well as the internal identification of the key. KEYGRIP must be a caller provided buffer with a size of 20 bytes which will receive the KEYGRIP of the keypair. If KEYID is not NULL, it returns the ID field of the key in allocated memory; this is a string without spaces. The function returns -1 when all keys have been enumerated. Note that the error GPG_ERR_MISSING_CERTIFICATE may be returned if there is just the private key but no public key (ie.e a certificate) available. Applications might want to continue enumerating after this error.*/ int card_enum_keypairs (CARD card, int idx, unsigned char *keygrip, char **keyid) { int rc; if (keyid) *keyid = NULL; if (!card || !keygrip) return gpg_error (GPG_ERR_INV_VALUE); if (idx < 0) return gpg_error (GPG_ERR_INV_INDEX); if (!card->fnc.initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!card->fnc.enum_keypairs) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); rc = card->fnc.enum_keypairs (card, idx, keygrip, keyid); if (opt.verbose) log_info ("card operation enum_keypairs result: %s\n", gpg_strerror (rc)); return rc; } /* Enumerate all trusted certificates available on the card, return their ID in CERT and the type in CERTTYPE. Types of certificates are: 0 := Unknown 100 := Regular X.509 cert 101 := Trusted X.509 cert 102 := Useful X.509 cert 110 := Root CA cert (DINSIG) */ int card_enum_certs (CARD card, int idx, char **certid, int *certtype) { int rc; if (certid) *certid = NULL; if (!card) return gpg_error (GPG_ERR_INV_VALUE); if (idx < 0) return gpg_error (GPG_ERR_INV_INDEX); if (!card->fnc.initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!card->fnc.enum_certs) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); rc = card->fnc.enum_certs (card, idx, certid, certtype); if (opt.verbose) log_info ("card operation enum_certs result: %s\n", gpg_strerror (rc)); return rc; } /* Read the certificate identified by CERTIDSTR which is the hexadecimal encoded ID of the certificate, prefixed with the string "3F005015.". The certificate is return in DER encoded form in CERT and NCERT. */ int card_read_cert (CARD card, const char *certidstr, unsigned char **cert, size_t *ncert) { int rc; if (!card || !certidstr || !cert || !ncert) return gpg_error (GPG_ERR_INV_VALUE); if (!card->fnc.initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!card->fnc.read_cert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); rc = card->fnc.read_cert (card, certidstr, cert, ncert); if (opt.verbose) log_info ("card operation read_cert result: %s\n", gpg_strerror (rc)); return rc; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ int card_sign (CARD card, const char *keyidstr, int hashalgo, int (pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { int rc; if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!card->fnc.initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!card->fnc.sign) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); rc = card->fnc.sign (card, keyidstr, hashalgo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); if (opt.verbose) log_info ("card operation sign result: %s\n", gpg_strerror (rc)); return rc; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ int card_decipher (CARD card, const char *keyidstr, int (pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { int rc; if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!card->fnc.initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!card->fnc.decipher) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); rc = card->fnc.decipher (card, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); if (opt.verbose) log_info ("card operation decipher result: %s\n", gpg_strerror (rc)); return rc; } diff --git a/scd/command.c b/scd/command.c index 2ed685587..4629d9edf 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1,1786 +1,1787 @@ /* command.c - SCdaemon command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #ifdef USE_GNU_PTH # include #endif #include #include "scdaemon.h" #include #include "app-common.h" #include "apdu.h" /* Required for apdu_*_reader (). */ /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ #define MAXLEN_PIN 100 /* Maximum allowed size of key data as used in inquiries. */ #define MAXLEN_KEYDATA 4096 #define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) /* Macro to flag a removed card. */ #define TEST_CARD_REMOVAL(c,r) \ do { \ int _r = (r); \ if (gpg_err_code (_r) == GPG_ERR_CARD_NOT_PRESENT \ || gpg_err_code (_r) == GPG_ERR_CARD_REMOVED) \ update_card_removed ((c)->reader_slot, 1); \ } while (0) #define IS_LOCKED(c) \ (locked_session && locked_session != (c)->server_local \ && (c)->reader_slot != -1 && locked_session->ctrl_backlink \ && (c)->reader_slot == locked_session->ctrl_backlink->reader_slot) /* This structure is used to keep track of open readers (slots). */ struct slot_status_s { int valid; /* True if the other objects are valid. */ int slot; /* Slot number of the reader or -1 if not open. */ int reset_failed; /* A reset failed. */ int any; /* Flag indicating whether any status check has been done. This is set once to indicate that the status tracking for the slot has been initialized. */ unsigned int status; /* Last status of the slot. */ unsigned int changed; /* Last change counter of teh slot. */ }; /* Data used to associate an Assuan context with local server data. This object describes the local properties of one session. */ struct server_local_s { /* We keep a list of all active sessions with the anchor at SESSION_LIST (see below). This field is used for linking. */ struct server_local_s *next_session; /* This object is usually assigned to a CTRL object (which is globally visible). While enumerating all sessions we sometimes need to access data of the CTRL object; thus we keep a backpointer here. */ ctrl_t ctrl_backlink; /* The Assuan context used by this session/server. */ assuan_context_t assuan_ctx; int event_signal; /* Or 0 if not used. */ /* True if the card has been removed and a reset is required to continue operation. */ int card_removed; }; /* The table with information on all used slots. */ static struct slot_status_s slot_table[10]; /* To keep track of all running sessions, we link all active server contexts and the anchor in this variable. */ static struct server_local_s *session_list; /* If a session has been locked we store a link to its server object in this variable. */ static struct server_local_s *locked_session; /* While doing a reset we need to make sure that the ticker does not call scd_update_reader_status_file while we are using it. */ static pth_mutex_t status_file_update_lock = PTH_MUTEX_INIT; /*-- Local prototypes --*/ static void update_reader_status_file (void); /* Update the CARD_REMOVED element of all sessions using the reader given by SLOT to VALUE */ static void update_card_removed (int slot, int value) { struct server_local_s *sl; for (sl=session_list; sl; sl = sl->next_session) if (sl->ctrl_backlink && sl->ctrl_backlink->reader_slot == slot) { sl->card_removed = value; } if (value) application_notify_card_removed (slot); } /* 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))); } /* Convert the STRING into a newly allocated buffer while translating the hex numbers. Stops at the first invalid character. Blanks and colons are allowed to separate the hex digits. Returns NULL on error or a newly malloced buffer and its length in LENGTH. */ static unsigned char * hex_to_buffer (const char *string, size_t *r_length) { unsigned char *buffer; const char *s; size_t n; buffer = xtrymalloc (strlen (string)+1); if (!buffer) return NULL; for (s=string, n=0; *s; s++) { if (spacep (s) || *s == ':') continue; if (hexdigitp (s) && hexdigitp (s+1)) { buffer[n++] = xtoi_2 (s); s++; } else break; } *r_length = n; return buffer; } /* Reset the card and free the application context. With SEND_RESET set to true actually send a RESET to the reader. */ static void do_reset (ctrl_t ctrl, int send_reset) { int slot = ctrl->reader_slot; if (!(slot == -1 || (slot >= 0 && slot < DIM(slot_table)))) BUG (); if (ctrl->app_ctx) { release_application (ctrl->app_ctx); ctrl->app_ctx = NULL; } if (slot != -1 && send_reset && !IS_LOCKED (ctrl) ) { if (apdu_reset (slot)) { slot_table[slot].reset_failed = 1; } } ctrl->reader_slot = -1; /* If we hold a lock, unlock now. */ if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESET\n"); } /* Reset card removed flag for the current reader. We need to take the lock here so that the ticker thread won't concurrently try to update the file. Note that the update function will set the card removed flag and we will later reset it - not a particualar nice way of implementing it but it works. */ if (!pth_mutex_acquire (&status_file_update_lock, 0, NULL)) { log_error ("failed to acquire status_fle_update lock\n"); return; } update_reader_status_file (); update_card_removed (slot, 0); if (!pth_mutex_release (&status_file_update_lock)) log_error ("failed to release status_file_update lock\n"); } static void reset_notify (assuan_context_t ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); do_reset (ctrl, 1); } static int option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "event-signal")) { /* A value of 0 is allowed to reset the event signal. */ int i = *value? atoi (value) : -1; if (i < 0) return ASSUAN_Parameter_Error; ctrl->server_local->event_signal = i; } return 0; } /* Return the slot of the current reader or open the reader if no other sessions are using a reader. Note, that we currently support only one reader but most of the code (except for this function) should be able to cope with several readers. */ static int get_reader_slot (void) { struct slot_status_s *ss; ss = &slot_table[0]; /* One reader for now. */ /* Initialize the item if needed. */ if (!ss->valid) { ss->slot = -1; ss->valid = 1; } /* Try to open the reader. */ if (ss->slot == -1) ss->slot = apdu_open_reader (opt.reader_port); return ss->slot; } /* If the card has not yet been opened, do it. Note that this function returns an Assuan error, so don't map the error a second time */ static assuan_error_t open_card (ctrl_t ctrl, const char *apptype) { gpg_error_t err; int slot; /* If we ever got a card not present error code, return that. Only the SERIALNO command and a reset are able to clear from that state. */ if (ctrl->server_local->card_removed) return map_to_assuan_status (gpg_error (GPG_ERR_CARD_REMOVED)); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if (ctrl->app_ctx) { /* Already initialized for one specific application. Need to check that the client didn't requested a specific application different from the one in use. */ return check_application_conflict (ctrl, apptype); } if (ctrl->reader_slot != -1) slot = ctrl->reader_slot; else slot = get_reader_slot (); ctrl->reader_slot = slot; if (slot == -1) err = gpg_error (GPG_ERR_CARD); else err = select_application (ctrl, slot, apptype, &ctrl->app_ctx); TEST_CARD_REMOVAL (ctrl, err); return map_to_assuan_status (err); } /* Do the percent and plus/space unescaping in place and return the length of the valid buffer. */ static size_t percent_plus_unescape (unsigned char *string) { unsigned char *p = string; size_t n = 0; while (*string) { if (*string == '%' && string[1] && string[2]) { string++; *p++ = xtoi_2 (string); n++; string+= 2; } else if (*string == '+') { *p++ = ' '; n++; string++; } else { *p++ = *string++; n++; } } return n; } /* SERIALNO [APPTYPE] Return the serial number of the card using a status reponse. This functon should be used to check for the presence of a card. If APPTYPE is given, an application of that type is selected and an error is returned if the application is not supported or available. The default is to auto-select the application using a hardwired preference system. Note, that a future extension to this function may allow to specify a list and order of applications to try. This function is special in that it can be used to reset the card. Most other functions will return an error when a card change has been detected and the use of this function is therefore required. Background: We want to keep the client clear of handling card changes between operations; i.e. the client can assume that all operations are done on the same card unless he calls this function. */ static int cmd_serialno (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *serial_and_stamp; char *serial; time_t stamp; /* Clear the remove flag so that the open_card is able to reread it. */ if (ctrl->server_local->card_removed) { if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); do_reset (ctrl, 1); } if ((rc = open_card (ctrl, *line? line:NULL))) return rc; rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp); if (rc) return map_to_assuan_status (rc); rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); xfree (serial); if (rc < 0) return ASSUAN_Out_Of_Core; rc = 0; assuan_write_status (ctx, "SERIALNO", serial_and_stamp); free (serial_and_stamp); return 0; } /* LEARN [--force] Learn all useful information of the currently inserted card. When used without the force options, the command might do an INQUIRE like this: INQUIRE KNOWNCARDP The client should just send an "END" if the processing should go on or a "CANCEL" to force the function to terminate with a Cancel error message. The response of this command is a list of status lines formatted as this: S APPTYPE This returns the type of the application, currently the strings: P15 = PKCS-15 structure used DINSIG = DIN SIG OPENPGP = OpenPGP card are implemented. These strings are aliases for the AID S KEYPAIRINFO If there is no certificate yet stored on the card a single "X" is returned as the keygrip. In addition to the keypair info, information about all certificates stored on the card is also returned: S CERTINFO Where CERTTYPE is a number indicating the type of certificate: 0 := Unknown 100 := Regular X.509 cert 101 := Trusted X.509 cert 102 := Useful X.509 cert 110 := Root CA cert (DINSIG) For certain cards, more information will be returned: S KEY-FPR For OpenPGP cards this returns the stored fingerprints of the keys. This can be used check whether a key is available on the card. NO may be 1, 2 or 3. S CA-FPR Similar to above, these are the fingerprints of keys assumed to be ultimately trusted. S DISP-NAME The name of the card holder as stored on the card; percent escaping takes place, spaces are encoded as '+' S PUBKEY-URL The URL to be used for locating the entire public key. Note, that this function may be even be used on a locked card. */ static int cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if ((rc = open_card (ctrl, NULL))) return rc; /* Unless the force option is used we try a shortcut by identifying the card using a serial number and inquiring the client with that. The client may choose to cancel the operation if he already knows about this card */ { char *serial_and_stamp; char *serial; time_t stamp; rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp); if (rc) return map_to_assuan_status (rc); rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); xfree (serial); if (rc < 0) return ASSUAN_Out_Of_Core; rc = 0; assuan_write_status (ctx, "SERIALNO", serial_and_stamp); if (!has_option (line, "--force")) { char *command; rc = asprintf (&command, "KNOWNCARDP %s", serial_and_stamp); if (rc < 0) { free (serial_and_stamp); return ASSUAN_Out_Of_Core; } rc = 0; rc = assuan_inquire (ctx, command, NULL, NULL, 0); free (command); /* (must use standard free here) */ if (rc) { if (rc != ASSUAN_Canceled) log_error ("inquire KNOWNCARDP failed: %s\n", assuan_strerror (rc)); free (serial_and_stamp); return rc; } /* not canceled, so we have to proceeed */ } free (serial_and_stamp); } /* Let the application print out its collection of useful status information. */ if (!rc) rc = app_write_learn_status (ctrl->app_ctx, ctrl); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* READCERT Note, that this function may even be used on a locked card. */ static int cmd_readcert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert; size_t ncert; if ((rc = open_card (ctrl, NULL))) return rc; line = xstrdup (line); /* Need a copy of the line. */ rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); xfree (line); line = NULL; if (!rc) { rc = assuan_send_data (ctx, cert, ncert); xfree (cert); if (rc) return rc; } TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* READKEY Return the public key for the given cert or key ID as an standard S-Expression. Note, that this function may even be used on a locked card. */ static int cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert = NULL; size_t ncert, n; ksba_cert_t kc = NULL; ksba_sexp_t p; unsigned char *pk; size_t pklen; if ((rc = open_card (ctrl, NULL))) return rc; line = xstrdup (line); /* Need a copy of the line. */ /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ rc = app_readkey (ctrl->app_ctx, line, &pk, &pklen); if (!rc) { /* Yeah, got that key - send it back. */ rc = assuan_send_data (ctx, pk, pklen); xfree (pk); rc = map_assuan_err (rc); xfree (line); line = NULL; goto leave; } if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION) log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); else { rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); } xfree (line); line = NULL; if (rc) goto leave; rc = ksba_cert_new (&kc); if (rc) { xfree (cert); goto leave; } rc = ksba_cert_init_from_mem (kc, cert, ncert); if (rc) { log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); goto leave; } p = ksba_cert_get_public_key (kc); if (!p) { rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } n = gcry_sexp_canon_len (p, 0, NULL, NULL); rc = assuan_send_data (ctx, p, n); rc = map_assuan_err (rc); xfree (p); leave: ksba_cert_release (kc); xfree (cert); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* SETDATA The client should use this command to tell us the data he want to sign. */ static int cmd_setdata (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int n; char *p; unsigned char *buf; if (locked_session && locked_session != ctrl->server_local) return gpg_error (GPG_ERR_LOCKED); /* Parse the hexstring. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p) return set_error (Parameter_Error, "invalid hexstring"); if (!n) return set_error (Parameter_Error, "no data given"); if ((n&1)) return set_error (Parameter_Error, "odd number of digits"); n /= 2; buf = xtrymalloc (n); if (!buf) return ASSUAN_Out_Of_Core; ctrl->in_data.value = buf; ctrl->in_data.valuelen = n; for (p=line, n=0; n < ctrl->in_data.valuelen; p += 2, n++) buf[n] = xtoi_2 (p); return 0; } static gpg_error_t pin_cb (void *opaque, const char *info, char **retstr) { assuan_context_t ctx = opaque; char *command; int rc; unsigned char *value; size_t valuelen; *retstr = NULL; log_debug ("asking for PIN '%s'\n", info); rc = asprintf (&command, "NEEDPIN %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); /* Fixme: Write an inquire function which returns the result in secure memory and check all further handling of the PIN. */ rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); free (command); if (rc) return map_assuan_err (rc); if (!valuelen || value[valuelen-1]) { /* We require that the returned value is an UTF-8 string */ xfree (value); return gpg_error (GPG_ERR_INV_RESPONSE); } *retstr = (char*)value; return 0; } /* PKSIGN [--hash=[rmd160|sha1|md5]] The --hash option is optional; the default is SHA1. */ static int cmd_pksign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; int hash_algo; if (has_option (line, "--hash=rmd160")) hash_algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=sha1")) hash_algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=md5")) hash_algo = GCRY_MD_MD5; else if (!strstr (line, "--")) hash_algo = GCRY_MD_SHA1; else return set_error (Parameter_Error, "invalid hash algorithm"); /* Skip over options. */ while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((rc = open_card (ctrl, NULL))) return rc; /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return ASSUAN_Out_Of_Core; rc = app_sign (ctrl->app_ctx, keyidstr, hash_algo, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("card_sign failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* PKAUTH */ static int cmd_pkauth (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((rc = open_card (ctrl, NULL))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return ASSUAN_Out_Of_Core; rc = app_auth (ctrl->app_ctx, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_auth_sign failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* PKDECRYPT */ static int cmd_pkdecrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((rc = open_card (ctrl, NULL))) return rc; keyidstr = xtrystrdup (line); if (!keyidstr) return ASSUAN_Out_Of_Core; rc = app_decipher (ctrl->app_ctx, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("card_create_signature failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* GETATTR This command is used to retrieve data from a smartcard. The allowed names depend on the currently selected smartcard application. NAME must be percent and '+' escaped. The value is returned through status message, see the LEARN command for details. However, the current implementation assumes that Name is not escaped; this works as long as noone uses arbitrary escaping. Note, that this function may even be used on a locked card. */ static int cmd_getattr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; const char *keyword; if ((rc = open_card (ctrl, NULL))) return rc; keyword = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* (We ignore any garbage for now.) */ /* FIXME: Applications should not return sensistive data if the card is locked. */ rc = app_getattr (ctrl->app_ctx, ctrl, keyword); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* SETATTR This command is used to store data on a a smartcard. The allowed names and values are depend on the currently selected smartcard application. NAME and VALUE must be percent and '+' escaped. However, the curent implementation assumes that Name is not escaped; this works as long as noone uses arbitrary escaping. A PIN will be requested for most NAMEs. See the corresponding setattr function of the actually used application (app-*.c) for details. */ static int cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((rc = open_card (ctrl, NULL))) return rc; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ line = linebuf = xtrystrdup (orig_line); if (!line) return ASSUAN_Out_Of_Core; keyword = line; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; if (*line) *line++ = 0; while (spacep (line)) line++; nbytes = percent_plus_unescape ((unsigned char*)line); rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx, (const unsigned char*)line, nbytes); xfree (linebuf); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* WRITEKEY [--force] This command is used to store a secret key on a a smartcard. The allowed keyids depend on the currently selected smartcard application. The actual keydata is requested using the inquiry "KETDATA" and need to be provided without any protection. With --force set an existing key under this KEYID will get overwritten. The keydata is expected to be the usual canonical encoded S-expression. A PIN will be requested for most NAMEs. See the corresponding writekey function of the actually used application (app-*.c) for details. */ static int cmd_writekey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyid; int force = has_option (line, "--force"); unsigned char *keydata; size_t keydatalen; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); /* Skip over options. */ while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } if (!*line) return set_error (Parameter_Error, "no keyid given"); keyid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl, NULL))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyid = xtrystrdup (keyid); if (!keyid) return ASSUAN_Out_Of_Core; /* Now get the actual keydata. */ rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA); if (rc) { xfree (keyid); return rc; } /* Write the key to the card. */ rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0, pin_cb, ctx, keydata, keydatalen); xfree (keyid); xfree (keydata); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* GENKEY [--force] Generate a key on-card identified by NO, which is application specific. Return values are application specific. For OpenPGP cards 2 status lines are returned: S KEY-FPR S KEY-CREATED-AT S KEY-DATA [p|n] --force is required to overwrite an already existing key. The KEY-CREATED-AT is required for further processing because it is part of the hashed key material for the fingerprint. The public part of the key can also later be retrieved using the READKEY command. */ static int cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyno; int force = has_option (line, "--force"); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); /* Skip over options. */ while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } if (!*line) return set_error (Parameter_Error, "no key number given"); keyno = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl, NULL))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyno = xtrystrdup (keyno); if (!keyno) return ASSUAN_Out_Of_Core; rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, pin_cb, ctx); xfree (keyno); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* RANDOM Get NBYTES of random from the card and send them back as data. Note, that this function may be even be used on a locked card. */ static int cmd_random (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; size_t nbytes; unsigned char *buffer; if (!*line) return set_error (Parameter_Error, "number of requested bytes missing"); nbytes = strtoul (line, NULL, 0); if ((rc = open_card (ctrl, NULL))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); buffer = xtrymalloc (nbytes); if (!buffer) return ASSUAN_Out_Of_Core; rc = app_get_challenge (ctrl->app_ctx, nbytes, buffer); if (!rc) { rc = assuan_send_data (ctx, buffer, nbytes); xfree (buffer); return rc; /* that is already an assuan error code */ } xfree (buffer); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* PASSWD [--reset] Change the PIN or reset the retry counter of the card holder verfication vector CHVNO. */ static int cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *chvnostr; int reset_mode = has_option (line, "--reset"); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); /* Skip over options. */ while (*line == '-' && line[1] == '-') { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } if (!*line) return set_error (Parameter_Error, "no CHV number given"); chvnostr = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl, NULL))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); chvnostr = xtrystrdup (chvnostr); if (!chvnostr) return ASSUAN_Out_Of_Core; rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, reset_mode, pin_cb, ctx); if (rc) log_error ("command passwd failed: %s\n", gpg_strerror (rc)); xfree (chvnostr); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* CHECKPIN Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache earlier to long lasting operations. Its use is highly application dependent. For OpenPGP: Perform a simple verify operation for CHV1 and CHV2, so that further operations won't ask for CHV2 and it is possible to do a cheap check on the PIN: If there is something wrong with the PIN entry system, only the regular CHV will get blocked and not the dangerous CHV3. IDSTR is the usual card's serial number in hex notation; an optional fingerprint part will get ignored. There is however a special mode if the IDSTR is sffixed with the literal string "[CHV3]": In this case the Admin PIN is checked if and only if the retry counter is still at 3. */ static int cmd_checkpin (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyidstr; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((rc = open_card (ctrl, NULL))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid. */ keyidstr = xtrystrdup (line); if (!keyidstr) return ASSUAN_Out_Of_Core; rc = app_check_pin (ctrl->app_ctx, keyidstr, pin_cb, ctx); xfree (keyidstr); if (rc) log_error ("app_check_pin failed: %s\n", gpg_strerror (rc)); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); } /* LOCK [--wait] Grant exclusive card access to this session. Note that there is no lock counter used and a second lock from the same session will be ignored. A single unlock (or RESET) unlocks the session. Return GPG_ERR_LOCKED if another session has locked the reader. If the option --wait is given the command will wait until a lock has been released. */ static int cmd_lock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; retry: if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); } else locked_session = ctrl->server_local; #ifdef USE_GNU_PTH if (rc && has_option (line, "--wait")) { rc = 0; pth_sleep (1); /* Better implement an event mechanism. However, for card operations this should be sufficient. */ /* FIXME: Need to check that the connection is still alive. This can be done by issuing status messages. */ goto retry; } #endif /*USE_GNU_PTH*/ if (rc) log_error ("cmd_lock failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* UNLOCK Release exclusive card access. */ static int cmd_unlock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); else locked_session = NULL; } else rc = gpg_error (GPG_ERR_NOT_LOCKED); if (rc) log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); } /* GETINFO Multi purpose command to return certain information. Supported values of WHAT are: socket_name - Return the name of the socket. */ static int cmd_getinfo (assuan_context_t ctx, char *line) { int rc = 0; if (!strcmp (line, "socket_name")) { const char *s = scd_get_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else rc = set_error (Parameter_Error, "unknown value for WHAT"); return rc; } /* RESTART Restart the current connection; this is a kind of warm reset. It deletes the context used by this connection but does not send a RESET to the card. Thus the card itself won't get reset. This is used by gpg-agent to reuse a primary pipe connection and may be used by clients to backup from a conflict in the serial command; i.e. to select another application. */ static int cmd_restart (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl->app_ctx) { release_application (ctrl->app_ctx); ctrl->app_ctx = NULL; } if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESTART\n"); } return 0; } /* APDU [--atr] [--more] [hexstring] Send an APDU to the current reader. This command bypasses the high level functions and sends the data directly to the card. HEXSTRING is expected to be a proper APDU. If HEXSTRING is not given no commands are set to the card but the command will implictly check whether the card is ready for use. Using the option "--atr" returns the ATR of the card as a status message before any data like this: S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1 Using the option --more handles the card status word MORE_DATA (61xx) and concatenate all reponses to one block. */ static int cmd_apdu (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int rc_is_assuan = 0; unsigned char *apdu; size_t apdulen; int with_atr; int handle_more; with_atr = has_option (line, "--atr"); handle_more = has_option (line, "--more"); /* Skip over options. */ while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((rc = open_card (ctrl, NULL))) return rc; if (with_atr) { unsigned char *atr; size_t atrlen; int i; char hexbuf[400]; atr = apdu_get_atr (ctrl->reader_slot, &atrlen); if (!atr || atrlen > sizeof hexbuf - 2 ) { rc = gpg_error (GPG_ERR_INV_CARD); goto leave; } for (i=0; i < atrlen; i++) sprintf (hexbuf+2*i, "%02X", atr[i]); xfree (atr); send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0); } apdu = hex_to_buffer (line, &apdulen); if (!apdu) { rc = gpg_error_from_errno (errno); goto leave; } if (apdulen) { unsigned char *result = NULL; size_t resultlen; rc = apdu_send_direct (ctrl->reader_slot, apdu, apdulen, handle_more, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else { rc_is_assuan = 1; rc = assuan_send_data (ctx, result, resultlen); xfree (result); } } xfree (apdu); leave: TEST_CARD_REMOVAL (ctrl, rc); return rc_is_assuan? rc : map_to_assuan_status (rc); } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; int (*handler)(assuan_context_t, char *line); } table[] = { { "SERIALNO", cmd_serialno }, { "LEARN", cmd_learn }, { "READCERT", cmd_readcert }, { "READKEY", cmd_readkey }, { "SETDATA", cmd_setdata }, { "PKSIGN", cmd_pksign }, { "PKAUTH", cmd_pkauth }, { "PKDECRYPT", cmd_pkdecrypt }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "GETATTR", cmd_getattr }, { "SETATTR", cmd_setattr }, { "WRITEKEY", cmd_writekey }, { "GENKEY", cmd_genkey }, { "RANDOM", cmd_random }, { "PASSWD", cmd_passwd }, { "CHECKPIN", cmd_checkpin }, { "LOCK", cmd_lock }, { "UNLOCK", cmd_unlock }, { "GETINFO", cmd_getinfo }, { "RESTART", cmd_restart }, { "APDU", cmd_apdu }, { 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_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If FD is given as -1 this is simple pipe server, otherwise it is a regular server. */ void scd_command_handler (int fd) { int rc; assuan_context_t ctx; struct server_control_s ctrl; memset (&ctrl, 0, sizeof ctrl); scd_init_default_ctrl (&ctrl); if (fd == -1) { int filedes[2]; filedes[0] = 0; filedes[1] = 1; rc = assuan_init_pipe_server (&ctx, filedes); } else { rc = assuan_init_connected_socket_server (&ctx, fd); } if (rc) { log_error ("failed to initialize the server: %s\n", assuan_strerror(rc)); scd_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", assuan_strerror(rc)); scd_exit (2); } assuan_set_pointer (ctx, &ctrl); /* Allocate and initialize the server object. Put it into the list of active sessions. */ ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->next_session = session_list; session_list = ctrl.server_local; ctrl.server_local->ctrl_backlink = &ctrl; ctrl.server_local->assuan_ctx = ctx; if (DBG_ASSUAN) assuan_set_log_stream (ctx, log_get_stream ()); /* We open the reader right at startup so that the ticker is able to update the status file. */ if (ctrl.reader_slot == -1) { ctrl.reader_slot = get_reader_slot (); } /* Command processing loop. */ 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; } } /* Cleanup. */ do_reset (&ctrl, 0); /* Release the server object. */ if (session_list == ctrl.server_local) session_list = ctrl.server_local->next_session; else { struct server_local_s *sl; for (sl=session_list; sl->next_session; sl = sl->next_session) if (sl->next_session == ctrl.server_local) break; if (!sl->next_session) BUG (); sl->next_session = ctrl.server_local->next_session; } xfree (ctrl.server_local); /* Release the Assuan context. */ assuan_deinit_server (ctx); } /* Send a line with status information via assuan and escape all given buffers. The variable elements are pairs of (char *, size_t), terminated with a (NULL, 0). */ void send_status_info (ctrl_t ctrl, const char *keyword, ...) { va_list arg_ptr; const unsigned char *value; size_t valuelen; char buf[950], *p; size_t n; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); p = buf; n = 0; while ( (value = va_arg (arg_ptr, const unsigned char *)) ) { valuelen = va_arg (arg_ptr, size_t); if (!valuelen) continue; /* empty buffer */ if (n) { *p++ = ' '; n++; } for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) { if (*value < ' ' || *value == '+') { sprintf (p, "%%%02X", *value); p += 3; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } } *p = 0; assuan_write_status (ctx, keyword, buf); va_end (arg_ptr); } /* This is the core of scd_update_reader_status_file but the caller needs to take care of the locking. */ static void update_reader_status_file (void) { int idx; unsigned int status, changed; /* Note, that we only try to get the status, because it does not make sense to wait here for a operation to complete. If we are busy working with a card, delays in the status file update should be acceptable. */ for (idx=0; idx < DIM(slot_table); idx++) { struct slot_status_s *ss = slot_table + idx; if (!ss->valid || ss->slot == -1) continue; /* Not valid or reader not yet open. */ if ( apdu_get_status (ss->slot, 0, &status, &changed) ) continue; /* Get status failed. */ if (!ss->any || ss->status != status || ss->changed != changed ) { char *fname; char templ[50]; FILE *fp; struct server_local_s *sl; log_info ("updating status of slot %d to 0x%04X\n", ss->slot, status); sprintf (templ, "reader_%d.status", ss->slot); fname = make_filename (opt.homedir, templ, NULL ); fp = fopen (fname, "w"); if (fp) { fprintf (fp, "%s\n", (status & 1)? "USABLE": (status & 4)? "ACTIVE": (status & 2)? "PRESENT": "NOCARD"); fclose (fp); } xfree (fname); /* Set the card removed flag for all current sessions. We will set this on any card change because a reset or SERIALNO request must be done in any case. */ if (ss->any) update_card_removed (ss->slot, 1); ss->any = 1; ss->status = status; ss->changed = changed; /* Send a signal to all clients who applied for it. */ for (sl=session_list; sl; sl = sl->next_session) if (sl->event_signal && sl->assuan_ctx) { pid_t pid = assuan_get_pid (sl->assuan_ctx); int signo = sl->event_signal; log_info ("client pid is %d, sending signal %d\n", pid, signo); #ifndef HAVE_W32_SYSTEM if (pid != (pid_t)(-1) && pid && signo > 0) kill (pid, signo); #endif } } } } /* This function is called by the ticker thread to check for changes of the reader stati. It updates the reader status files and if requested by the caller also send a signal to the caller. */ void scd_update_reader_status_file (void) { if (!pth_mutex_acquire (&status_file_update_lock, 1, NULL)) return; /* locked - give up. */ update_reader_status_file (); if (!pth_mutex_release (&status_file_update_lock)) log_error ("failed to release status_file_update lock\n"); } diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c index 66b6894e0..395b4625a 100644 --- a/scd/sc-copykeys.c +++ b/scd/sc-copykeys.c @@ -1,735 +1,736 @@ /* sc-copykeys.c - A tool to store keys on a smartcard. * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #define JNLIB_NEED_LOG_LOGV #include "scdaemon.h" #include #include "../common/ttyio.h" #include "../common/simple-pwquery.h" #include "apdu.h" /* for open_reader */ #include "atr.h" #include "app-common.h" #define _(a) (a) enum cmd_and_opt_values { oVerbose = 'v', oReaderPort = 500, octapiDriver, oDebug, oDebugAll, aTest }; static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, "@Options:\n " }, { oVerbose, "verbose", 0, "verbose" }, { oReaderPort, "reader-port", 2, "|N|connect to reader at port N"}, { octapiDriver, "ctapi-driver", 2, "NAME|use NAME as ctAPI driver"}, { oDebug, "debug" ,4|16, "set debugging flags"}, { oDebugAll, "debug-all" ,0, "enable full debugging"}, {0} }; static void copykeys (APP app, const char *fname); static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "sc-copykeys (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: sc-copykeys [options] (-h for help)\n"); break; case 41: p = _("Syntax: sc-copykeys [options] " "file-with-key\n" "Copy keys to a smartcards\n"); break; default: p = NULL; } return p; } /* 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); } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int slot, rc; const char *reader_port = NULL; struct app_ctx_s appbuf; memset (&appbuf, 0, sizeof appbuf); set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix ("sc-copykeys", 1); /* 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) ); } gcry_set_log_handler (my_gcry_logger, NULL); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* FIXME - we want to use it */ /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/ 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 oDebug: opt.debug |= pargs.r.ret_ulong; break; case oDebugAll: opt.debug = ~0; break; case oReaderPort: reader_port = pargs.r.ret_str; break; case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break; default : pargs.err = 2; break; } } if (log_get_errorcount(0)) exit(2); if (argc != 1) usage (1); slot = apdu_open_reader (reader_port); if (slot == -1) exit (1); /* FIXME: Use select_application. */ appbuf.slot = slot; rc = app_select_openpgp (&appbuf); if (rc) { log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc)); exit (1); } appbuf.initialized = 1; log_info ("openpgp application selected\n"); copykeys (&appbuf, *argv); return 0; } void send_status_info (CTRL ctrl, const char *keyword, ...) { /* DUMMY */ } static char * read_file (const char *fname, size_t *r_length) { FILE *fp; struct stat st; char *buf; size_t buflen; fp = fname? fopen (fname, "rb") : stdin; if (!fp) { log_error ("can't open `%s': %s\n", fname? fname: "[stdin]", strerror (errno)); return NULL; } if (fstat (fileno(fp), &st)) { log_error ("can't stat `%s': %s\n", fname? fname: "[stdin]", strerror (errno)); if (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? fname: "[stdin]", strerror (errno)); if (fname) fclose (fp); xfree (buf); return NULL; } if (fname) fclose (fp); *r_length = buflen; return buf; } static gcry_sexp_t read_key (const char *fname) { char *buf; size_t buflen; gcry_sexp_t private; int rc; buf = read_file (fname, &buflen); if (!buf) return NULL; rc = gcry_sexp_new (&private, buf, buflen, 1); if (rc) { log_error ("gcry_sexp_new failed: %s\n", gpg_strerror (rc)); return NULL; } xfree (buf); return private; } static gcry_mpi_t * sexp_to_kparms (gcry_sexp_t sexp, unsigned long *created) { gcry_sexp_t list, l2; const char *name; const char *s; size_t n; int i, idx; const char *elems; gcry_mpi_t *array; *created = 0; list = gcry_sexp_find_token (sexp, "private-key", 0 ); if(!list) return NULL; /* quick hack to get the creation time. */ l2 = gcry_sexp_find_token (list, "created", 0); if (l2 && (name = gcry_sexp_nth_data (l2, 1, &n))) { char *tmp = xmalloc (n+1); memcpy (tmp, name, n); tmp[n] = 0; *created = strtoul (tmp, NULL, 10); xfree (tmp); } gcry_sexp_release (l2); 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 32) { log_error ("public exponent too large (more than 32 bits)\n"); goto failure; } nbits = gcry_mpi_get_nbits (rsa_p); if (nbits != 512) { log_error ("length of first RSA prime is not 512\n"); goto failure; } nbits = gcry_mpi_get_nbits (rsa_q); if (nbits != 512) { log_error ("length of second RSA prime is not 512\n"); goto failure; } nbits = gcry_mpi_get_nbits (rsa_n); if (nbits != 1024) { log_error ("length of RSA modulus is not 1024\n"); goto failure; } keyno = query_card (app); if (!keyno) goto failure; /* Build the private key template as described in section 4.3.3.6 of the specs. 0xC0 public exponent 0xC1 prime p 0xC2 prime q */ template = tp = xmalloc (1+2 + 1+1+4 + 1+1+64 + 1+1+64); *tp++ = 0xC0; *tp++ = 4; rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 4, &n, rsa_e); if (rc) { log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); goto failure; } assert (n <= 4); memcpy (e, tp, n); elen = n; if (n != 4) { memmove (tp+4-n, tp, 4-n); memset (tp, 0, 4-n); } tp += 4; *tp++ = 0xC1; *tp++ = 64; rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_p); if (rc) { log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); goto failure; } assert (n == 64); tp += 64; *tp++ = 0xC2; *tp++ = 64; rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_q); if (rc) { log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); goto failure; } assert (n == 64); tp += 64; assert (tp - template == 138); /* (we need the modulus to calculate the fingerprint) */ rc = gcry_mpi_print (GCRYMPI_FMT_USG, m, 128, &n, rsa_n); if (rc) { log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); goto failure; } assert (n == 128); mlen = 128; rc = app_openpgp_storekey (app, keyno, template, tp - template, created_at, m, mlen, e, elen, pincb, NULL); if (rc) { log_error ("error storing key: %s\n", gpg_strerror (rc)); goto failure; } log_info ("key successfully stored\n"); { unsigned char *mm, *ee; size_t mmlen, eelen; int i; rc = app_openpgp_readkey (app, keyno, &mm, &mmlen, &ee, &eelen); if (rc) { log_error ("error reading key back: %s\n", gpg_strerror (rc)); goto failure; } /* Strip leading zeroes. */ for (i=0; i < mmlen && !mm[i]; i++) ; mmlen -= i; memmove (mm, mm+i, mmlen); for (i=0; i < eelen && !ee[i]; i++) ; eelen -= i; memmove (ee, ee+i, eelen); if (eelen != elen || mmlen != mlen) { log_error ("key parameter length mismatch (n=%u/%u, e=%u/%u)\n", (unsigned int)mlen, (unsigned int)mmlen, (unsigned int)elen, (unsigned int)eelen); xfree (mm); xfree (ee); goto failure; } if (memcmp (m, mm, mlen)) { log_error ("key parameter n mismatch\n"); log_printhex ("original n: ", m, mlen); log_printhex (" copied n: ", mm, mlen); xfree (mm); xfree (ee); goto failure; } if (memcmp (e, ee, elen)) { log_error ("key parameter e mismatch\n"); log_printhex ("original e: ", e, elen); log_printhex (" copied e: ", ee, elen); xfree (mm); xfree (ee); goto failure; } xfree (mm); xfree (ee); } gcry_mpi_release (rsa_e); gcry_mpi_release (rsa_p); gcry_mpi_release (rsa_q); gcry_mpi_release (rsa_n); return; failure: gcry_mpi_release (rsa_e); gcry_mpi_release (rsa_p); gcry_mpi_release (rsa_q); gcry_mpi_release (rsa_n); exit (1); } diff --git a/scd/scdaemon.c b/scd/scdaemon.c index e24b42132..b11cc7a91 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -1,1167 +1,1168 @@ /* scdaemon.c - The GnuPG Smartcard Daemon * Copyright (C) 2001, 2002, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*HAVE_W32_SYSTEM*/ #include #include #include #define JNLIB_NEED_LOG_LOGV #include "scdaemon.h" #include #include #include /* malloc hooks */ #include "i18n.h" #include "sysutils.h" #include "app-common.h" #ifdef HAVE_W32_SYSTEM #include "../jnlib/w32-afunix.h" #endif #include "ccid-driver.h" #include "mkdtemp.h" enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, oOptions, oDebug, oDebugAll, oDebugLevel, oDebugWait, oDebugAllowCoreDump, oDebugCCIDDriver, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oNoGrab, oLogFile, oServer, oMultiServer, oDaemon, oBatch, oReaderPort, octapiDriver, opcscDriver, oDisableCCID, oDisableOpenSC, oDisableKeypad, oAllowAdmin, oDenyAdmin, oDisableApplication, oDebugDisableTicker }; static ARGPARSE_OPTS opts[] = { { aGPGConfList, "gpgconf-list", 256, "@" }, { 301, NULL, 0, N_("@Options:\n ") }, { oServer, "server", 0, N_("run in server mode (foreground)") }, { oMultiServer, "multi-server", 0, N_("run in multi 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, "@"}, { oDebugAll, "debug-all" ,0, "@"}, { oDebugLevel, "debug-level" ,2, "@"}, { oDebugWait,"debug-wait",1, "@"}, { oDebugAllowCoreDump, "debug-allow-core-dump", 0, "@" }, { oDebugCCIDDriver, "debug-ccid-driver", 0, "@"}, { oDebugDisableTicker, "debug-disable-ticker", 0, "@"}, { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, { oLogFile, "log-file" ,2, N_("use a log file for the server")}, { oReaderPort, "reader-port", 2, N_("|N|connect to reader at port N")}, { octapiDriver, "ctapi-driver", 2, N_("|NAME|use NAME as ct-API driver")}, { opcscDriver, "pcsc-driver", 2, N_("|NAME|use NAME as PC/SC driver")}, { oDisableCCID, "disable-ccid", 0, #ifdef HAVE_LIBUSB N_("do not use the internal CCID driver") #else "@" #endif /* end --disable-ccid */}, { oDisableKeypad, "disable-keypad", 0, N_("do not use a reader's keypad")}, { oAllowAdmin, "allow-admin", 0, N_("allow the use of admin card commands")}, { oDenyAdmin, "deny-admin", 0, "@" }, { oDisableApplication, "disable-application", 2, "@"}, {0} }; /* The card dirver we use by default for PC/SC. */ #if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__) #define DEFAULT_PCSC_DRIVER "winscard.dll" #elif defined(__GLIBC__) #define DEFAULT_PCSC_DRIVER "libpcsclite.so.1" #else #define DEFAULT_PCSC_DRIVER "libpcsclite.so" #endif /* 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; /* Debug flag to disable the ticker. The ticker is in fact not disabled but it won't perform any ticker specific actions. */ static int ticker_disabled; static char *create_socket_name (int use_standard_socket, char *standard_name, char *template); static int create_server_socket (int is_standard_name, const char *name); static void *start_connection_thread (void *arg); static void handle_connections (int listen_fd); /* Pth wrapper function definitions. */ GCRY_THREAD_OPTION_PTH_IMPL; static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "scdaemon (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: scdaemon [options] (-h for help)"); break; case 41: p = _("Syntax: scdaemon [options] [command [args]]\n" "Smartcard daemon for GnuPG\n"); break; default: p = NULL; } return p; } static void i18n_init (void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file( PACKAGE_GT ); #else #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); #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); } /* Setup the debugging. With a LEVEL of NULL only the active debug flags are propagated to the subsystems. With LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. */ static void set_debug (const char *level) { if (!level) ; else if (!strcmp (level, "none")) opt.debug = 0; else if (!strcmp (level, "basic")) opt.debug = DBG_ASSUAN_VALUE; else if (!strcmp (level, "advanced")) opt.debug = DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE; else if (!strcmp (level, "expert")) opt.debug = (DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE |DBG_CACHE_VALUE|DBG_CARD_IO_VALUE); else if (!strcmp (level, "guru")) opt.debug = ~0; else { log_error (_("invalid debug-level `%s' given\n"), level); scd_exit(2); } if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } static void cleanup (void) { if (socket_name && *socket_name) { char *p; remove (socket_name); p = strrchr (socket_name, '/'); if (p) { *p = 0; rmdir (socket_name); *p = '/'; } *socket_name = 0; } } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int orig_argc; gpg_error_t err; int may_coredump; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; const char *debug_level = NULL; int default_config =1; int greeting = 0; int nogreeting = 0; int pipe_server = 0; int multi_server = 0; int is_daemon = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; int debug_wait = 0; int gpgconf_list = 0; const char *config_filename = NULL; int allow_coredump = 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 ("scdaemon", 1|4); /* Try to auto set the character set. */ set_native_charset (NULL); i18n_init (); /* Libgcrypt requires us to register the threading model first. Note that this will also do the pth_init. */ err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); if (err) { log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", gpg_strerror (err)); } /* Check that the libraries are suitable. Do it here because the option parsing may need services of the library */ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) { log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); } ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); assuan_set_assuan_log_stream (log_get_stream ()); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); gcry_set_log_handler (my_gcry_logger, NULL); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); /* Set default options. */ opt.pcsc_driver = DEFAULT_PCSC_DRIVER; shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; opt.homedir = 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 working under our real uid */ if (default_config) configname = make_filename (opt.homedir, "scdaemon.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) ) { switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oBatch: opt.batch=1; break; case oDebug: opt.debug |= pargs.r.ret_ulong; break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oDebugAllowCoreDump: enable_core_dumps (); allow_coredump = 1; break; case oDebugCCIDDriver: #ifdef HAVE_LIBUSB ccid_set_debug_level (ccid_set_debug_level (-1)+1); #endif /*HAVE_LIBUSB*/ break; case oDebugDisableTicker: ticker_disabled = 1; 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 oMultiServer: pipe_server = 1; multi_server = 1; break; case oDaemon: is_daemon = 1; break; case oReaderPort: opt.reader_port = pargs.r.ret_str; break; case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break; case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break; case oDisableCCID: opt.disable_ccid = 1; break; case oDisableOpenSC: break; case oDisableKeypad: opt.disable_keypad = 1; break; case oAllowAdmin: opt.allow_admin = 1; break; case oDenyAdmin: opt.allow_admin = 0; break; case oDisableApplication: add_to_strlist (&opt.disabled_applications, pargs.r.ret_str); break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Keep a copy of the config name for use by --gpgconf-list. */ 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); } set_debug (debug_level); 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 (gpgconf_list) { /* List options and default values in the GPG Conf format. */ /* The following list is taken from gnupg/tools/gpgconf-comp.c. */ /* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ #define GC_OPT_FLAG_NONE 0UL /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* The DEFAULT flag for an option indicates that the option has a default value. */ #define GC_OPT_FLAG_DEFAULT (1UL << 4) /* The DEF_DESC flag for an option indicates that the option has a default, which is described by the value of the default field. */ #define GC_OPT_FLAG_DEF_DESC (1UL << 5) /* The NO_ARG_DESC flag for an option indicates that the argument has a default, which is described by the value of the ARGDEF field. */ #define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) if (!config_filename) config_filename = make_filename (opt.homedir, "scdaemon.conf", NULL ); printf ("gpgconf-scdaemon.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, config_filename); printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE, GC_OPT_FLAG_NONE, GC_OPT_FLAG_DEFAULT, GC_OPT_FLAG_NONE ); printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE ); printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE ); printf ("pcsc-driver:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER ); #ifdef HAVE_LIBUSB printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE ); #endif printf ("allow-admin:%lu:\n", GC_OPT_FLAG_NONE ); scd_exit (0); } /* now start with logging to a file if this is desired */ if (logfile) { log_set_file (logfile); log_set_prefix (NULL, 1|2|4); } if (pipe_server) { /* This is the simple pipe based server */ pth_attr_t tattr; int fd = -1; { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } /* If --debug-allow-core-dump has been given we also need to switch the working directory to a place where we can actually write. */ if (allow_coredump) { if (chdir("/tmp")) log_debug ("chdir to `/tmp' failed: %s\n", strerror (errno)); else log_debug ("changed working directory to `/tmp'\n"); } /* In multi server mode we need to listen on an additional socket. Create that socket now before starting the handler for the pipe connection. This allows that handler to send back the name of that socket. */ if (multi_server) { socket_name = create_socket_name (0, "S.scdaemon", "/tmp/gpg-XXXXXX/S.scdaemon"); fd = create_server_socket (0, socket_name); } tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 512*1024); pth_attr_set (tattr, PTH_ATTR_NAME, "pipe-connection"); if (!pth_spawn (tattr, start_connection_thread, (void*)(-1))) { log_error ("error spawning pipe connection handler: %s\n", strerror (errno) ); scd_exit (2); } handle_connections (fd); if (fd != -1) close (fd); } else if (!is_daemon) { log_info (_("please use the option `--daemon'" " to run the program in the background\n")); } else { /* Regular server mode */ int fd; pid_t pid; int i; /* Create the socket. */ socket_name = create_socket_name (0, "S.scdaemon", "/tmp/gpg-XXXXXX/S.scdaemon"); fd = create_server_socket (0, socket_name); fflush (NULL); #ifndef HAVE_W32_SYSTEM pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* we are the parent */ char *infostr; close (fd); /* create the info string: :: */ if (asprintf (&infostr, "SCDAEMON_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 SCDAEMON_INFO;\n", infostr); } free (infostr); exit (0); } /* NOTREACHED */ } /* end parent */ /* This is the child. */ /* Detach from tty and put process into a new session. */ if (!nodetach ) { /* Close stdin, stdout and stderr unless it is the log stream. */ for (i=0; i <= 2; i++) { if ( log_test_fd (i) && i != fd) close (i); } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } if (chdir("/")) { log_error ("chdir to / failed: %s\n", strerror (errno)); exit (1); } #endif /*!HAVE_W32_SYSTEM*/ handle_connections (fd); close (fd); } return 0; } void scd_exit (int rc) { #if 0 #warning no update_random_seed_file update_random_seed_file(); #endif #if 0 /* 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 scd_init_default_ctrl (ctrl_t ctrl) { ctrl->reader_slot = -1; } /* Return the name of the socket to be used to connect to this process. If no socket is available, return NULL. */ const char * scd_get_socket_name () { if (socket_name && *socket_name) return socket_name; return NULL; } static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: log_info ("SIGHUP received - " "re-reading configuration and resetting cards\n"); /* reread_configuration (); */ break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); app_dump_state (); break; case SIGUSR2: log_info ("SIGUSR2 received - no action defined\n"); 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 (); scd_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); scd_exit (0); break; #endif /*!HAVE_W32_SYSTEM*/ default: log_info ("signal %d received - no action defined\n", signo); } } static void handle_tick (void) { if (!ticker_disabled) scd_update_reader_status_file (); } /* Create a name for the socket. With USE_STANDARD_SOCKET given as true using STANDARD_NAME in the home directory or if given has false from the mkdir type name TEMPLATE. In the latter case a unique name in a unique new directory will be created. In both cases check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. Retunrs: Pointer to an allcoated string with the absolute name of the socket used. */ static char * create_socket_name (int use_standard_socket, char *standard_name, char *template) { char *name, *p; if (use_standard_socket) name = make_filename (opt.homedir, standard_name, NULL); else { name = xstrdup (template); p = strrchr (name, '/'); if (!p) BUG (); *p = 0; if (!mkdtemp (name)) { log_error (_("can't create directory `%s': %s\n"), name, strerror (errno)); scd_exit (2); } *p = '/'; } if (strchr (name, PATHSEP_C)) { log_error (("`%s' are not allowed in the socket name\n"), PATHSEP_S); scd_exit (2); } if (strlen (name) + 1 >= DIMof (struct sockaddr_un, sun_path) ) { log_error (_("name of socket too long\n")); scd_exit (2); } return name; } /* Create a Unix domain socket with NAME. IS_STANDARD_NAME indicates whether a non-random socket is used. Returns the file descriptor or terminates the process in case of an error. */ static int create_server_socket (int is_standard_name, const char *name) { struct sockaddr_un *serv_addr; socklen_t len; int fd; int rc; #ifdef HAVE_W32_SYSTEM fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); #else fd = socket (AF_UNIX, SOCK_STREAM, 0); #endif if (fd == -1) { log_error (_("can't create socket: %s\n"), strerror (errno)); scd_exit (2); } serv_addr = xmalloc (sizeof (*serv_addr)); memset (serv_addr, 0, sizeof *serv_addr); serv_addr->sun_family = AF_UNIX; assert (strlen (name) + 1 < sizeof (serv_addr->sun_path)); strcpy (serv_addr->sun_path, name); len = (offsetof (struct sockaddr_un, sun_path) + strlen (serv_addr->sun_path) + 1); #ifdef HAVE_W32_SYSTEM rc = _w32_sock_bind (fd, (struct sockaddr*) serv_addr, len); if (is_standard_name && rc == -1 ) { remove (name); rc = bind (fd, (struct sockaddr*) serv_addr, len); } #else rc = bind (fd, (struct sockaddr*) serv_addr, len); if (is_standard_name && rc == -1 && errno == EADDRINUSE) { remove (name); rc = bind (fd, (struct sockaddr*) serv_addr, len); } #endif if (rc == -1) { log_error (_("error binding socket to `%s': %s\n"), serv_addr->sun_path, strerror (errno)); close (fd); scd_exit (2); } if (listen (fd, 5 ) == -1) { log_error (_("listen() failed: %s\n"), strerror (errno)); close (fd); scd_exit (2); } if (opt.verbose) log_info (_("listening on socket `%s'\n"), serv_addr->sun_path); return fd; } /* This is the standard connection thread's main function. */ static void * start_connection_thread (void *arg) { int fd = (int)arg; if (opt.verbose) log_info (_("handler for fd %d started\n"), fd); scd_command_handler (fd); if (opt.verbose) log_info (_("handler for fd %d terminated\n"), fd); /* If this thread is the pipe connection thread, flag that a shutdown is required. With the next ticker event and given that no other connections are running the shutdown will then happen. */ if (fd == -1) shutdown_pending = 1; return NULL; } /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. LISTEN_FD is allowed to be -1 in which case this code will only do regular timeouts and handle signals. */ static void handle_connections (int listen_fd) { pth_attr_t tattr; pth_event_t ev, time_ev; sigset_t sigs; int signo; struct sockaddr_un paddr; socklen_t plen; fd_set fdset, read_fdset; int ret; int fd; tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 512*1024); #ifndef HAVE_W32_SYSTEM /* fixme */ sigemptyset (&sigs ); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGUSR1); sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); pth_sigmask (SIG_UNBLOCK, &sigs, NULL); ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #else ev = NULL; #endif time_ev = NULL; FD_ZERO (&fdset); if (listen_fd != -1) FD_SET (listen_fd, &fdset); for (;;) { sigset_t oldsigs; if (shutdown_pending) { if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1) break; /* ready */ /* Do not accept anymore connections but wait for existing connections to terminate. We do this by clearing out all file descriptors to wait for, so that the select will be used to just wait on a signal or timeout event. */ FD_ZERO (&fdset); } /* Create a timeout event if needed. */ if (!time_ev) time_ev = pth_event (PTH_EVENT_TIME, pth_timeout (2, 0)); /* POSIX says that fd_set should be implemented as a structure, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; if (time_ev) pth_event_concat (ev, time_ev, NULL); ret = pth_select_ev (FD_SETSIZE, &read_fdset, NULL, NULL, NULL, ev); if (time_ev) pth_event_isolate (time_ev); if (ret == -1) { if (pth_event_occurred (ev) || (time_ev && pth_event_occurred (time_ev))) { if (pth_event_occurred (ev)) handle_signal (signo); if (time_ev && pth_event_occurred (time_ev)) { pth_event_free (time_ev, PTH_FREE_ALL); time_ev = NULL; handle_tick (); } continue; } log_error (_("pth_select failed: %s - waiting 1s\n"), strerror (errno)); pth_sleep (1); continue; } if (pth_event_occurred (ev)) { handle_signal (signo); } if (time_ev && pth_event_occurred (time_ev)) { pth_event_free (time_ev, PTH_FREE_ALL); time_ev = NULL; handle_tick (); } /* We now might create new threads and because we don't want any signals - we are handling here - to be delivered to a new thread. Thus we need to block those signals. */ pth_sigmask (SIG_BLOCK, &sigs, &oldsigs); if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset)) { plen = sizeof paddr; fd = pth_accept (listen_fd, (struct sockaddr *)&paddr, &plen); if (fd == -1) { log_error ("accept failed: %s\n", strerror (errno)); } else { char threadname[50]; snprintf (threadname, sizeof threadname-1, "conn fd=%d", fd); threadname[sizeof threadname -1] = 0; pth_attr_set (tattr, PTH_ATTR_NAME, threadname); if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) { log_error ("error spawning connection handler: %s\n", strerror (errno) ); close (fd); } } fd = -1; } /* Restore the signal mask. */ pth_sigmask (SIG_SETMASK, &oldsigs, NULL); } pth_event_free (ev, PTH_FREE_ALL); if (time_ev) pth_event_free (time_ev, PTH_FREE_ALL); cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); } diff --git a/scd/scdaemon.h b/scd/scdaemon.h index abe9730a7..f9689ee09 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -1,121 +1,122 @@ /* scdaemon.h - Global definitions for the SCdaemon * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef SCDAEMON_H #define SCDAEMON_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_SCD #include #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include #include #include "../common/util.h" #include "../common/errors.h" #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 *ctapi_driver; /* Library to access the ctAPI. */ const char *pcsc_driver; /* Library to access the PC/SC system. */ const char *reader_port; /* NULL or reder port to use. */ int disable_ccid; /* Disable the use of the internal CCID driver. */ int disable_keypad; /* Do not use a keypad. */ int allow_admin; /* Allow the use of admin commands for certain cards. */ strlist_t disabled_applications; /* Card applications we do not want to use. */ } 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_CARD_IO_VALUE 2048 #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) #define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) struct server_local_s; struct app_ctx_s; struct server_control_s { /* Local data of the server; used only in command.c. */ struct server_local_s *server_local; /* Slot of the open reader or -1 if not open. */ int reader_slot; /* The application context used with this connection or NULL if none associated. Note that this is shared with the other connections: All connections accessing the same reader are using the same application context. */ struct app_ctx_s *app_ctx; /* Helper to store the value we are going to sign */ struct { unsigned char *value; int valuelen; } in_data; }; typedef struct server_control_s *CTRL; typedef struct server_control_s *ctrl_t; typedef struct app_ctx_s *APP; typedef struct app_ctx_s *app_t; /*-- scdaemon.c --*/ void scd_exit (int rc); void scd_init_default_ctrl (ctrl_t ctrl); const char *scd_get_socket_name (void); /*-- command.c --*/ void scd_command_handler (int); void send_status_info (CTRL ctrl, const char *keyword, ...); void scd_update_reader_status_file (void); #endif /*SCDAEMON_H*/ diff --git a/scd/tlv.c b/scd/tlv.c index b436d956a..6ddbeaf1f 100644 --- a/scd/tlv.c +++ b/scd/tlv.c @@ -1,304 +1,305 @@ /* tlv.c - Tag-Length-Value Utilities * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #if GNUPG_MAJOR_VERSION == 1 #define GPG_ERR_EOF (-1) #define GPG_ERR_BAD_BER (1) /*G10ERR_GENERAL*/ #define GPG_ERR_INV_SEXP (45) /*G10ERR_INV_ARG*/ typedef int gpg_error_t; #define gpg_error(n) (n) #else #include #endif #include "tlv.h" static const unsigned char * do_find_tlv (const unsigned char *buffer, size_t length, int tag, size_t *nbytes, int nestlevel) { const unsigned char *s = buffer; size_t n = length; size_t len; int this_tag; int composite; for (;;) { buffer = s; if (n < 2) return NULL; /* Buffer definitely too short for tag and length. */ if (!*s || *s == 0xff) { /* Skip optional filler between TLV objects. */ s++; n--; continue; } composite = !!(*s & 0x20); if ((*s & 0x1f) == 0x1f) { /* more tag bytes to follow */ s++; n--; if (n < 2) return NULL; /* buffer definitely too short for tag and length. */ if ((*s & 0x1f) == 0x1f) return NULL; /* We support only up to 2 bytes. */ this_tag = (s[-1] << 8) | (s[0] & 0x7f); } else this_tag = s[0]; len = s[1]; s += 2; n -= 2; if (len < 0x80) ; else if (len == 0x81) { /* One byte length follows. */ if (!n) return NULL; /* we expected 1 more bytes with the length. */ len = s[0]; s++; n--; } else if (len == 0x82) { /* Two byte length follows. */ if (n < 2) return NULL; /* We expected 2 more bytes with the length. */ len = (s[0] << 8) | s[1]; s += 2; n -= 2; } else return NULL; /* APDU limit is 65535, thus it does not make sense to assume longer length fields. */ if (composite && nestlevel < 100) { /* Dive into this composite DO after checking for a too deep nesting. */ const unsigned char *tmp_s; size_t tmp_len; tmp_s = do_find_tlv (s, len, tag, &tmp_len, nestlevel+1); if (tmp_s) { *nbytes = tmp_len; return tmp_s; } } if (this_tag == tag) { *nbytes = len; return s; } if (len > n) return NULL; /* Buffer too short to skip to the next tag. */ s += len; n -= len; } } /* Locate a TLV encoded data object in BUFFER of LENGTH and return a pointer to value as well as its length in NBYTES. Return NULL if it was not found or if the object does not fit into the buffer. */ const unsigned char * find_tlv (const unsigned char *buffer, size_t length, int tag, size_t *nbytes) { const unsigned char *p; p = do_find_tlv (buffer, length, tag, nbytes, 0); if (p && *nbytes > (length - (p-buffer))) p = NULL; /* Object longer than buffer. */ return p; } /* Locate a TLV encoded data object in BUFFER of LENGTH and return a pointer to value as well as its length in NBYTES. Return NULL if it was not found. Note, that the function does not check whether the value fits into the provided buffer. */ const unsigned char * find_tlv_unchecked (const unsigned char *buffer, size_t length, int tag, size_t *nbytes) { return do_find_tlv (buffer, length, tag, nbytes, 0); } /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE on success. */ gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, int *r_class, int *r_tag, int *r_constructed, int *r_ndef, size_t *r_length, size_t *r_nhdr) { int c; unsigned long tag; const unsigned char *buf = *buffer; size_t length = *size; *r_ndef = 0; *r_length = 0; *r_nhdr = 0; /* Get the tag. */ if (!length) return gpg_error (GPG_ERR_EOF); c = *buf++; length--; ++*r_nhdr; *r_class = (c & 0xc0) >> 6; *r_constructed = !!(c & 0x20); tag = c & 0x1f; if (tag == 0x1f) { tag = 0; do { tag <<= 7; if (!length) return gpg_error (GPG_ERR_EOF); c = *buf++; length--; ++*r_nhdr; tag |= c & 0x7f; } while (c & 0x80); } *r_tag = tag; /* Get the length. */ if (!length) return gpg_error (GPG_ERR_EOF); c = *buf++; length--; ++*r_nhdr; if ( !(c & 0x80) ) *r_length = c; else if (c == 0x80) *r_ndef = 1; else if (c == 0xff) return gpg_error (GPG_ERR_BAD_BER); else { unsigned long len = 0; int count = c & 0x7f; if (count > sizeof (len) || count > sizeof (size_t)) return gpg_error (GPG_ERR_BAD_BER); for (; count; count--) { len <<= 8; if (!length) return gpg_error (GPG_ERR_EOF); c = *buf++; length--; ++*r_nhdr; len |= c & 0xff; } *r_length = len; } /* Without this kludge some example certs can't be parsed. */ if (*r_class == CLASS_UNIVERSAL && !*r_tag) *r_length = 0; *buffer = buf; *size = length; return 0; } /* FIXME: The following function should not go into this file but for now it is easier to keep it here. */ /* Return the next token of an canconical encoded S-expression. BUF is the pointer to the S-expression and BUFLEN is a pointer to the length of this S-expression (used to validate the syntax). Both are updated to reflect the new position. The token itself is returned as a pointer into the orginal buffer at TOK and TOKLEN. If a parentheses is the next token, TOK will be set to NULL. TOKLEN is checked to be within the bounds. On error a error code is returned and all pointers should are not guaranteed to point to a meanigful value. DEPTH should be initialized to 0 and will reflect on return the actual depth of the tree. To detect the end of the S-expression it is advisable to check DEPTH after a successful return: depth = 0; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth) process_token (tok, toklen); if (err) handle_error (); */ gpg_error_t parse_sexp (unsigned char const **buf, size_t *buflen, int *depth, unsigned char const **tok, size_t *toklen) { const unsigned char *s; size_t n, vlen; s = *buf; n = *buflen; *tok = NULL; *toklen = 0; if (!n) return *depth ? gpg_error (GPG_ERR_INV_SEXP) : 0; if (*s == '(') { s++; n--; (*depth)++; *buf = s; *buflen = n; return 0; } if (*s == ')') { if (!*depth) return gpg_error (GPG_ERR_INV_SEXP); *toklen = 1; s++; n--; (*depth)--; *buf = s; *buflen = n; return 0; } for (vlen=0; n && *s && *s != ':' && (*s >= '0' && *s <= '9'); s++, n--) vlen = vlen*10 + (*s - '0'); if (!n || *s != ':') return gpg_error (GPG_ERR_INV_SEXP); s++; n--; if (vlen > n) return gpg_error (GPG_ERR_INV_SEXP); *tok = s; *toklen = vlen; s += vlen; n -= vlen; *buf = s; *buflen = n; return 0; } diff --git a/scd/tlv.h b/scd/tlv.h index f587dd9df..877573d25 100644 --- a/scd/tlv.h +++ b/scd/tlv.h @@ -1,108 +1,109 @@ /* tlv.h - Tag-Length-Value Utilities * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef SCD_TLV_H #define SCD_TLV_H 1 enum tlv_tag_class { CLASS_UNIVERSAL = 0, CLASS_APPLICATION = 1, CLASS_CONTEXT = 2, CLASS_PRIVATE =3 }; enum tlv_tag_type { 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 }; /* Locate a TLV encoded data object in BUFFER of LENGTH and return a pointer to value as well as its length in NBYTES. Return NULL if it was not found or if the object does not fit into the buffer. */ const unsigned char *find_tlv (const unsigned char *buffer, size_t length, int tag, size_t *nbytes); /* Locate a TLV encoded data object in BUFFER of LENGTH and return a pointer to value as well as its length in NBYTES. Return NULL if it was not found. Note, that the function does not check whether the value fits into the provided buffer.*/ const unsigned char *find_tlv_unchecked (const unsigned char *buffer, size_t length, int tag, size_t *nbytes); /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE on success. */ gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, int *r_class, int *r_tag, int *r_constructed, int *r_ndef, size_t *r_length, size_t *r_nhdr); /* Return the next token of an canconical encoded S-expression. BUF is the pointer to the S-expression and BUFLEN is a pointer to the length of this S-expression (used to validate the syntax). Both are updated to reflect the new position. The token itself is returned as a pointer into the orginal buffer at TOK and TOKLEN. If a parentheses is the next token, TOK will be set to NULL. TOKLEN is checked to be within the bounds. On error a error code is returned and all pointers should are not guaranteed to point to a meanigful value. DEPTH should be initialized to 0 and will reflect on return the actual depth of the tree. To detect the end of the S-expression it is advisable to check DEPTH after a successful return. */ gpg_error_t parse_sexp (unsigned char const **buf, size_t *buflen, int *depth, unsigned char const **tok, size_t *toklen); #endif /* SCD_TLV_H */ diff --git a/scripts/compile b/scripts/compile index ac07cc541..b6e6dcb0f 100755 --- a/scripts/compile +++ b/scripts/compile @@ -1,107 +1,108 @@ #! /bin/sh # Wrapper for compilers which do not understand `-c -o'. # Copyright 1999, 2000 Free Software Foundation, Inc. # Written by Tom Tromey . # # This program 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, or (at your option) # any later version. # # This program 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA.. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Usage: # compile PROGRAM [ARGS]... # `-o FOO.o' is removed from the args passed to the actual compile. # Usage statement added by Billy Biggs . if [ -z $1 ]; then echo "Wrapper for compilers which do not understand '-c -o'." echo "usage: compile PROGRAM [ARGS]..." echo "'-o FOO.o' is removed from the args passed to the actual compile." exit 1 fi prog=$1 shift ofile= cfile= args= while test $# -gt 0; do case "$1" in -o) # configure might choose to run compile as `compile cc -o foo foo.c'. # So we do something ugly here. ofile=$2 shift case "$ofile" in *.o | *.obj) ;; *) args="$args -o $ofile" ofile= ;; esac ;; *.c) cfile=$1 args="$args $1" ;; *) args="$args $1" ;; esac shift done if test -z "$ofile" || test -z "$cfile"; then # If no `-o' option was seen then we might have been invoked from a # pattern rule where we don't need one. That is ok -- this is a # normal compilation that the losing compiler can handle. If no # `.c' file was seen then we are probably linking. That is also # ok. exec "$prog" $args fi # Name of file we expect compiler to create. cofile=`echo $cfile | sed -e 's|^.*/||' -e 's/\.c$/.o/'` # Create the lock directory. # Note: use `[/.-]' here to ensure that we don't use the same name # that we are using for the .o file. Also, base the name on the expected # object file name, since that is what matters with a parallel build. lockdir=`echo $cofile | sed -e 's|[/.-]|_|g'`.d while true; do if mkdir $lockdir > /dev/null 2>&1; then break fi sleep 1 done # FIXME: race condition here if user kills between mkdir and trap. trap "rmdir $lockdir; exit 1" 1 2 15 # Run the compile. "$prog" $args status=$? if test -f "$cofile"; then mv "$cofile" "$ofile" fi rmdir $lockdir exit $status diff --git a/scripts/config.guess b/scripts/config.guess index 77c7cbab0..a4929a979 100755 --- a/scripts/config.guess +++ b/scripts/config.guess @@ -1,1441 +1,1442 @@ #! /bin/sh # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. timestamp='2004-08-13' # This file 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. # # This program 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA.. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Per Bothner . # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # This script attempts to guess a canonical system name similar to # config.sub. If it succeeds, it prints the system name on stdout, and # exits with 0. Otherwise, it exits with 1. # # The plan is that this can be called by configure scripts if you # don't specify an explicit build system type. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep __ELF__ >/dev/null then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit 0 ;; amd64:OpenBSD:*:*) echo x86_64-unknown-openbsd${UNAME_RELEASE} exit 0 ;; amiga:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; cats:OpenBSD:*:*) echo arm-unknown-openbsd${UNAME_RELEASE} exit 0 ;; hp300:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; luna88k:OpenBSD:*:*) echo m88k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mac68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; macppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme88k:OpenBSD:*:*) echo m88k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvmeppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sgi:OpenBSD:*:*) echo mips64-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sun3:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:OpenBSD:*:*) echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:ekkoBSD:*:*) echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} exit 0 ;; macppc:MirBSD:*:*) echo powerppc-unknown-mirbsd${UNAME_RELEASE} exit 0 ;; *:MirBSD:*:*) echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} exit 0 ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` exit 0 ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit 0 ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit 0 ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit 0;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit 0 ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit 0 ;; *:OS/390:*:*) echo i370-ibm-openedition exit 0 ;; *:OS400:*:*) echo powerpc-ibm-os400 exit 0 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit 0;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit 0;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit 0 ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit 0 ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit 0 ;; DRS?6000:UNIX_SV:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7 && exit 0 ;; esac ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; i86pc:SunOS:5.*:*) echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit 0 ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit 0 ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit 0 ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit 0 ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit 0 ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit 0 ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit 0 ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit 0 ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit 0 ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit 0 ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit 0 ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit 0 ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit 0 ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c \ && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ && exit 0 echo mips-mips-riscos${UNAME_RELEASE} exit 0 ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit 0 ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit 0 ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit 0 ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit 0 ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit 0 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit 0 ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit 0 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit 0 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit 0 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit 0 ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit 0 ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit 0 ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo rs6000-ibm-aix3.2.5 elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit 0 ;; *:AIX:*:[45]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:*:*) echo rs6000-ibm-aix exit 0 ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit 0 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit 0 ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit 0 ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit 0 ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit 0 ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit 0 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then # avoid double evaluation of $set_cc_for_build test -n "$CC_FOR_BUILD" || eval $set_cc_for_build if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit 0 ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit 0 ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo unknown-hitachi-hiuxwe2 exit 0 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit 0 ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit 0 ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit 0 ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit 0 ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit 0 ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit 0 ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit 0 ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit 0 ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit 0 ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit 0 ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit 0 ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit 0 ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:FreeBSD:*:*) echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit 0 ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit 0 ;; i*:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit 0 ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit 0 ;; x86:Interix*:[34]*) echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' exit 0 ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit 0 ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit 0 ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit 0 ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit 0 ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; *:GNU:*:*) # the GNU system echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit 0 ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu exit 0 ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit 0 ;; arm*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; cris:Linux:*:*) echo cris-axis-linux-gnu exit 0 ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; m32r*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; mips:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips #undef mipsel #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mipsel #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips64 #undef mips64el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mips64el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips64 #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; ppc:Linux:*:*) echo powerpc-unknown-linux-gnu exit 0 ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-gnu exit 0 ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} exit 0 ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-gnu ;; PA8*) echo hppa2.0-unknown-linux-gnu ;; *) echo hppa-unknown-linux-gnu ;; esac exit 0 ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-gnu exit 0 ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux exit 0 ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; x86_64:Linux:*:*) echo x86_64-unknown-linux-gnu exit 0 ;; i*86:Linux:*:*) # The BFD linker knows what the default object file format is, so # first see if it will tell us. cd to the root directory to prevent # problems with other programs or directories called `ld' in the path. # Set LC_ALL=C to ensure ld outputs messages in English. ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ | sed -ne '/supported targets:/!d s/[ ][ ]*/ /g s/.*supported targets: *// s/ .*// p'` case "$ld_supported_targets" in elf32-i386) TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" ;; a.out-i386-linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" exit 0 ;; coff-i386) echo "${UNAME_MACHINE}-pc-linux-gnucoff" exit 0 ;; "") # Either a pre-BFD a.out linker (linux-gnuoldld) or # one that does not give us useful --help. echo "${UNAME_MACHINE}-pc-linux-gnuoldld" exit 0 ;; esac # Determine whether the default compiler is a.out or elf eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #ifdef __ELF__ # ifdef __GLIBC__ # if __GLIBC__ >= 2 LIBC=gnu # else LIBC=gnulibc1 # endif # else LIBC=gnulibc1 # endif #else #ifdef __INTEL_COMPILER LIBC=gnu #else LIBC=gnuaout #endif #endif #ifdef __dietlibc__ LIBC=dietlibc #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit 0 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit 0 ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit 0 ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit 0 ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit 0 ;; i*86:syllable:*:*) echo ${UNAME_MACHINE}-pc-syllable exit 0 ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit 0 ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit 0 ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit 0 ;; i*86:*:5:[78]*) case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit 0 ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit 0 ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i386. echo i386-pc-msdosdjgpp exit 0 ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit 0 ;; paragon:*:*:*) echo i860-intel-osf1 exit 0 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit 0 ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit 0 ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit 0 ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit 0 ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4.3${OS_REL} && exit 0 /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4 && exit 0 ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit 0 ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit 0 ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit 0 ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit 0 ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit 0 ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit 0 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit 0 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit 0 ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit 0 ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit 0 ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit 0 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit 0 ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit 0 ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit 0 ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit 0 ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit 0 ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit 0 ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit 0 ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown case $UNAME_PROCESSOR in *86) UNAME_PROCESSOR=i686 ;; unknown) UNAME_PROCESSOR=powerpc ;; esac echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit 0 ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit 0 ;; *:QNX:*:4*) echo i386-pc-qnx exit 0 ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit 0 ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit 0 ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit 0 ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit 0 ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit 0 ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit 0 ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit 0 ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit 0 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit 0 ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit 0 ;; *:ITS:*:*) echo pdp10-unknown-its exit 0 ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit 0 ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit 0 ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "${UNAME_MACHINE}" in A*) echo alpha-dec-vms && exit 0 ;; I*) echo ia64-dec-vms && exit 0 ;; V*) echo vax-dec-vms && exit 0 ;; esac esac #echo '(No uname command or uname output not recognized.)' 1>&2 #echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 eval $set_cc_for_build cat >$dummy.c < # include #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (__arm) && defined (__acorn) && defined (__unix) printf ("arm-acorn-riscix"); exit (0); #endif #if defined (hp300) && !defined (hpux) printf ("m68k-hp-bsd\n"); exit (0); #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) # if !defined (ultrix) # include # if defined (BSD) # if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); # else # if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); # else printf ("vax-dec-bsd\n"); exit (0); # endif # endif # else printf ("vax-dec-bsd\n"); exit (0); # endif # else printf ("vax-dec-ultrix\n"); exit (0); # endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 # Apollos put the system type in the environment. test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } # Convex versions that predate uname can use getsysinfo(1) if [ -x /usr/convex/getsysinfo ] then case `getsysinfo -f cpu_type` in c1*) echo c1-convex-bsd exit 0 ;; c2*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; c34*) echo c34-convex-bsd exit 0 ;; c38*) echo c38-convex-bsd exit 0 ;; c4*) echo c4-convex-bsd exit 0 ;; esac fi cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: diff --git a/sm/ChangeLog b/sm/ChangeLog index 48e8473fa..f191e7512 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,1614 +1,1621 @@ +2006-06-20 Werner Koch + + * gpgsm.c (gpgsm_init_default_ctrl): Take care of the command line + option --include-certs. + + * keylist.c (list_cert_raw): Print the certid. + 2006-05-23 Werner Koch * keydb.c (hextobyte): Deleted as it is now defined in jnlib. * Makefile.am (gpgsm_LDADD): Include ZLIBS. 2006-05-19 Marcus Brinkmann * keydb.c (keydb_insert_cert): Do not lock here, but only check if it is locked. (keydb_store_cert): Lock here. * keydb.h (keydb_delete): Accept new argument UNLOCK. * keydb.c (keydb_delete): Likewise. Only unlock if this is set. * delete.c (delete_one): Add new argument to invocation of keydb_delete. 2006-05-15 Werner Koch * keylist.c (print_names_raw): Sanitize URI. 2006-03-21 Werner Koch * certchain.c (get_regtp_ca_info): New. (allowed_ca): Use it. 2006-03-20 Werner Koch * qualified.c (gpgsm_is_in_qualified_list): New optional arg COUNTRY. 2006-02-17 Werner Koch * call-dirmngr.c (start_dirmngr): Print name of dirmngr to be started. 2005-11-23 Werner Koch * gpgsm.h: New member QUALSIG_APPROVAL. * sign.c (gpgsm_sign): Print a warning if a certificate is not qualified. * qualified.c (gpgsm_qualified_consent): Include a note that this is not approved software. (gpgsm_not_qualified_warning): New. * gpgsm.c (main): Prepared to print a note whether the software has been approved. 2005-11-13 Werner Koch * call-agent.c (gpgsm_agent_get_confirmation): New. * keylist.c (list_cert_std): Print qualified status. * qualified.c: New. * certchain.c (gpgsm_validate_chain): Check for qualified certificates. * certchain.c (gpgsm_basic_cert_check): Release keydb handle when no-chain-validation is used. 2005-11-11 Werner Koch * keylist.c (print_capabilities): Print is_qualified status. 2005-10-28 Werner Koch * certdump.c (pretty_print_sexp): New. (gpgsm_print_name2): Use it here. This allows proper printing of DNS names as used with server certificates. 2005-10-10 Werner Koch * keylist.c: Add pkaAdress OID as reference. 2005-10-08 Marcus Brinkmann * Makefile.am (gpgsm_LDADD): Add ../gl/libgnu.a after ../common/libcommon.a. 2005-09-13 Werner Koch * verify.c (gpgsm_verify): Print a note if the unknown algorithm is MD2. * sign.c (gpgsm_sign): Ditto. * certcheck.c (gpgsm_check_cert_sig): Ditto. 2005-09-08 Werner Koch * export.c (popen_protect_tool): Add option --have-cert. We probably lost this option with 1.9.14 due to restructuring of export.c. 2005-07-21 Werner Koch * gpgsm.c (main): New options --no-log-file and --debug-none. * certreqgen.c (get_parameter, get_parameter_value): Add SEQ arg to allow enumeration. Changed all callers. (create_request): Process DNS and URI parameters. 2005-07-20 Werner Koch * keylist.c (email_kludge): Reworked. * certdump.c (gpgsm_print_serial, gpgsm_dump_serial): Cast printf arg to unsigned. * call-dirmngr.c (gpgsm_dirmngr_run_command): Ditto 2005-07-19 Werner Koch * fingerprint.c (gpgsm_get_certid): Cast printf arg to unsigned. Bug accidently introduced while solving the #$%^& gcc signed/unsigned char* warnings. 2005-06-15 Werner Koch * delete.c (delete_one): Changed FPR to unsigned. * encrypt.c (encrypt_dek): Made ENCVAL unsigned. (gpgsm_encrypt): Ditto. * sign.c (gpgsm_sign): Made SIGVAL unsigned. * base64.c (base64_reader_cb): Need to use some casting to get around signed/unsigned char* warnings. * certcheck.c (gpgsm_check_cms_signature): Ditto. (gpgsm_create_cms_signature): Changed arg R_SIGVAL to unsigned char*. (do_encode_md): Made NFRAME a size_t. * certdump.c (gpgsm_print_serial): Fixed signed/unsigned warning. (gpgsm_dump_serial): Ditto. (gpgsm_format_serial): Ditto. (gpgsm_dump_string): Ditto. (gpgsm_dump_cert): Ditto. (parse_dn_part): Ditto. (gpgsm_print_name2): Ditto. * keylist.c (email_kludge): Ditto. * certreqgen.c (proc_parameters, create_request): Ditto. (create_request): Ditto. * call-agent.c (gpgsm_agent_pksign): Made arg R_BUF unsigned. (struct cipher_parm_s): Made CIPHERTEXT unsigned. (struct genkey_parm_s): Ditto. * server.c (strcpy_escaped_plus): Made arg S signed char*. * fingerprint.c (gpgsm_get_fingerprint): Made ARRAY unsigned. (gpgsm_get_keygrip): Ditto. * keydb.c (keydb_insert_cert): Made DIGEST unsigned. (keydb_update_cert): Ditto. (classify_user_id): Apply cast to signed/unsigned assignment. (hextobyte): Ditto. 2005-06-01 Werner Koch * misc.c: Include setenv.h. 2005-04-21 Werner Koch * gpgsm.c: New options --{enable,disable}-trusted-cert-crl-check. * certchain.c (gpgsm_validate_chain): Make use of it. * certchain.c (gpgsm_validate_chain): Check revocations even for expired certificates. This is required because on signature verification an expired key is fine whereas a revoked one is not. 2005-04-20 Werner Koch * Makefile.am (AM_CFLAGS): Add PTH_CFLAGS as noted by several folks. 2005-04-19 Werner Koch * certchain.c (check_cert_policy): Print the diagnostic for a open failure of policies.txt only in verbose mode or when it is not ENOENT. 2005-04-17 Werner Koch * call-dirmngr.c (inq_certificate): Add new inquire SENDCERT_SKI. * certlist.c (gpgsm_find_cert): Add new arg KEYID and implement this filter. Changed all callers. * certchain.c (find_up_search_by_keyid): New helper. (find_up): Also try using the AKI.keyIdentifier. (find_up_external): Ditto. 2005-04-15 Werner Koch * keylist.c (list_cert_raw): Print the subjectKeyIdentifier as well as the keyIdentifier part of the authorityKeyIdentifier. 2005-03-31 Werner Koch * call-dirmngr.c (start_dirmngr): Use PATHSEP_C instead of ':'. * call-agent.c (start_agent): Ditto. 2005-03-17 Werner Koch * certcheck.c: Fixed use of DBG_CRYPTO and DBG_X509. * certchain.c (gpgsm_basic_cert_check): Dump certificates after a failed gcry_pk_verify. (find_up): Do an external lookup also for an authorityKeyIdentifier lookup. Factored external lookup code out to .. (find_up_external): .. new. 2005-03-03 Werner Koch * Makefile.am (gpgsm_LDADD): Added PTH_LIBS. Noted by Kazu Yamamoto. 2005-01-13 Werner Koch * certreqgen.c (proc_parameters): Cast printf arg. 2004-12-22 Werner Koch * gpgsm.c (set_binary): New. (main, open_read, open_fwrite): Use it. 2004-12-21 Werner Koch * gpgsm.c (main): Use default_homedir(). (main) [W32]: Default to disabled CRL checks. 2004-12-20 Werner Koch * call-agent.c (start_agent): Before starting a pipe server start to connect to a server on the standard socket. Use PATHSEP * call-dirmngr.c (start_dirmngr): Use PATHSEP. * import.c: Include unistd.h for dup and close. 2004-12-18 Werner Koch * gpgsm.h (map_assuan_err): Define in terms of map_assuan_err_with_source. * call-agent.c (start_agent): Pass error source to send_pinentry_environment. 2004-12-17 Werner Koch * call-dirmngr.c (isvalid_status_cb, lookup_status_cb) (run_command_status_cb): Return cancel status if gpgsm_status returned an error. * server.c (gpgsm_status, gpgsm_status2) (gpgsm_status_with_err_code): Return an error code. (gpgsm_status2): Always call va_end(). 2004-12-15 Werner Koch * call-dirmngr.c (lookup_status_cb): Send progress messages upstream. (isvalid_status_cb): Ditto. (gpgsm_dirmngr_isvalid): Put CTRL into status CB parameters. (gpgsm_dirmngr_run_command, run_command_status_cb): Pass CTRL to status callback and handle PROGRESS. * misc.c (setup_pinentry_env) [W32]: Don't use it. * gpgsm.c (main) [W32]: Init Pth because we need it for the socket operations and to resolve libassuan symbols. (run_protect_tool) [W32]: Disable it. * Makefile.am (gpgsm_LDADD): Move LIBASSUAN_LIBS more to the end. 2004-12-07 Werner Koch * Makefile.am (gpgsm_LDADD): Put libassuan before jnlib because under W32 we need the w32 pth code from jnlib. * misc.c (setup_pinentry_env) [W32]: Disabled. 2004-12-06 Werner Koch * gpgsm.c (run_protect_tool) [_WIN32]: Disabled. * import.c (popen_protect_tool): Simplified by making use of gnupg_spawn_process. (parse_p12): Likewise, using gnupg_wait_process. * export.c (popen_protect_tool): Ditto. (export_p12): Ditto. * keydb.c: Don't define DIRSEP_S here. 2004-12-02 Werner Koch * certchain.c (gpgsm_basic_cert_check): Dump certs with bad signature for debugging. (gpgsm_validate_chain): Ditto. 2004-11-29 Werner Koch * gpgsm.c (set_debug): Changed to use a globals DEBUG_LEVEL and DEBUG_VALUE. (main): Made DEBUG_LEVEL global and introduced DEBUG_VALUE. This now allows to add debug flags on top of a debug-level setting. 2004-11-23 Werner Koch * gpgsm.c: New option --prefer-system-dirmngr. * call-dirmngr.c (start_dirmngr): Implement this option. 2004-10-22 Werner Koch * certreqgen.c (gpgsm_genkey): Remove the NEW from the certificate request PEM header. This is according to the Sphinx standard. 2004-10-08 Moritz Schulte * certchain.c (gpgsm_validate_chain): Do not use keydb_new() in case the no_chain_validation-return-short-cut is used (fixes memory leak). 2004-10-04 Werner Koch * misc.c (setup_pinentry_env): Try hard to set a default for GPG_TTY. 2004-09-30 Werner Koch * gpgsm.c (i18n_init): Always use LC_ALL. * certdump.c (gpgsm_format_name): Factored code out to .. (gpgsm_format_name2): .. new. (gpgsm_print_name): Factored code out to .. (gpgsm_print_name2): .. new. (print_dn_part): New arg TRANSLATE. Changed all callers. (print_dn_parts): Ditto. (gpgsm_format_keydesc): Do not translate the SUBJECT; we require it to stay UTF-8 but we still want to filter out bad control characters. * Makefile.am: Adjusted for gettext 0.14. * keylist.c (list_cert_colon): Make sure that the expired flag has a higher precedence than the invalid flag. 2004-09-29 Werner Koch * import.c (parse_p12): Write an error status line for bad passphrases. Add new arg CTRL and changed caller. * export.c (export_p12): Likewise. 2004-09-14 Werner Koch * certchain.c (gpgsm_validate_chain): Give expired certificates a higher error precedence and don't bother to check any CRL in that case. 2004-08-24 Werner Koch * certlist.c: Fixed typo in ocsp OID. 2004-08-18 Werner Koch * certlist.c (gpgsm_cert_use_ocsp_p): New. (cert_usage_p): Support it here. * call-dirmngr.c (gpgsm_dirmngr_isvalid): Use it here. 2004-08-17 Marcus Brinkmann * import.c: Fix typo in last change. 2004-08-17 Werner Koch * import.c (check_and_store): Do a full validation if --with-validation is set. * certchain.c (gpgsm_basic_cert_check): Print more detailed error messages. * certcheck.c (do_encode_md): Partly support DSA. Add new arg PKALGO. Changed all callers to pass it. (pk_algo_from_sexp): New. 2004-08-16 Werner Koch * gpgsm.c: New option --fixed-passphrase. * import.c (popen_protect_tool): Pass it to the protect-tool. * server.c (cmd_encrypt): Use DEFAULT_RECPLIST and not recplist for encrypt-to keys. 2004-08-06 Werner Koch * gpgsm.c: New option --with-ephemeral-keys. * keylist.c (list_internal_keys): Set it here. (list_cert_raw): And indicate those keys. Changed all our callers to pass the new arg HD through. 2004-07-23 Werner Koch * certreqgen.c (proc_parameters): Do not allow key length below 1024. 2004-07-22 Werner Koch * keylist.c (list_cert_raw): Print the keygrip. 2004-07-20 Werner Koch * certchain.c (gpgsm_validate_chain): The trust check didn't worked anymore, probably due to the changes at 2003-03-04. Fixed. 2004-06-06 Werner Koch * certreqgen.c (get_parameter_uint, create_request): Create an extension for key usage when requested. 2004-05-12 Werner Koch * gpgsm.c (main): Install emergency_cleanup also as an atexit handler. * verify.c (gpgsm_verify): Removed the separate error code handling for KSBA. We use shared error codes anyway. * export.c (export_p12): Removed debugging code. * encrypt.c (gpgsm_encrypt): Put the session key in to secure memory. 2004-05-11 Werner Koch * sign.c (gpgsm_sign): Include the error source in the final error message. * decrypt.c (gpgsm_decrypt): Ditto. * fingerprint.c (gpgsm_get_key_algo_info): New. * sign.c (gpgsm_sign): Don't assume RSA in the status line. * keylist.c (list_cert_colon): Really print the algorithm and key length. (list_cert_raw, list_cert_std): Ditto. (list_cert_colon): Reorganized to be able to tell whether a root certificate is trusted. * gpgsm.c: New option --debug-allow-core-dump. * gpgsm.h (opt): Add member CONFIG_FILENAME. * gpgsm.c (main): Use it here instead of the local var. * server.c (gpgsm_server): Print some additional information with the hello in verbose mode. 2004-04-30 Werner Koch * import.c (check_and_store): Do not update the stats for hidden imports of issuer certs. (popen_protect_tool): Request statusmessages from the protect-tool. (parse_p12): Detect status messages. Add new arg STATS and update them. (print_imported_summary): Include secret key stats. 2004-04-28 Werner Koch * gpgsm.c: New command --keydb-clear-some-cert-flags. * keydb.c (keydb_clear_some_cert_flags): New. (keydb_update_keyblock, keydb_set_flags): Change error code CONFLICT to NOT_LOCKED. 2004-04-26 Werner Koch * gpgsm.c (main) : Do not use /dev/null as default config filename. * call-agent.c (gpgsm_agent_pksign, gpgsm_agent_pkdecrypt) (gpgsm_agent_genkey, gpgsm_agent_istrusted) (gpgsm_agent_marktrusted, gpgsm_agent_havekey) (gpgsm_agent_passwd): Add new arg CTRL and changed all callers. (start_agent): New arg CTRL. Send progress item when starting a new agent. * sign.c (gpgsm_get_default_cert, get_default_signer): New arg CTRL to be passed down to the agent function. * decrypt.c (prepare_decryption): Ditto. * certreqgen.c (proc_parameters, read_parameters): Ditto. * certcheck.c (gpgsm_create_cms_signature): Ditto. 2004-04-23 Werner Koch * keydb.c (keydb_add_resource): Try to compress the file on init. * keylist.c (oidtranstbl): New. OIDs collected from several sources. (print_name_raw, print_names_raw, list_cert_raw): New. (gpgsm_list_keys): Check the dump mode and pass it down as necessary. 2004-04-22 Werner Koch * gpgsm.c (main): New commands --dump-keys, --dump-external-keys, --dump-secret-keys. 2004-04-13 Werner Koch * misc.c (setup_pinentry_env): New. * import.c (popen_protect_tool): Call it. * export.c (popen_protect_tool): Call it. 2004-04-08 Werner Koch * decrypt.c (gpgsm_decrypt): Return GPG_ERR_NO_DATA if it is not a encrypted message. 2004-04-07 Werner Koch * gpgsm.c: New option --force-crl-refresh. * call-dirmngr.c (gpgsm_dirmngr_isvalid): Pass option to dirmngr. 2004-04-05 Werner Koch * server.c (get_status_string): Add STATUS_NEWSIG. * verify.c (gpgsm_verify): Print STATUS_NEWSIG for each signature. * certchain.c (gpgsm_validate_chain) : Do not just warn if a cert is not suitable; bail out immediately. 2004-04-01 Werner Koch * call-dirmngr.c (isvalid_status_cb): New. (unhexify_fpr): New. Taken from ../g10/call-agent.c (gpgsm_dirmngr_isvalid): Add new arg CTRL, changed caller to pass it thru. Detect need to check the respondert cert and do that. * certchain.c (gpgsm_validate_chain): Add new arg FLAGS. Changed all callers. 2004-03-24 Werner Koch * sign.c (gpgsm_sign): Include a short list of capabilities. 2004-03-17 Werner Koch * gpgsm.c (main) : Fixed default value quoting. 2004-03-16 Werner Koch * gpgsm.c (main): Implemented --gpgconf-list. 2004-03-15 Werner Koch * keylist.c (list_cert_colon): Hack to set the expired flag. 2004-03-09 Werner Koch * gpgsm.c (main): Correctly intitialze USE_OCSP flag. * keydb.c (keydb_delete): s/GPG_ERR_CONFLICT/GPG_ERR_NOT_LOCKED/ 2004-03-04 Werner Koch * call-dirmngr.c (gpgsm_dirmngr_isvalid): New arg ISSUER_CERT. * certchain.c (is_cert_still_valid): New. Code moved from ... (gpgsm_validate_chain): ... here because we now need to check at two places and at a later stage, so that we can pass the issuer cert down to the dirmngr. 2004-03-03 Werner Koch * call-agent.c (start_agent): Replaced pinentry setup code by a call to a new common function. * certdump.c (gpgsm_format_keydesc): Make sure the string is returned as utf-8. * export.c (gpgsm_export): Make sure that we don't export more than one certificate. 2004-03-02 Werner Koch * export.c (create_duptable, destroy_duptable) (insert_duptable): New. (gpgsm_export): Avoid duplicates. 2004-02-26 Werner Koch * certchain.c (compare_certs): New. (gpgsm_validate_chain): Fixed infinite certificate checks after bad signatures. 2004-02-24 Werner Koch * keylist.c (list_cert_colon): Print the fingerprint as the cert-id for root certificates. 2004-02-21 Werner Koch * keylist.c (list_internal_keys): Return error codes. (list_external_keys, gpgsm_list_keys): Ditto. * server.c (do_listkeys): Ditto. * gpgsm.c (main): Display a key description for --passwd. * call-agent.c (gpgsm_agent_passwd): New arg DESC. 2004-02-20 Werner Koch * gpgsm.c (main): New option --debug-ignore-expiration. * certchain.c (gpgsm_validate_chain): Use it here. * certlist.c (cert_usage_p): Apply extKeyUsage. 2004-02-19 Werner Koch * export.c (export_p12, popen_protect_tool) (gpgsm_p12_export): New. * gpgsm.c (main): New command --export-secret-key-p12. 2004-02-18 Werner Koch * gpgsm.c (set_debug): Set the new --debug-level flags. (main): New option --gpgconf-list. (main): Do not setup -u and -r keys when not required. (main): Setup the used character set. * keydb.c (keydb_add_resource): Print a hint to start the gpg-agent. 2004-02-17 Werner Koch * gpgsm.c: Fixed value parsing for --with-validation. * call-agent.c (start_agent): Ignore an empty GPG_AGENT_INFO. * call-dirmngr.c (start_dirmngr): Likewise for DIRMNGR_INFO. * gpgsm.c: New option --with-md5-fingerprint. * keylist.c (list_cert_std): Print MD5 fpr. * gpgsm.c: New options --with-validation. * server.c (option_handler): New option "with-validation". * keylist.c (list_cert_std, list_internal_keys): New args CTRL and WITH_VALIDATION. Changed callers to set it. (list_external_cb, list_external_keys): Pass CTRL to the callback. (list_cert_colon): Add arg CTRL. Check validation if requested. * certchain.c (unknown_criticals, allowed_ca, check_cert_policy) (gpgsm_validate_chain): New args LISTMODE and FP. (do_list): New helper for info output. (find_up): New arg FIND_NEXT. (gpgsm_validate_chain): After a bad signature try again with other CA certificates. * import.c (print_imported_status): New arg NEW_CERT. Print additional STATUS_IMPORT_OK becuase that is what gpgme expects. (check_and_store): Always call above function after import. * server.c (get_status_string): Added STATUS_IMPORT_OK. 2004-02-13 Werner Koch * certcheck.c (gpgsm_create_cms_signature): Format a description for use by the pinentry. * decrypt.c (gpgsm_decrypt): Ditto. Free HEXKEYGRIP. * certdump.c (format_name_cookie, format_name_writer) (gpgsm_format_name): New. (gpgsm_format_serial): New. (gpgsm_format_keydesc): New. * call-agent.c (gpgsm_agent_pksign): New arg DESC. (gpgsm_agent_pkdecrypt): Ditto. * encrypt.c (init_dek): Check for too weak algorithms. * import.c (parse_p12, popen_protect_tool): New. * base64.c (gpgsm_create_reader): New arg ALLOW_MULTI_PEM. Changed all callers. (base64_reader_cb): Handle it here. (gpgsm_reader_eof_seen): New. (base64_reader_cb): Set a flag for EOF. (simple_reader_cb): Ditto. 2004-02-12 Werner Koch * gpgsm.h, gpgsm.c: New option --protect-tool-program. * gpgsm.c (run_protect_tool): Use it. 2004-02-11 Werner Koch * Makefile.am (AM_CPPFLAGS): Pass directory constants via -D; this will allow to override directory names at make time. 2004-02-02 Werner Koch * import.c (check_and_store): Import certificates even with missing issuer's cert. Fixed an "depending on the verbose setting" bug. * certchain.c (gpgsm_validate_chain): Mark revoked certs in the keybox. * keylist.c (list_cert_colon): New arg VALIDITY; use it to print a revoked flag. (list_internal_keys): Retrieve validity flag. (list_external_cb): Pass 0 as validity flag. * keydb.c (keydb_get_flags, keydb_set_flags): New. (keydb_set_cert_flags): New. (lock_all): Return a proper error code. (keydb_lock): New. (keydb_delete): Don't lock but check that it has been locked. (keydb_update_keyblock): Ditto. * delete.c (delete_one): Take a lock. 2004-01-30 Werner Koch * certchain.c (check_cert_policy): Fixed read error checking. (check_cert_policy): With no critical policies issue only a warning if the policy file does not exists. * sign.c (add_certificate_list): Decrement N for the first cert. 2004-01-29 Werner Koch * certdump.c (parse_dn_part): Map common OIDs to human readable labels. Make sure that a value won't get truncated if it includes a Nul. 2004-01-28 Werner Koch * certchain.c (gpgsm_validate_chain): Changed the message printed for an untrusted root certificate. 2004-01-27 Werner Koch * certdump.c (parse_dn_part): Pretty print the nameDistinguisher OID. (print_dn_part): Do not delimit multiple RDN by " + ". Handle multi-valued RDNs in a special way, i.e. in the order specified by the certificate. (print_dn_parts): Simplified. 2004-01-16 Werner Koch * sign.c (gpgsm_sign): Print an error message on all failures. * decrypt.c (gpgsm_decrypt): Ditto. 2003-12-17 Werner Koch * server.c (gpgsm_server): Add arg DEFAULT_RECPLIST. (cmd_encrypt): Add all enrypt-to marked certs to the list. * encrypt.c (gpgsm_encrypt): Check that real recipients are available. * gpgsm.c (main): Make the --encrypt-to and --no-encrypt-to options work. Pass the list of recients to gpgsm_server. * gpgsm.h (certlist_s): Add field IS_ENCRYPT_TO. (opt): Add NO_ENCRYPT_TO. * certlist.c (gpgsm_add_to_certlist): New arg IS_ENCRYPT_TO. Changed all callers and ignore duplicate entries. (is_cert_in_certlist): New. (gpgsm_add_cert_to_certlist): New. * certdump.c (gpgsm_print_serial): Cleaned up cast use in strtoul. (gpgsm_dump_serial): Ditto. * decrypt.c (gpgsm_decrypt): Replaced ERR by RC. 2003-12-16 Werner Koch * gpgsm.c (main): Set the prefixes for assuan logging. * sign.c (gpgsm_sign): Add validation checks for the default certificate. * gpgsm.c: Add -k as alias for --list-keys and -K for --list-secret-keys. 2003-12-15 Werner Koch * encrypt.c (init_dek): Use gry_create_nonce for the IV; there is not need for real strong random here and it even better protect the random bits used for the key. 2003-12-01 Werner Koch * gpgsm.c, gpgsm.h: New options --{enable,disable}-ocsp. (gpgsm_init_default_ctrl): Set USE_OCSP to the default value. * certchain.c (gpgsm_validate_chain): Handle USE_OCSP. * call-dirmngr.c (gpgsm_dirmngr_isvalid): Add arg USE_OCSP and proceed accordingly. 2003-11-19 Werner Koch * verify.c (gpgsm_verify): Use "0" instead of an empty string for the VALIDSIG status. 2003-11-18 Werner Koch * verify.c (gpgsm_verify): Fixed for changes API of gcry_md_info. * certchain.c (unknown_criticals): Fixed an error code test. 2003-11-12 Werner Koch Adjusted for API changes in Libksba. 2003-10-31 Werner Koch * certchain.c (gpgsm_validate_chain): Changed to use ksba_isotime_t. * verify.c (strtimestamp_r, gpgsm_verify): Ditto. * sign.c (gpgsm_sign): Ditto. * keylist.c (print_time, list_cert_std, list_cert_colon): Ditto. * certdump.c (gpgsm_print_time, gpgsm_dump_time, gpgsm_dump_cert): Ditto. 2003-10-25 Werner Koch * certreqgen.c (read_parameters): Fixed faulty of !spacep(). 2003-08-20 Marcus Brinkmann * encrypt.c (encode_session_key): Allocate enough space. Cast key byte to unsigned char to prevent sign extension. (encrypt_dek): Check return value before error. 2003-08-14 Timo Schulz * encrypt.c (encode_session_key): Use new Libgcrypt interface. 2003-07-31 Werner Koch * Makefile.am (gpgsm_LDADD): Added INTLLIBS. 2003-07-29 Werner Koch * gpgsm.c (main): Add secmem features and set the random seed file. (gpgsm_exit): Update the random seed file and enable debug output. 2003-07-27 Werner Koch Adjusted for gcry_mpi_print and gcry_mpi_scan API change. 2003-06-24 Werner Koch * server.c (gpgsm_status_with_err_code): New. * verify.c (gpgsm_verify): Use it here instead of the old tokenizing version. * verify.c (strtimestamp): Renamed to strtimestamp_r Adjusted for changes in the libgcrypt API. Some more fixes for the libgpg-error stuff. 2003-06-04 Werner Koch * call-agent.c (init_membuf,put_membuf,get_membuf): Removed. Include new membuf header and changed used type. Renamed error codes from INVALID to INV and removed _ERROR suffixes. 2003-06-03 Werner Koch Changed all error codes in all files to the new libgpg-error scheme. * gpgsm.h: Include gpg-error.h . * Makefile.am: Link with libgpg-error. 2003-04-29 Werner Koch * Makefile.am: Use libassuan. Don't override LDFLAGS anymore. * server.c (register_commands): Adjust for new Assuan semantics. 2002-12-03 Werner Koch * call-agent.c (gpgsm_agent_passwd): New. * gpgsm.c (main): New command --passwd and --call-protect-tool (run_protect_tool): New. 2002-11-25 Werner Koch * verify.c (gpgsm_verify): Handle content-type attribute. 2002-11-13 Werner Koch * call-agent.c (start_agent): Try to use $GPG_TTY instead of ttyname. Changed ttyname to test stdin becuase it can be assumed that output redirection is more common that input redirection. 2002-11-12 Werner Koch * gpgsm.c: New command --call-dirmngr. * call-dirmngr.c (gpgsm_dirmngr_run_command) (run_command_inq_cb,run_command_cb) (run_command_status_cb): New. 2002-11-11 Werner Koch * certcheck.c (gpgsm_check_cms_signature): Don't double free s_sig but free s_pkey at leave. 2002-11-10 Werner Koch * gpgsm.c: Removed duplicate --list-secret-key entry. 2002-09-19 Werner Koch * certcheck.c (gpgsm_check_cert_sig): Add cert hash debugging. * certchain.c (find_up): Print info when the cert was not found by the autorithyKeyIdentifier. 2002-09-03 Werner Koch * gpgsm.c (main): Disable the internal libgcrypt locking. 2002-08-21 Werner Koch * import.c (print_imported_summary): Cleaned up. Print new not_imported value. (check_and_store): Update non_imported counter. (print_import_problem): New. (check_and_store): Print error status message. * server.c (get_status_string): Added STATUS_IMPORT_PROBLEM. 2002-08-20 Werner Koch * gpgsm.c (main): Use the log file only in server mode. * import.c (print_imported_summary): New. (check_and_store): Update the counters, take new argument. (import_one): Factored out core of gpgsm_import. (gpgsm_import): Print counters. (gpgsm_import_files): New. * gpgsm.c (main): Use the new function for import. 2002-08-19 Werner Koch * decrypt.c (gpgsm_decrypt): Return a better error status token. * verify.c (gpgsm_verify): Don't error on messages with no signing time or no message digest. This is only the case for messages without any signed attributes. 2002-08-16 Werner Koch * certpath.c: Renamed to .. * certchain.c: this. Renamed all all other usages of "path" in the context of certificates to "chain". * call-agent.c (learn_cb): Special treatment when the issuer certificate is missing. 2002-08-10 Werner Koch * Makefile.am (INCLUDES): Add definition for localedir. * keylist.c (list_cert_colon): Print the short fingerprint in the key ID field. * fingerprint.c (gpgsm_get_short_fingerprint): New. * verify.c (gpgsm_verify): Print more verbose info for a good signature. 2002-08-09 Werner Koch * decrypt.c (prepare_decryption): Hack to detected already unpkcsedone keys. * gpgsm.c (emergency_cleanup): New. (main): Initialize the signal handler. * sign.c (gpgsm_sign): Reset the hash context for subsequent signers and release it at the end. 2002-08-05 Werner Koch * server.c (cmd_signer): New command "SIGNER" (register_commands): Register it. (cmd_sign): Pass the signer list to gpgsm_sign. * certlist.c (gpgsm_add_to_certlist): Add SECRET argument, check for secret key if set and changed all callers. * sign.c (gpgsm_sign): New argument SIGNERLIST and implemt multiple signers. * gpgsm.c (main): Support more than one -u. * server.c (cmd_recipient): Return reason code 1 for No_Public_Key which is actually what gets returned from add_to_certlist. 2002-07-26 Werner Koch * certcheck.c (gpgsm_check_cert_sig): Implement proper cleanup. (gpgsm_check_cms_signature): Ditto. 2002-07-22 Werner Koch * keydb.c (keydb_add_resource): Register a lock file. (lock_all, unlock_all): Implemented. * delete.c: New. * gpgsm.c: Made --delete-key work. * server.c (cmd_delkeys): New. (register_commands): New command DELKEYS. * decrypt.c (gpgsm_decrypt): Print a convenience note when RC2 is used and a STATUS_ERROR with the algorithm oid. 2002-07-03 Werner Koch * server.c (gpgsm_status2): Insert a blank between all optional arguments when using assuan. * server.c (cmd_recipient): No more need for extra blank in constants. * import.c (print_imported_status): Ditto. * gpgsm.c (main): Ditto. 2002-07-02 Werner Koch * verify.c (gpgsm_verify): Extend the STATUS_BADSIG line with the fingerprint. * certpath.c (check_cert_policy): Don't use log_error to print a warning. * keydb.c (keydb_store_cert): Add optional ar EXISTED and changed all callers. * call-agent.c (learn_cb): Print info message only for real imports. * import.c (gpgsm_import): Moved duplicated code to ... (check_and_store): new function. Added magic to import the entire chain. Print status only for real imports and moved printing code to .. (print_imported_status): New. * call-dirmngr.c (gpgsm_dirmngr_isvalid): print status of dirmngr call in very verbose mode. * gpgsm.c (main): Use the same error codes for STATUS_INV_RECP as with the server mode. 2002-06-29 Werner Koch * gpgsm.c: New option --auto-issuer-key-retrieve. * certpath.c (find_up): Try to retrieve an issuer key from an external source and from the ephemeral key DB. (find_up_store_certs_cb): New. * keydb.c (keydb_set_ephemeral): Does now return the old state. Call the backend only when required. * call-dirmngr.c (start_dirmngr): Use GNUPG_DEFAULT_DIRMNGR. (lookup_status_cb): Issue status only when CTRL is not NULL. (gpgsm_dirmngr_lookup): Document that CTRL is optional. * call-agent.c (start_agent): Use GNUPG_DEFAULT_AGENT. 2002-06-28 Werner Koch * server.c (cmd_recipient): Add more reason codes. 2002-06-27 Werner Koch * certpath.c (gpgsm_basic_cert_check): Use --debug-no-path-validation to also bypass this basic check. * gpgsm.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. * call-agent.c (start_agent): Create and pass the list of FD to keep in the child to assuan. * call-dirmngr.c (start_dirmngr): Ditto. 2002-06-26 Werner Koch * import.c (gpgsm_import): Print an STATUS_IMPORTED. * gpgsm.c: --debug-no-path-validation does not take an argument. 2002-06-25 Werner Koch * certdump.c (print_dn_part): Always print a leading slash, removed NEED_DELIM arg and changed caller. * export.c (gpgsm_export): Print LFs to FP and not stdout. (print_short_info): Ditto. Make use of gpgsm_print_name. * server.c (cmd_export): Use output-fd instead of data lines; this was actually the specified way. 2002-06-24 Werner Koch * gpgsm.c: Removed duped help entry for --list-keys. * gpgsm.c, gpgsm.h: New option --debug-no-path-validation. * certpath.c (gpgsm_validate_path): Use it here instead of the debug flag hack. * certpath.c (check_cert_policy): Return No_Policy_Match if the policy file could not be opened. 2002-06-20 Werner Koch * certlist.c (gpgsm_add_to_certlist): Fixed locating of a certificate with the required key usage. * gpgsm.c (main): Fixed a segv when using --outfile without an argument. * keylist.c (print_capabilities): Also check for non-repudiation and data encipherment. * certlist.c (cert_usage_p): Test for signing and encryption was swapped. Add a case for certification usage, handle non-repudiation and data encipherment. (gpgsm_cert_use_cert_p): New. (gpgsm_add_to_certlist): Added a CTRL argument and changed all callers to pass it. * certpath.c (gpgsm_validate_path): Use it here to print a status message. Added a CTRL argument and changed all callers to pass it. * decrypt.c (gpgsm_decrypt): Print a status message for wrong key usage. * verify.c (gpgsm_verify): Ditto. * keydb.c (classify_user_id): Allow a colon delimited fingerprint. 2002-06-19 Werner Koch * call-agent.c (learn_cb): Use log_info instead of log_error on successful import. * keydb.c (keydb_set_ephemeral): New. (keydb_store_cert): New are ephemeral, changed all callers. * keylist.c (list_external_cb): Store cert as ephemeral. * export.c (gpgsm_export): Kludge to export epehmeral certificates. * gpgsm.c (main): New command --list-external-keys. 2002-06-17 Werner Koch * certreqgen.c (read_parameters): Improved error handling. (gpgsm_genkey): Print error message. 2002-06-13 Werner Koch * gpgsm.c (main): New option --log-file. 2002-06-12 Werner Koch * call-dirmngr.c (lookup_status_cb): New. (gpgsm_dirmngr_lookup): Use the status CB. Add new arg CTRL and changed caller to pass it. * gpgsm.c (open_fwrite): New. (main): Allow --output for --verify. * sign.c (hash_and_copy_data): New. (gpgsm_sign): Implemented normal (non-detached) signatures. * gpgsm.c (main): Ditto. * certpath.c (gpgsm_validate_path): Special error handling for no policy match. 2002-06-10 Werner Koch * server.c (get_status_string): Add STATUS_ERROR. * certpath.c (gpgsm_validate_path): Tweaked the error checking to return error codes in a more sensitive way. * verify.c (gpgsm_verify): Send status TRUST_NEVER also for a bad CA certificate and when the certificate has been revoked. Issue TRUST_FULLY even when the cert has expired. Append an error token to these status lines. Issue the new generic error status when a cert was not found and when leaving the function. 2002-06-04 Werner Koch * gpgsm.c (main): New command --list-sigs * keylist.c (list_cert_std): New. Use it whenever colon mode is not used. (list_cert_chain): New. 2002-05-31 Werner Koch * gpgsm.c (main): Don't print the "go ahead" message for an invalid command. 2002-05-23 Werner Koch * import.c (gpgsm_import): Add error messages. 2002-05-21 Werner Koch * keylist.c (list_internal_keys): Renamed from gpgsm_list_keys. (list_external_keys): New. (gpgsm_list_keys): Dispatcher for above. * call-dirmngr.c (lookup_cb,pattern_from_strlist) (gpgsm_dirmngr_lookup): New. * server.c (option_handler): Handle new option --list-mode. (do_listkeys): Handle options and actually use the mode argument. (get_status_string): New code TRUNCATED. * import.c (gpgsm_import): Try to identify the type of input and handle certs-only messages. 2002-05-14 Werner Koch * gpgsm.c: New option --faked-system-time * sign.c (gpgsm_sign): And use it here. * certpath.c (gpgsm_validate_path): Ditto. 2002-05-03 Werner Koch * certpath.c (gpgsm_validate_path): Added EXPTIME arg and changed all callers. * verify.c (gpgsm_verify): Tweaked usage of log_debug and log_error. Return EXPSIG status and add expiretime to VALIDSIG. 2002-04-26 Werner Koch * gpgsm.h (DBG_AGENT,DBG_AGENT_VALUE): Replaced by DBG_ASSUAN_*. Changed all users. * call-agent.c (start_agent): Be more silent without -v. * call-dirmngr.c (start_dirmngr): Ditto. 2002-04-25 Werner Koch * call-agent.c (start_agent): Make copies of old locales and check for setlocale. 2002-04-25 Marcus Brinkmann * call-agent.c (start_agent): Fix error handling logic so the locale is always correctly reset. 2002-04-25 Marcus Brinkmann * server.c (option_handler): Accept display, ttyname, ttytype, lc_ctype and lc_messages options. * gpgsm.c (main): Allocate memory for these options. * gpgsm.h (struct opt): Make corresponding members non-const. 2002-04-24 Marcus Brinkmann * gpgsm.h (struct opt): New members display, ttyname, ttytype, lc_ctype, lc_messages. * gpgsm.c (enum cmd_and_opt_values): New members oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages. (opts): New entries for these options. (main): Handle these new options. * call-agent.c (start_agent): Set the various display and tty parameter after resetting. 2002-04-18 Werner Koch * certreqgen.c (gpgsm_genkey): Write status output on success. 2002-04-15 Werner Koch * gpgsm.c (main): Check ksba version. * certpath.c (find_up): New to use the authorithKeyIdentifier. Use it in all other functions to locate the signing cert.. 2002-04-11 Werner Koch * certlist.c (cert_usable_p): New. (gpgsm_cert_use_sign_p,gpgsm_cert_use_encrypt_p): New. (gpgsm_cert_use_verify_p,gpgsm_cert_use_decrypt_p): New. (gpgsm_add_to_certlist): Check the key usage. * sign.c (gpgsm_sign): Ditto. * verify.c (gpgsm_verify): Print a message wehn an unsuitable certificate was used. * decrypt.c (gpgsm_decrypt): Ditto * keylist.c (print_capabilities): Determine values from the cert. 2002-03-28 Werner Koch * keylist.c (list_cert_colon): Fixed listing of crt record; the issuer is not at the right place. Print a chainingID. * certpath.c (gpgsm_walk_cert_chain): Be a bit more silent on common errors. 2002-03-21 Werner Koch * export.c: New. * gpgsm.c: Add command --export. * server.c (cmd_export): New. 2002-03-13 Werner Koch * decrypt.c (gpgsm_decrypt): Allow multiple recipients. 2002-03-12 Werner Koch * certpath.c (check_cert_policy): Print the policy list. * verify.c (gpgsm_verify): Detect certs-only message. 2002-03-11 Werner Koch * import.c (gpgsm_import): Print a notice about imported certificates when in verbose mode. * gpgsm.c (main): Print INV_RECP status. * server.c (cmd_recipient): Ditto. * server.c (gpgsm_status2): New. Allows for a list of strings. (gpgsm_status): Divert to gpgsm_status2. * encrypt.c (gpgsm_encrypt): Don't use a default key when no recipients are given. Print a NO_RECP status. 2002-03-06 Werner Koch * server.c (cmd_listkeys, cmd_listsecretkeys): Divert to (do_listkeys): new. Add pattern parsing. * keylist.c (gpgsm_list_keys): Handle selection pattern. * gpgsm.c: New command --learn-card * call-agent.c (learn_cb,gpgsm_agent_learn): New. * gpgsm.c (main): Print error messages for non-implemented commands. * base64.c (base64_reader_cb): Use case insensitive compare of the Content-Type string to detect plain base-64. 2002-03-05 Werner Koch * gpgsm.c, gpgsm.h: Add local_user. * sign.c (gpgsm_get_default_cert): New. (get_default_signer): Use the new function if local_user is not set otherwise used that value. * encrypt.c (get_default_recipient): Removed. (gpgsm_encrypt): Use gpgsm_get_default_cert. * verify.c (gpgsm_verify): Better error text for a bad signature found by comparing the hashs. 2002-02-27 Werner Koch * call-dirmngr.c, call-agent.c: Add 2 more arguments to all uses of assuan_transact. 2002-02-25 Werner Koch * server.c (option_handler): Allow to use -2 for "send all certs except the root cert". * sign.c (add_certificate_list): Implement it here. * certpath.c (gpgsm_is_root_cert): New. 2002-02-19 Werner Koch * certpath.c (check_cert_policy): New. (gpgsm_validate_path): And call it from here. * gpgsm.c (main): New options --policy-file, --disable-policy-checks and --enable-policy-checks. * gpgsm.h (opt): Added policy_file, no_policy_checks. 2002-02-18 Werner Koch * certpath.c (gpgsm_validate_path): Ask the agent to add the certificate into the trusted list. * call-agent.c (gpgsm_agent_marktrusted): New. 2002-02-07 Werner Koch * certlist.c (gpgsm_add_to_certlist): Check that the specified name identifies a certificate unambiguously. (gpgsm_find_cert): Ditto. * server.c (cmd_listkeys): Check that the data stream is available. (cmd_listsecretkeys): Ditto. (has_option): New. (cmd_sign): Fix ambiguousity in option recognition. * gpgsm.c (main): Enable --logger-fd. * encrypt.c (gpgsm_encrypt): Increased buffer size for better performance. * call-agent.c (gpgsm_agent_pksign): Check the S-Exp received from the agent. * keylist.c (list_cert_colon): Filter out control characters. 2002-02-06 Werner Koch * decrypt.c (gpgsm_decrypt): Bail out after an decryption error. * server.c (reset_notify): Close input and output FDs. (cmd_encrypt,cmd_decrypt,cmd_verify,cmd_sign.cmd_import) (cmd_genkey): Close the FDs and release the recipient list even in the error case. 2002-02-01 Marcus Brinkmann * sign.c (gpgsm_sign): Do not release certificate twice. 2002-01-29 Werner Koch * call-agent.c (gpgsm_agent_havekey): New. * keylist.c (list_cert_colon): New arg HAVE_SECRET, print "crs" when we know that the secret key is available. (gpgsm_list_keys): New arg MODE, check whether a secret key is available. Changed all callers. * gpgsm.c (main): New command --list-secret-keys. * server.c (cmd_listsecretkeys): New. (cmd_listkeys): Return secret keys with "crs" record. 2002-01-28 Werner Koch * certreqgen.c (create_request): Store the email address in the req. 2002-01-25 Werner Koch * gpgsm.c (main): Disable core dumps. * sign.c (add_certificate_list): New. (gpgsm_sign): Add the certificates to the CMS object. * certpath.c (gpgsm_walk_cert_chain): New. * gpgsm.h (server_control_s): Add included_certs. * gpgsm.c: Add option --include-certs. (gpgsm_init_default_ctrl): New. (main): Call it. * server.c (gpgsm_server): Ditto. (option_handler): Support --include-certs. 2002-01-23 Werner Koch * certpath.c (gpgsm_validate_path): Print the DN of a missing issuer. * certdump.c (gpgsm_dump_string): New. (print_dn): Replaced by above. 2002-01-22 Werner Koch * certpath.c (unknown_criticals): New. (allowed_ca): New. (gpgsm_validate_path): Check validity, CA attribute, path length and unknown critical extensions. 2002-01-21 Werner Koch * gpgsm.c: Add option --enable-crl-checks. * call-agent.c (start_agent): Implemented socket based access. * call-dirmngr.c (start_dirmngr): Ditto. 2002-01-20 Werner Koch * server.c (option_handler): New. (gpgsm_server): Register it with assuan. 2002-01-19 Werner Koch * server.c (gpgsm_server): Use assuan_deinit_server and setup assuan logging if enabled. * call-agent.c (inq_ciphertext_cb): Don't show the session key in an Assuan log file. * gpgsm.c (my_strusage): Take bugreport address from configure.ac 2002-01-15 Werner Koch * import.c (gpgsm_import): Just do a basic cert check before storing it. * certpath.c (gpgsm_basic_cert_check): New. * keydb.c (keydb_store_cert): New. * import.c (store_cert): Removed and change all caller to use the new function. * verify.c (store_cert): Ditto. * certlist.c (gpgsm_add_to_certlist): Validate the path * certpath.c (gpgsm_validate_path): Check the trust list. * call-agent.c (gpgsm_agent_istrusted): New. 2002-01-14 Werner Koch * call-dirmngr.c (inq_certificate): Changed for new interface semantic. * certlist.c (gpgsm_find_cert): New. 2002-01-13 Werner Koch * fingerprint.c (gpgsm_get_certid): Print the serial and not the hash after the dot. 2002-01-11 Werner Koch * call-dirmngr.c: New. * certpath.c (gpgsm_validate_path): Check the CRL here. * fingerprint.c (gpgsm_get_certid): New. * gpgsm.c: New options --dirmngr-program and --disable-crl-checks. 2002-01-10 Werner Koch * base64.c (gpgsm_create_writer): Allow to set the object name 2002-01-08 Werner Koch * keydb.c (spacep): Removed because it is now in util.c * server.c (cmd_genkey): New. * certreqgen.c: New. The parameter handling code has been taken from gnupg/g10/keygen.c version 1.0.6. * call-agent.c (gpgsm_agent_genkey): New. 2002-01-02 Werner Koch * server.c (rc_to_assuan_status): Removed and changed all callers to use map_to_assuan_status. 2001-12-20 Werner Koch * verify.c (gpgsm_verify): Implemented non-detached signature verification. Add OUT_FP arg, initialize a writer and changed all callers. * server.c (cmd_verify): Pass an out_fp if one has been set. * base64.c (base64_reader_cb): Try to detect an S/MIME body part. * certdump.c (print_sexp): Renamed to gpgsm_dump_serial, made global. (print_time): Renamed to gpgsm_dump_time, made global. (gpgsm_dump_serial): Take a real S-Expression as argument and print the first item. * keylist.c (list_cert_colon): Ditto. * keydb.c (keydb_search_issuer_sn): Ditto. * decrypt.c (print_integer_sexp): Removed and made callers use gpgsm_dump_serial. * verify.c (print_time): Removed, made callers use gpgsm_dump_time. 2001-12-19 Marcus Brinkmann * call-agent.c (start_agent): Add new argument to assuan_pipe_connect. 2001-12-18 Werner Koch * verify.c (print_integer_sexp): Renamed from print_integer and print the serial number according to the S-Exp rules. * decrypt.c (print_integer_sexp): Ditto. 2001-12-17 Werner Koch * keylist.c (list_cert_colon): Changed for new return value of get_serial. * keydb.c (keydb_search_issuer_sn): Ditto. * certcheck.c (gpgsm_check_cert_sig): Likewise for other S-Exp returingin functions. * fingerprint.c (gpgsm_get_keygrip): Ditto. * encrypt.c (encrypt_dek): Ditto * certcheck.c (gpgsm_check_cms_signature): Ditto * decrypt.c (prepare_decryption): Ditto. * call-agent.c (gpgsm_agent_pkdecrypt): Removed arg ciphertextlen, use KsbaSexp type and calculate the length. * certdump.c (print_sexp): Remaned from print_integer, changed caller. * Makefile.am: Use the LIBGCRYPT and LIBKSBA variables. * fingerprint.c (gpgsm_get_keygrip): Use the new gcry_pk_get_keygrip to calculate the grip - note the algorithm and therefore the grip values changed. 2001-12-15 Werner Koch * certcheck.c (gpgsm_check_cms_signature): Removed the faked-key kludge. (gpgsm_create_cms_signature): Removed the commented fake key code. This makes the function pretty simple. * gpgsm.c (main): Renamed the default key database to "keyring.kbx". * decrypt.c (gpgsm_decrypt): Write STATUS_DECRYPTION_*. * sign.c (gpgsm_sign): Write a STATUS_SIG_CREATED. 2001-12-14 Werner Koch * keylist.c (list_cert_colon): Kludge to show an email address encoded in the subject's DN. * verify.c (gpgsm_verify): Add hash debug helpers * sign.c (gpgsm_sign): Ditto. * base64.c (base64_reader_cb): Reset the linelen when we need to skip the line and adjusted test; I somehow forgot about DeMorgan. * server.c (cmd_encrypt,cmd_decrypt,cmd_sign,cmd_verify) (cmd_import): Close the FDs on success. (close_message_fd): New. (input_notify): Setting autodetect_encoding to 0 after initializing it to 0 is pretty pointless. Easy to fix. * gpgsm.c (main): New option --debug-wait n, so that it is possible to attach gdb when used in server mode. * sign.c (get_default_signer): Use keydb_classify_name here. 2001-12-14 Marcus Brinkmann * call-agent.c (LINELENGTH): Removed. (gpgsm_agent_pksign): Use ASSUAN_LINELENGTH, not LINELENGTH. (gpgsm_agent_pkdecrypt): Likewise. 2001-12-13 Werner Koch * keylist.c (list_cert_colon): Print alternative names of subject and a few other values. 2001-12-12 Werner Koch * gpgsm.c (main): New options --assume-{armor,base64,binary}. * base64.c (base64_reader_cb): Fixed non-autodetection mode. 2001-12-04 Werner Koch * call-agent.c (read_from_agent): Check for inquire responses. (request_reply): Handle them using a new callback arg, changed all callers. (gpgsm_agent_pkdecrypt): New. 2001-11-27 Werner Koch * base64.c: New. Changed all other functions to use this instead of direct creation of ksba_reader/writer. * gpgsm.c (main): Set ctrl.auto_encoding unless --no-armor is used. 2001-11-26 Werner Koch * gpgsm.c: New option --agent-program * call-agent.c (start_agent): Allow to override the default path to the agent. * keydb.c (keydb_add_resource): Create keybox * keylist.c (gpgsm_list_keys): Fixed non-server keylisting. * server.c (rc_to_assuan_status): New. Use it for all commands. Copyright 2001, 2002, 2003, 2004, 2005 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/sm/Makefile.am b/sm/Makefile.am index b5882ae1d..be53e8d25 100644 --- a/sm/Makefile.am +++ b/sm/Makefile.am @@ -1,61 +1,62 @@ # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in bin_PROGRAMS = gpgsm AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS) \ $(PTH_CFLAGS) AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common -I$(top_srcdir)/intl include $(top_srcdir)/am/cmacros.am gpgsm_SOURCES = \ gpgsm.c gpgsm.h \ misc.c \ keydb.c keydb.h \ server.c \ call-agent.c \ call-dirmngr.c \ fingerprint.c \ base64.c \ certlist.c \ certdump.c \ certcheck.c \ certchain.c \ keylist.c \ verify.c \ sign.c \ encrypt.c \ decrypt.c \ import.c \ export.c \ delete.c \ certreqgen.c \ qualified.c gpgsm_LDADD = ../jnlib/libjnlib.a ../kbx/libkeybox.a \ ../common/libcommon.a ../gl/libgnu.a \ $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) -lgpg-error \ $(LIBINTL) $(PTH_LIBS) $(ZLIBS) diff --git a/sm/base64.c b/sm/base64.c index 62c2c9ad9..59ab6f24b 100644 --- a/sm/base64.c +++ b/sm/base64.c @@ -1,657 +1,658 @@ /* base64.c * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include "i18n.h" #ifdef HAVE_DOSISH_SYSTEM #define LF "\r\n" #else #define LF "\n" #endif /* data used by the reader callbacks */ struct reader_cb_parm_s { FILE *fp; unsigned char line[1024]; int linelen; int readpos; int have_lf; unsigned long line_counter; int allow_multi_pem; /* Allow processing of multiple PEM objects. */ int autodetect; /* Try to detect the input encoding. */ int assume_pem; /* Assume input encoding is PEM. */ int assume_base64; /* Assume input is base64 encoded. */ int identified; int is_pem; int is_base64; int stop_seen; int might_be_smime; int eof_seen; struct { int idx; unsigned char val; int stop_seen; } base64; }; /* data used by the writer callbacks */ struct writer_cb_parm_s { FILE *fp; const char *pem_name; int wrote_begin; int did_finish; struct { int idx; int quad_count; unsigned char radbuf[4]; } base64; }; /* context for this module's functions */ struct base64_context_s { union { struct reader_cb_parm_s rparm; struct writer_cb_parm_s wparm; } u; }; /* The base-64 character list */ static char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /* The reverse base-64 list */ static unsigned char asctobin[256] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static int has_only_base64 (const unsigned char *line, int linelen) { if (linelen < 20) return 0; for (; linelen; line++, linelen--) { if (*line == '\n' || (linelen > 1 && *line == '\r' && line[1] == '\n')) break; if ( !strchr (bintoasc, *line) ) return 0; } return 1; /* yes */ } static int is_empty_line (const unsigned char *line, int linelen) { if (linelen >= 2 && *line == '\r' && line[1] == '\n') return 1; if (linelen >= 1 && *line == '\n') return 1; return 0; } static int base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct reader_cb_parm_s *parm = cb_value; size_t n; int c, c2; *nread = 0; if (!buffer) return -1; /* not supported */ next: if (!parm->linelen) { /* read an entire line or up to the size of the buffer */ parm->line_counter++; parm->have_lf = 0; for (n=0; n < DIM(parm->line);) { c = getc (parm->fp); if (c == EOF) { parm->eof_seen = 1; if (ferror (parm->fp)) return -1; break; } parm->line[n++] = c; if (c == '\n') { parm->have_lf = 1; /* Fixme: we need to skip overlong lines while detecting the dashed lines */ break; } } parm->linelen = n; if (!n) return -1; /* eof */ parm->readpos = 0; } if (!parm->identified) { if (!parm->autodetect) { if (parm->assume_pem) { /* wait for the header line */ parm->linelen = parm->readpos = 0; if (!parm->have_lf || strncmp ((char*)parm->line, "-----BEGIN ", 11) || !strncmp ((char*)parm->line+11, "PGP ", 4)) goto next; parm->is_pem = 1; } else if (parm->assume_base64) parm->is_base64 = 1; } else if (parm->line_counter == 1 && !parm->have_lf) { /* first line too long - assume DER encoding */ parm->is_pem = 0; } else if (parm->line_counter == 1 && parm->linelen && *parm->line == 0x30) { /* the very first byte does pretty much look like a SEQUENCE tag*/ parm->is_pem = 0; } else if ( parm->have_lf && !strncmp ((char*)parm->line, "-----BEGIN ", 11) && strncmp ((char *)parm->line+11, "PGP ", 4) ) { /* Fixme: we must only compare if the line really starts at the beginning */ parm->is_pem = 1; parm->linelen = parm->readpos = 0; } else if ( parm->have_lf && parm->line_counter == 1 && parm->linelen >= 13 && !ascii_memcasecmp (parm->line, "Content-Type:", 13)) { /* might be a S/MIME body */ parm->might_be_smime = 1; parm->linelen = parm->readpos = 0; goto next; } else if (parm->might_be_smime == 1 && is_empty_line (parm->line, parm->linelen)) { parm->might_be_smime = 2; parm->linelen = parm->readpos = 0; goto next; } else if (parm->might_be_smime == 2) { parm->might_be_smime = 0; if ( !has_only_base64 (parm->line, parm->linelen)) { parm->linelen = parm->readpos = 0; goto next; } parm->is_pem = 1; } else { parm->linelen = parm->readpos = 0; goto next; } parm->identified = 1; parm->base64.stop_seen = 0; parm->base64.idx = 0; } n = 0; if (parm->is_pem || parm->is_base64) { if (parm->is_pem && parm->have_lf && !strncmp ((char*)parm->line, "-----END ", 9)) { parm->identified = 0; parm->linelen = parm->readpos = 0; /* If the caller want to read multiple PEM objects from one file, we have to reset our internal state and return a EOF immediately. The caller is the expected to use ksba_reader_clear to clear the EOF condition and continue to read. If we don't want to do that we just return 0 bytes which will force the ksba_reader to skip until EOF. */ if (parm->allow_multi_pem) { parm->identified = 0; parm->autodetect = 0; parm->assume_pem = 1; parm->stop_seen = 0; return -1; /* Send EOF now. */ } } else if (parm->stop_seen) { /* skip the rest of the line */ parm->linelen = parm->readpos = 0; } else { int idx = parm->base64.idx; unsigned char val = parm->base64.val; while (n < count && parm->readpos < parm->linelen ) { c = parm->line[parm->readpos++]; if (c == '\n' || c == ' ' || c == '\r' || c == '\t') continue; if (c == '=') { /* pad character: stop */ if (idx == 1) buffer[n++] = val; parm->stop_seen = 1; break; } if( (c = asctobin[(c2=c)]) == 255 ) { log_error (_("invalid radix64 character %02x skipped\n"), c2); continue; } switch (idx) { case 0: val = c << 2; break; case 1: val |= (c>>4)&3; buffer[n++] = val; val = (c<<4)&0xf0; break; case 2: val |= (c>>2)&15; buffer[n++] = val; val = (c<<6)&0xc0; break; case 3: val |= c&0x3f; buffer[n++] = val; break; } idx = (idx+1) % 4; } if (parm->readpos == parm->linelen) parm->linelen = parm->readpos = 0; parm->base64.idx = idx; parm->base64.val = val; } } else { /* DER encoded */ while (n < count && parm->readpos < parm->linelen) buffer[n++] = parm->line[parm->readpos++]; if (parm->readpos == parm->linelen) parm->linelen = parm->readpos = 0; } *nread = n; return 0; } static int simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct reader_cb_parm_s *parm = cb_value; size_t n; int c = 0; *nread = 0; if (!buffer) return -1; /* not supported */ for (n=0; n < count; n++) { c = getc (parm->fp); if (c == EOF) { parm->eof_seen = 1; if ( ferror (parm->fp) ) return -1; if (n) break; /* return what we have before an EOF */ return -1; } *(byte *)buffer++ = c; } *nread = n; return 0; } static int base64_writer_cb (void *cb_value, const void *buffer, size_t count) { struct writer_cb_parm_s *parm = cb_value; unsigned char radbuf[4]; int i, c, idx, quad_count; const unsigned char *p; FILE *fp = parm->fp; if (!count) return 0; if (!parm->wrote_begin) { if (parm->pem_name) { fputs ("-----BEGIN ", fp); fputs (parm->pem_name, fp); fputs ("-----\n", fp); } parm->wrote_begin = 1; parm->base64.idx = 0; parm->base64.quad_count = 0; } idx = parm->base64.idx; quad_count = parm->base64.quad_count; for (i=0; i < idx; i++) radbuf[i] = parm->base64.radbuf[i]; for (p=buffer; count; p++, count--) { radbuf[idx++] = *p; if (idx > 2) { idx = 0; c = bintoasc[(*radbuf >> 2) & 077]; putc (c, fp); c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; putc (c, fp); c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; putc (c, fp); c = bintoasc[radbuf[2]&077]; putc (c, fp); if (++quad_count >= (64/4)) { fputs (LF, fp); quad_count = 0; } } } for (i=0; i < idx; i++) parm->base64.radbuf[i] = radbuf[i]; parm->base64.idx = idx; parm->base64.quad_count = quad_count; return ferror (fp) ? gpg_error_from_errno (errno) : 0; } static int base64_finish_write (struct writer_cb_parm_s *parm) { unsigned char radbuf[4]; int i, c, idx, quad_count; FILE *fp = parm->fp; if (!parm->wrote_begin) return 0; /* nothing written */ /* flush the base64 encoding */ idx = parm->base64.idx; quad_count = parm->base64.quad_count; for (i=0; i < idx; i++) radbuf[i] = parm->base64.radbuf[i]; if (idx) { c = bintoasc[(*radbuf>>2)&077]; putc (c, fp); if (idx == 1) { c = bintoasc[((*radbuf << 4) & 060) & 077]; putc (c, fp); putc ('=', fp); putc ('=', fp); } else { c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; putc (c, fp); c = bintoasc[((radbuf[1] << 2) & 074) & 077]; putc (c, fp); putc ('=', fp); } if (++quad_count >= (64/4)) { fputs (LF, fp); quad_count = 0; } } if (quad_count) fputs (LF, fp); if (parm->pem_name) { fputs ("-----END ", fp); fputs (parm->pem_name, fp); fputs ("-----\n", fp); } return ferror (fp)? gpg_error (gpg_err_code_from_errno (errno)) : 0; } /* Create a reader for the given file descriptor. Depending on the control information an input decoding is automagically choosen. The function returns a Base64Context object which must be passed to the gpgme_destroy_reader function. The created KsbaReader object is also returned, but the caller must not call the ksba_reader_release function on. If ALLOW_MULTI_PEM is true, the reader expects that the caller uses ksba_reader_clear after EOF until no more objects were found. */ int gpgsm_create_reader (Base64Context *ctx, CTRL ctrl, FILE *fp, int allow_multi_pem, ksba_reader_t *r_reader) { int rc; ksba_reader_t r; *r_reader = NULL; *ctx = xtrycalloc (1, sizeof **ctx); if (!*ctx) return OUT_OF_CORE (errno); (*ctx)->u.rparm.allow_multi_pem = allow_multi_pem; rc = ksba_reader_new (&r); if (rc) { xfree (*ctx); *ctx = NULL; return rc; } (*ctx)->u.rparm.fp = fp; if (ctrl->is_pem) { (*ctx)->u.rparm.assume_pem = 1; (*ctx)->u.rparm.assume_base64 = 1; rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); } else if (ctrl->is_base64) { (*ctx)->u.rparm.assume_base64 = 1; rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); } else if (ctrl->autodetect_encoding) { (*ctx)->u.rparm.autodetect = 1; rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); } else rc = ksba_reader_set_cb (r, simple_reader_cb, &(*ctx)->u.rparm); if (rc) { ksba_reader_release (r); xfree (*ctx); *ctx = NULL; return rc; } *r_reader = r; return 0; } int gpgsm_reader_eof_seen (Base64Context ctx) { return ctx && ctx->u.rparm.eof_seen; } void gpgsm_destroy_reader (Base64Context ctx) { xfree (ctx); } /* Create a writer for the given stream. Depending on the control information an output encoding is automagically choosen. The function returns a Base64Context object which must be passed to the gpgme_destroy_writer function. The created KsbaWriter object is also returned, but the caller must not call the ksba_reader_release function on. */ int gpgsm_create_writer (Base64Context *ctx, CTRL ctrl, FILE *fp, ksba_writer_t *r_writer) { int rc; ksba_writer_t w; *r_writer = NULL; *ctx = xtrycalloc (1, sizeof **ctx); if (!*ctx) return OUT_OF_CORE (errno); rc = ksba_writer_new (&w); if (rc) { xfree (*ctx); *ctx = NULL; return rc; } if (ctrl->create_pem || ctrl->create_base64) { (*ctx)->u.wparm.fp = fp; if (ctrl->create_pem) (*ctx)->u.wparm.pem_name = ctrl->pem_name? ctrl->pem_name : "CMS OBJECT"; rc = ksba_writer_set_cb (w, base64_writer_cb, &(*ctx)->u.wparm); } else rc = ksba_writer_set_file (w, fp); if (rc) { ksba_writer_release (w); xfree (*ctx); *ctx = NULL; return rc; } *r_writer = w; return 0; } int gpgsm_finish_writer (Base64Context ctx) { struct writer_cb_parm_s *parm; if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); parm = &ctx->u.wparm; if (parm->did_finish) return 0; /* already done */ parm->did_finish = 1; if (!parm->fp) return 0; /* callback was not used */ return base64_finish_write (parm); } void gpgsm_destroy_writer (Base64Context ctx) { xfree (ctx); } diff --git a/sm/call-agent.c b/sm/call-agent.c index 9942672ae..85ec78c63 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -1,715 +1,716 @@ /* call-agent.c - divert operations to the agent * Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "gpgsm.h" #include #include #include "i18n.h" #include "asshelp.h" #include "keydb.h" /* fixme: Move this to import.c */ #include "../common/membuf.h" static assuan_context_t agent_ctx = NULL; static int force_pipe_server = 0; struct cipher_parm_s { assuan_context_t ctx; const unsigned char *ciphertext; size_t ciphertextlen; }; struct genkey_parm_s { assuan_context_t ctx; const unsigned char *sexp; size_t sexplen; }; struct learn_parm_s { int error; assuan_context_t ctx; membuf_t *data; }; /* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_agent (ctrl_t ctrl) { int rc = 0; char *infostr, *p; assuan_context_t ctx; if (agent_ctx) return 0; /* fixme: We need a context for each thread or serialize the access to the agent (which is suitable given that the agent is not MT. */ infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO"); if (!infostr || !*infostr) { const char *pgmname; const char *argv[3]; char *sockname; int no_close_list[3]; int i; /* First check whether we can connect at the standard socket. */ sockname = make_filename (opt.homedir, "S.gpg-agent", NULL); rc = assuan_socket_connect (&ctx, sockname, 0); xfree (sockname); if (rc) { /* With no success start a new server. */ if (opt.verbose) log_info (_("no running gpg-agent - starting one\n")); gpgsm_status (ctrl, STATUS_PROGRESS, "starting_agent ? 0 0"); 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 tmperr; } if (!opt.agent_program || !*opt.agent_program) opt.agent_program = GNUPG_DEFAULT_AGENT; if ( !(pgmname = strrchr (opt.agent_program, '/'))) pgmname = opt.agent_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; 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 agent and perform initial handshaking. */ rc = assuan_pipe_connect (&ctx, opt.agent_program, (char**)argv, no_close_list); } } else { int prot; int pid; infostr = xstrdup (infostr); if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) { log_error (_("malformed GPG_AGENT_INFO environment variable\n")); xfree (infostr); force_pipe_server = 1; return start_agent (ctrl); } *p++ = 0; pid = atoi (p); while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("gpg-agent protocol version %d is not supported\n"), prot); xfree (infostr); force_pipe_server = 1; return start_agent (ctrl); } rc = assuan_socket_connect (&ctx, infostr, pid); xfree (infostr); if (rc == ASSUAN_Connect_Failed) { log_error (_("can't connect to the agent - trying fall back\n")); force_pipe_server = 1; return start_agent (ctrl); } } if (rc) { log_error ("can't connect to the agent: %s\n", assuan_strerror (rc)); return gpg_error (GPG_ERR_NO_AGENT); } agent_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to agent established\n"); rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); return send_pinentry_environment (agent_ctx, GPG_ERR_SOURCE_DEFAULT, opt.display, opt.ttyname, opt.ttytype, opt.lc_ctype, opt.lc_messages); } static AssuanError membuf_data_cb (void *opaque, const void *buffer, size_t length) { membuf_t *data = opaque; if (buffer) put_membuf (data, buffer, length); return 0; } /* Call the agent to do a sign operation using the key identified by the hex string KEYGRIP. */ int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen ) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; *r_buf = NULL; rc = start_agent (ctrl); if (rc) return rc; if (digestlen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); snprintf (line, DIM(line)-1, "SIGKEY %s", keygrip); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); if (desc) { snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); } sprintf (line, "SETHASH %d ", digestalgo); p = line + strlen (line); for (i=0; i < digestlen ; i++, p += 2 ) sprintf (p, "%02X", digest[i]); rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); init_membuf (&data, 1024); rc = assuan_transact (agent_ctx, "PKSIGN", membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } *r_buf = get_membuf (&data, r_buflen); if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)) { xfree (*r_buf); *r_buf = NULL; return gpg_error (GPG_ERR_INV_VALUE); } return *r_buf? 0 : OUT_OF_CORE (errno); } /* Handle a CIPHERTEXT inquiry. Note, we only send the data, assuan_transact talkes care of flushing and writing the end */ static AssuanError inq_ciphertext_cb (void *opaque, const char *keyword) { struct cipher_parm_s *parm = opaque; AssuanError rc; assuan_begin_confidential (parm->ctx); rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); assuan_end_confidential (parm->ctx); return rc; } /* Call the agent to do a decrypt operation using the key identified by the hex string KEYGRIP. */ int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen ) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; struct cipher_parm_s cipher_parm; size_t n, len; char *buf, *endp; size_t ciphertextlen; if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen) return gpg_error (GPG_ERR_INV_VALUE); *r_buf = NULL; ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL); if (!ciphertextlen) return gpg_error (GPG_ERR_INV_VALUE); rc = start_agent (ctrl); if (rc) return rc; rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); assert ( DIM(line) >= 50 ); snprintf (line, DIM(line)-1, "SETKEY %s", keygrip); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); if (desc) { snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); } init_membuf (&data, 1024); cipher_parm.ctx = agent_ctx; cipher_parm.ciphertext = ciphertext; cipher_parm.ciphertextlen = ciphertextlen; rc = assuan_transact (agent_ctx, "PKDECRYPT", membuf_data_cb, &data, inq_ciphertext_cb, &cipher_parm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } put_membuf (&data, "", 1); /* make sure it is 0 terminated */ buf = get_membuf (&data, &len); if (!buf) return gpg_error (GPG_ERR_ENOMEM); /* FIXME: We would better a return a full S-exp and not just a part */ assert (len); len--; /* remove the terminating 0 */ n = strtoul (buf, &endp, 10); if (!n || *endp != ':') return gpg_error (GPG_ERR_INV_SEXP); endp++; if (endp-buf+n > len) return gpg_error (GPG_ERR_INV_SEXP); /* oops len does not match internal len*/ memmove (buf, endp, n); *r_buflen = n; *r_buf = buf; return 0; } /* Handle a KEYPARMS inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static AssuanError inq_genkey_parms (void *opaque, const char *keyword) { struct genkey_parm_s *parm = opaque; AssuanError rc; rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen); return rc; } /* Call the agent to generate a newkey */ int gpgsm_agent_genkey (ctrl_t ctrl, ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey) { int rc; struct genkey_parm_s gk_parm; membuf_t data; size_t len; unsigned char *buf; *r_pubkey = NULL; rc = start_agent (ctrl); if (rc) return rc; rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); init_membuf (&data, 1024); gk_parm.ctx = agent_ctx; gk_parm.sexp = keyparms; gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL); if (!gk_parm.sexplen) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_transact (agent_ctx, "GENKEY", membuf_data_cb, &data, inq_genkey_parms, &gk_parm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } buf = get_membuf (&data, &len); if (!buf) return gpg_error (GPG_ERR_ENOMEM); if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } *r_pubkey = buf; return 0; } /* Call the agent to read the public key part for a given keygrip. */ int gpgsm_agent_readkey (ctrl_t ctrl, const char *hexkeygrip, ksba_sexp_t *r_pubkey) { int rc; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; *r_pubkey = NULL; rc = start_agent (ctrl); if (rc) return rc; rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); snprintf (line, DIM(line)-1, "READKEY %s", hexkeygrip); line[DIM(line)-1] = 0; init_membuf (&data, 1024); rc = assuan_transact (agent_ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return map_assuan_err (rc); } buf = get_membuf (&data, &len); if (!buf) return gpg_error (GPG_ERR_ENOMEM); if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } *r_pubkey = buf; return 0; } /* Ask the agent whether the certificate is in the list of trusted keys */ int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert) { int rc; char *fpr; char line[ASSUAN_LINELENGTH]; rc = start_agent (ctrl); if (rc) return rc; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (!fpr) { log_error ("error getting the fingerprint\n"); return gpg_error (GPG_ERR_GENERAL); } snprintf (line, DIM(line)-1, "ISTRUSTED %s", fpr); line[DIM(line)-1] = 0; xfree (fpr); rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } /* Ask the agent to mark CERT as a trusted Root-CA one */ int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert) { int rc; char *fpr, *dn; char line[ASSUAN_LINELENGTH]; rc = start_agent (ctrl); if (rc) return rc; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (!fpr) { log_error ("error getting the fingerprint\n"); return gpg_error (GPG_ERR_GENERAL); } dn = ksba_cert_get_issuer (cert, 0); if (!dn) { xfree (fpr); return gpg_error (GPG_ERR_GENERAL); } snprintf (line, DIM(line)-1, "MARKTRUSTED %s S %s", fpr, dn); line[DIM(line)-1] = 0; ksba_free (dn); xfree (fpr); rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } /* Ask the agent whether the a corresponding secret key is available for the given keygrip */ int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_agent (ctrl); if (rc) return rc; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } static AssuanError learn_cb (void *opaque, const void *buffer, size_t length) { struct learn_parm_s *parm = opaque; size_t len; char *buf; ksba_cert_t cert; int rc; if (parm->error) return 0; if (buffer) { put_membuf (parm->data, buffer, length); return 0; } /* END encountered - process what we have */ buf = get_membuf (parm->data, &len); if (!buf) { parm->error = gpg_error (GPG_ERR_ENOMEM); return 0; } /* FIXME: this should go into import.c */ rc = ksba_cert_new (&cert); if (rc) { parm->error = rc; return 0; } rc = ksba_cert_init_from_mem (cert, buf, len); if (rc) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); ksba_cert_release (cert); parm->error = rc; return 0; } rc = gpgsm_basic_cert_check (cert); if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT) { /* For later use we store it in the ephemeral database. */ log_info ("issuer certificate missing - storing as ephemeral\n"); keydb_store_cert (cert, 1, NULL); } else if (rc) log_error ("invalid certificate: %s\n", gpg_strerror (rc)); else { int existed; if (!keydb_store_cert (cert, 0, &existed)) { if (opt.verbose > 1 && existed) log_info ("certificate already in DB\n"); else if (opt.verbose && !existed) log_info ("certificate imported\n"); } } ksba_cert_release (cert); init_membuf (parm->data, 4096); return 0; } /* Call the agent to learn about a smartcard */ int gpgsm_agent_learn (ctrl_t ctrl) { int rc; struct learn_parm_s learn_parm; membuf_t data; size_t len; rc = start_agent (ctrl); if (rc) return rc; init_membuf (&data, 4096); learn_parm.error = 0; learn_parm.ctx = agent_ctx; learn_parm.data = &data; rc = assuan_transact (agent_ctx, "LEARN --send", learn_cb, &learn_parm, NULL, NULL, NULL, NULL); xfree (get_membuf (&data, &len)); if (rc) return map_assuan_err (rc); return learn_parm.error; } /* Ask the agent to change the passphrase of the key identified by HEXKEYGRIP. If DESC is not NULL, display instead of the default description message. */ int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_agent (ctrl); if (rc) return rc; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return map_assuan_err (rc); } snprintf (line, DIM(line)-1, "PASSWD %s", hexkeygrip); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } /* Ask the agent to pop up a confirmation dialog with the text DESC and an okay and cancel button. */ gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc) { int rc; char line[ASSUAN_LINELENGTH]; rc = start_agent (ctrl); if (rc) return rc; snprintf (line, DIM(line)-1, "GET_CONFIRMATION %s", desc); line[DIM(line)-1] = 0; rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return map_assuan_err (rc); } diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 85467d4a2..0de09a9ba 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -1,847 +1,848 @@ /* call-dirmngr.c - communication with the dromngr * Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "i18n.h" #include "keydb.h" /* The name of the socket for a system daemon. */ #define DEFAULT_SOCKET_NAME "/var/run/dirmngr/socket" struct membuf { size_t len; size_t size; char *buf; int out_of_core; }; static ASSUAN_CONTEXT dirmngr_ctx = NULL; static int force_pipe_server = 0; struct inq_certificate_parm_s { ASSUAN_CONTEXT ctx; ksba_cert_t cert; ksba_cert_t issuer_cert; }; struct isvalid_status_parm_s { CTRL ctrl; int seen; unsigned char fpr[20]; }; struct lookup_parm_s { CTRL ctrl; ASSUAN_CONTEXT ctx; void (*cb)(void *, ksba_cert_t); void *cb_value; struct membuf data; int error; }; struct run_command_parm_s { ASSUAN_CONTEXT ctx; }; /* 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; } /* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_dirmngr (void) { int rc; char *infostr, *p; ASSUAN_CONTEXT ctx; int try_default = 0; if (dirmngr_ctx) return 0; /* fixme: We need a context for each thread or serialize the access to the dirmngr */ /* Note: if you change this to multiple connections, you also need to take care of the implicit option sending caching. */ infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); if (opt.prefer_system_dirmngr && !force_pipe_server &&(!infostr || !*infostr)) { infostr = DEFAULT_SOCKET_NAME; try_default = 1; } if (!infostr || !*infostr) { const char *pgmname; const char *argv[3]; int no_close_list[3]; int i; if (!opt.dirmngr_program || !*opt.dirmngr_program) opt.dirmngr_program = GNUPG_DEFAULT_DIRMNGR; if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) pgmname = opt.dirmngr_program; else pgmname++; if (opt.verbose) log_info (_("no running dirmngr - starting `%s'\n"), opt.dirmngr_program); 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 tmperr; } argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; 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 agent and perform initial handshaking */ rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv, no_close_list); } else { int prot; int pid; infostr = xstrdup (infostr); if (!try_default && *infostr) { if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) { log_error (_("malformed DIRMNGR_INFO environment variable\n")); xfree (infostr); force_pipe_server = 1; return start_dirmngr (); } *p++ = 0; pid = atoi (p); while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("dirmngr protocol version %d is not supported\n"), prot); xfree (infostr); force_pipe_server = 1; return start_dirmngr (); } } else pid = -1; rc = assuan_socket_connect (&ctx, infostr, pid); xfree (infostr); if (rc == ASSUAN_Connect_Failed) { log_error (_("can't connect to the dirmngr - trying fall back\n")); force_pipe_server = 1; return start_dirmngr (); } } if (rc) { log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc)); return gpg_error (GPG_ERR_NO_DIRMNGR); } dirmngr_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to dirmngr established\n"); return 0; } /* Handle a SENDCERT inquiry. */ static AssuanError inq_certificate (void *opaque, const char *line) { struct inq_certificate_parm_s *parm = opaque; AssuanError rc; const unsigned char *der; size_t derlen; int issuer_mode = 0; ksba_sexp_t ski = NULL; if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) { line += 8; } else if (!strncmp (line, "SENDCERT_SKI", 12) && (line[12]==' ' || !line[12])) { size_t n; /* Send a certificate where a sourceKeyIdentifier is included. */ line += 12; while (*line == ' ') line++; ski = make_simple_sexp_from_hexstr (line, &n); line += n; while (*line == ' ') line++; } else if (!strncmp (line, "SENDISSUERCERT", 14) && (line[14] == ' ' || !line[14])) { line += 14; issuer_mode = 1; } else { log_error ("unsupported inquiry `%s'\n", line); return ASSUAN_Inquire_Unknown; } if (!*line) { /* Send the current certificate. */ der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert, &derlen); if (!der) rc = ASSUAN_Inquire_Error; else rc = assuan_send_data (parm->ctx, der, derlen); } else if (issuer_mode) { log_error ("sending specific issuer certificate back " "is not yet implemented\n"); rc = ASSUAN_Inquire_Error; } else { /* Send the given certificate. */ int err; ksba_cert_t cert; err = gpgsm_find_cert (line, ski, &cert); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); rc = ASSUAN_Inquire_Error; } else { der = ksba_cert_get_image (cert, &derlen); if (!der) rc = ASSUAN_Inquire_Error; else rc = assuan_send_data (parm->ctx, der, derlen); ksba_cert_release (cert); } } xfree (ski); return rc; } /* Take a 20 byte hexencoded string and put it into the the provided 20 byte buffer FPR in binary format. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if (*s || (n != 40)) return 0; /* no fingerprint (invalid or wrong length). */ n /= 2; for (s=hexstr, n=0; *s; s += 2, n++) fpr[n] = xtoi_2 (s); return 1; /* okay */ } static assuan_error_t isvalid_status_cb (void *opaque, const char *line) { struct isvalid_status_parm_s *parm = opaque; if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) { if (parm->ctrl) { for (line += 8; *line == ' '; line++) ; if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) return ASSUAN_Canceled; } } else if (!strncmp (line, "ONLY_VALID_IF_CERT_VALID", 24) && (line[24]==' ' || !line[24])) { parm->seen++; if (!line[24] || !unhexify_fpr (line+25, parm->fpr)) parm->seen++; /* Bumb it to indicate an error. */ } return 0; } /* Call the directory manager to check whether the certificate is valid Returns 0 for valid or usually one of the errors: GPG_ERR_CERTIFICATE_REVOKED GPG_ERR_NO_CRL_KNOWN GPG_ERR_CRL_TOO_OLD With USE_OCSP set to true, the dirmngr is asked to do an OCSP request first. */ int gpgsm_dirmngr_isvalid (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp) { static int did_options; int rc; char *certid; char line[ASSUAN_LINELENGTH]; struct inq_certificate_parm_s parm; struct isvalid_status_parm_s stparm; rc = start_dirmngr (); if (rc) return rc; if (use_ocsp) { certid = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); } else { certid = gpgsm_get_certid (cert); if (!certid) { log_error ("error getting the certificate ID\n"); return gpg_error (GPG_ERR_GENERAL); } } if (opt.verbose > 1) { char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); log_info ("asking dirmngr about %s%s\n", fpr, use_ocsp? " (using OCSP)":""); xfree (fpr); } parm.ctx = dirmngr_ctx; parm.cert = cert; parm.issuer_cert = issuer_cert; stparm.ctrl = ctrl; stparm.seen = 0; memset (stparm.fpr, 0, 20); /* FIXME: If --disable-crl-checks has been set, we should pass an option to dirmngr, so that no fallback CRL check is done after an ocsp check. */ /* It is sufficient to send the options only once because we have one connection per process only. */ if (!did_options) { if (opt.force_crl_refresh) assuan_transact (dirmngr_ctx, "OPTION force-crl-refresh=1", NULL, NULL, NULL, NULL, NULL, NULL); did_options = 1; } snprintf (line, DIM(line)-1, "ISVALID %s", certid); line[DIM(line)-1] = 0; xfree (certid); rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, inq_certificate, &parm, isvalid_status_cb, &stparm); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay"); rc = map_assuan_err (rc); if (!rc && stparm.seen) { /* Need to also check the certificate validity. */ if (stparm.seen != 1) { log_error ("communication problem with dirmngr detected\n"); rc = gpg_error (GPG_ERR_INV_CRL); } else { KEYDB_HANDLE kh; ksba_cert_t rspcert = NULL; /* Fixme: First try to get the certificate from the dirmngr's cache - it should be there. */ kh = keydb_new (0); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); if (!rc) rc = keydb_search_fpr (kh, stparm.fpr); if (!rc) rc = keydb_get_cert (kh, &rspcert); if (rc) { log_error ("unable to find the certificate used " "by the dirmngr: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_CRL); } keydb_release (kh); if (!rc) { rc = gpgsm_cert_use_ocsp_p (rspcert); if (rc) rc = gpg_error (GPG_ERR_INV_CRL); else { /* Note, the flag = 1: This avoids checking this certificate over and over again. */ rc = gpgsm_validate_chain (ctrl, rspcert, NULL, 0, NULL, 1); if (rc) { log_error ("invalid certificate used for CRL/OCSP: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_CRL); } } } ksba_cert_release (rspcert); } } return rc; } /* Lookup helpers*/ static AssuanError lookup_cb (void *opaque, const void *buffer, size_t length) { struct lookup_parm_s *parm = opaque; size_t len; char *buf; ksba_cert_t cert; int rc; if (parm->error) return 0; if (buffer) { put_membuf (&parm->data, buffer, length); return 0; } /* END encountered - process what we have */ buf = get_membuf (&parm->data, &len); if (!buf) { parm->error = gpg_error (GPG_ERR_ENOMEM); return 0; } rc = ksba_cert_new (&cert); if (rc) { parm->error = rc; return 0; } rc = ksba_cert_init_from_mem (cert, buf, len); if (rc) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); } else { parm->cb (parm->cb_value, cert); } ksba_cert_release (cert); init_membuf (&parm->data, 4096); return 0; } /* Return a properly escaped pattern from NAMES. The only error return is NULL to indicate a malloc failure. */ static char * pattern_from_strlist (STRLIST names) { STRLIST sl; int n; const char *s; char *pattern, *p; for (n=0, sl=names; sl; sl = sl->next) { for (s=sl->d; *s; s++, n++) { if (*s == '%' || *s == ' ' || *s == '+') n += 2; } n++; } p = pattern = xtrymalloc (n+1); if (!pattern) return NULL; for (n=0, sl=names; sl; sl = sl->next) { for (s=sl->d; *s; s++) { switch (*s) { case '%': *p++ = '%'; *p++ = '2'; *p++ = '5'; break; case ' ': *p++ = '%'; *p++ = '2'; *p++ = '0'; break; case '+': *p++ = '%'; *p++ = '2'; *p++ = 'B'; break; default: *p++ = *s; break; } } *p++ = ' '; } if (p == pattern) *pattern = 0; /* is empty */ else p[-1] = '\0'; /* remove trailing blank */ return pattern; } static AssuanError lookup_status_cb (void *opaque, const char *line) { struct lookup_parm_s *parm = opaque; if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) { if (parm->ctrl) { for (line += 8; *line == ' '; line++) ; if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) return ASSUAN_Canceled; } } else if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9])) { if (parm->ctrl) { for (line +=9; *line == ' '; line++) ; gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line); } } return 0; } /* Run the Directroy Managers lookup command using the pattern compiled from the strings given in NAMES. The caller must provide the callback CB which will be passed cert by cert. Note that CTRL is optional. */ int gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names, void (*cb)(void*, ksba_cert_t), void *cb_value) { int rc; char *pattern; char line[ASSUAN_LINELENGTH]; struct lookup_parm_s parm; size_t len; rc = start_dirmngr (); if (rc) return rc; pattern = pattern_from_strlist (names); if (!pattern) return OUT_OF_CORE (errno); snprintf (line, DIM(line)-1, "LOOKUP %s", pattern); line[DIM(line)-1] = 0; xfree (pattern); parm.ctrl = ctrl; parm.ctx = dirmngr_ctx; parm.cb = cb; parm.cb_value = cb_value; parm.error = 0; init_membuf (&parm.data, 4096); rc = assuan_transact (dirmngr_ctx, line, lookup_cb, &parm, NULL, NULL, lookup_status_cb, &parm); xfree (get_membuf (&parm.data, &len)); if (rc) return map_assuan_err (rc); return parm.error; } /* Run Command helpers*/ /* Fairly simple callback to write all output of dirmngr to stdout. */ static AssuanError run_command_cb (void *opaque, const void *buffer, size_t length) { if (buffer) { if ( fwrite (buffer, length, 1, stdout) != 1 ) log_error ("error writing to stdout: %s\n", strerror (errno)); } return 0; } /* Handle inquiries from the dirmngr COMMAND. */ static AssuanError run_command_inq_cb (void *opaque, const char *line) { struct run_command_parm_s *parm = opaque; AssuanError rc = 0; if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) ) { /* send the given certificate */ int err; ksba_cert_t cert; const unsigned char *der; size_t derlen; line += 8; if (!*line) return ASSUAN_Inquire_Error; err = gpgsm_find_cert (line, NULL, &cert); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); rc = ASSUAN_Inquire_Error; } else { der = ksba_cert_get_image (cert, &derlen); if (!der) rc = ASSUAN_Inquire_Error; else rc = assuan_send_data (parm->ctx, der, derlen); ksba_cert_release (cert); } } else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) ) { /* Simply show the message given in the argument. */ line += 9; log_info ("dirmngr: %s\n", line); } else { log_error ("unsupported inquiry `%s'\n", line); rc = ASSUAN_Inquire_Unknown; } return rc; } static AssuanError run_command_status_cb (void *opaque, const char *line) { ctrl_t ctrl = opaque; if (opt.verbose) { log_info ("dirmngr status: %s\n", line); } if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) { if (ctrl) { for (line += 8; *line == ' '; line++) ; if (gpgsm_status (ctrl, STATUS_PROGRESS, line)) return ASSUAN_Canceled; } } return 0; } /* Pass COMMAND to dirmngr and print all output generated by Dirmngr to stdout. A couple of inquiries are defined (see above). ARGC arguments in ARGV are given to the Dirmngr. Spaces, plus and percent characters within the argument strings are percent escaped so that blanks can act as delimiters. */ int gpgsm_dirmngr_run_command (CTRL ctrl, const char *command, int argc, char **argv) { int rc; int i; const char *s; char *line, *p; size_t len; struct run_command_parm_s parm; rc = start_dirmngr (); if (rc) return rc; parm.ctx = dirmngr_ctx; len = strlen (command) + 1; for (i=0; i < argc; i++) len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */ line = xtrymalloc (len); if (!line) return OUT_OF_CORE (errno); p = stpcpy (line, command); for (i=0; i < argc; i++) { *p++ = ' '; for (s=argv[i]; *s; s++) { if (!isascii (*s)) *p++ = *s; else if (*s == ' ') *p++ = '+'; else if (!isprint (*s) || *s == '+') { sprintf (p, "%%%02X", *(const unsigned char *)s); p += 3; } else *p++ = *s; } } *p = 0; rc = assuan_transact (dirmngr_ctx, line, run_command_cb, NULL, run_command_inq_cb, &parm, run_command_status_cb, ctrl); xfree (line); log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay"); return map_assuan_err (rc); } diff --git a/sm/certchain.c b/sm/certchain.c index 44d72efd3..4a4ac49f6 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -1,1300 +1,1301 @@ /* certchain.c - certificate chain validation * Copyright (C) 2001, 2002, 2003, 2004, 2005, * 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #define JNLIB_NEED_LOG_LOGV /* We need log_logv. */ #include "gpgsm.h" #include #include #include "keydb.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ #include "i18n.h" static int get_regtp_ca_info (ksba_cert_t cert, int *chainlen); /* If LISTMODE is true, print FORMAT using LISTMODE to FP. If LISTMODE is false, use the string to print an log_info or, if IS_ERROR is true, and log_error. */ static void do_list (int is_error, int listmode, FILE *fp, const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format) ; if (listmode) { if (fp) { fputs (" [", fp); vfprintf (fp, format, arg_ptr); fputs ("]\n", fp); } } else { log_logv (is_error? JNLIB_LOG_ERROR: JNLIB_LOG_INFO, format, arg_ptr); log_printf ("\n"); } va_end (arg_ptr); } /* Return 0 if A and B are equal. */ static int compare_certs (ksba_cert_t a, ksba_cert_t b) { const unsigned char *img_a, *img_b; size_t len_a, len_b; img_a = ksba_cert_get_image (a, &len_a); if (!img_a) return 1; img_b = ksba_cert_get_image (b, &len_b); if (!img_b) return 1; return !(len_a == len_b && !memcmp (img_a, img_b, len_a)); } static int unknown_criticals (ksba_cert_t cert, int listmode, FILE *fp) { static const char *known[] = { "2.5.29.15", /* keyUsage */ "2.5.29.19", /* basic Constraints */ "2.5.29.32", /* certificatePolicies */ "2.5.29.37", /* extendedKeyUsage - handled by certlist.c */ NULL }; int rc = 0, i, idx, crit; const char *oid; gpg_error_t err; for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &crit, NULL, NULL));idx++) { if (!crit) continue; for (i=0; known[i] && strcmp (known[i],oid); i++) ; if (!known[i]) { do_list (1, listmode, fp, _("critical certificate extension %s is not supported"), oid); rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); } } if (err && gpg_err_code (err) != GPG_ERR_EOF) rc = err; return rc; } static int allowed_ca (ksba_cert_t cert, int *chainlen, int listmode, FILE *fp) { gpg_error_t err; int flag; err = ksba_cert_is_ca (cert, &flag, chainlen); if (err) return err; if (!flag) { if (get_regtp_ca_info (cert, chainlen)) { return 0; /* RegTP issued certificate. */ } do_list (1, listmode, fp,_("issuer certificate is not marked as a CA")); return gpg_error (GPG_ERR_BAD_CA_CERT); } return 0; } static int check_cert_policy (ksba_cert_t cert, int listmode, FILE *fplist) { gpg_error_t err; char *policies; FILE *fp; int any_critical; err = ksba_cert_get_cert_policies (cert, &policies); if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 0; /* no policy given */ if (err) return err; /* STRING is a line delimited list of certifiate policies as stored in the certificate. The line itself is colon delimited where the first field is the OID of the policy and the second field either N or C for normal or critical extension */ if (opt.verbose > 1 && !listmode) log_info ("certificate's policy list: %s\n", policies); /* The check is very minimal but won't give false positives */ any_critical = !!strstr (policies, ":C"); if (!opt.policy_file) { xfree (policies); if (any_critical) { do_list (1, listmode, fplist, _("critical marked policy without configured policies")); return gpg_error (GPG_ERR_NO_POLICY_MATCH); } return 0; } fp = fopen (opt.policy_file, "r"); if (!fp) { if (opt.verbose || errno != ENOENT) log_info (_("failed to open `%s': %s\n"), opt.policy_file, strerror (errno)); xfree (policies); /* With no critical policies this is only a warning */ if (!any_critical) { do_list (0, listmode, fplist, _("note: non-critical certificate policy not allowed")); return 0; } do_list (1, listmode, fplist, _("certificate policy not allowed")); return gpg_error (GPG_ERR_NO_POLICY_MATCH); } for (;;) { int c; char *p, line[256]; char *haystack, *allowed; /* read line */ do { if (!fgets (line, DIM(line)-1, fp) ) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); xfree (policies); if (feof (fp)) { fclose (fp); /* With no critical policies this is only a warning */ if (!any_critical) { do_list (0, listmode, fplist, _("note: non-critical certificate policy not allowed")); return 0; } do_list (1, listmode, fplist, _("certificate policy not allowed")); return gpg_error (GPG_ERR_NO_POLICY_MATCH); } fclose (fp); return tmperr; } if (!*line || line[strlen(line)-1] != '\n') { /* eat until end of line */ while ( (c=getc (fp)) != EOF && c != '\n') ; fclose (fp); xfree (policies); return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); /* parse line */ for (allowed=line; spacep (allowed); allowed++) ; p = strpbrk (allowed, " :\n"); if (!*p || p == allowed) { fclose (fp); xfree (policies); return gpg_error (GPG_ERR_CONFIGURATION); } *p = 0; /* strip the rest of the line */ /* See whether we find ALLOWED (which is an OID) in POLICIES */ for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) { if ( !(p == policies || p[-1] == '\n') ) continue; /* Does not match the begin of a line. */ if (p[strlen (allowed)] != ':') continue; /* The length does not match. */ /* Yep - it does match so return okay. */ fclose (fp); xfree (policies); return 0; } } } /* Helper function for find_up. This resets the key handle and search for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns 0 obn success or -1 when not found. */ static int find_up_search_by_keyid (KEYDB_HANDLE kh, const char *issuer, ksba_sexp_t keyid) { int rc; ksba_cert_t cert = NULL; ksba_sexp_t subj = NULL; keydb_search_reset (kh); while (!(rc = keydb_search_subject (kh, issuer))) { ksba_cert_release (cert); cert = NULL; rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("keydb_get_cert() failed: rc=%d\n", rc); rc = -1; break; } xfree (subj); if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) { if (!cmp_simple_canon_sexp (keyid, subj)) break; /* Found matching cert. */ } } ksba_cert_release (cert); xfree (subj); return rc? -1:0; } static void find_up_store_certs_cb (void *cb_value, ksba_cert_t cert) { if (keydb_store_cert (cert, 1, NULL)) log_error ("error storing issuer certificate as ephemeral\n"); ++*(int*)cb_value; } /* Helper for find_up(). Locate the certificate for ISSUER using an external lookup. KH is the keydb context we are currently using. On success 0 is returned and the certificate may be retrieved from the keydb using keydb_get_cert(). KEYID is the keyIdentifier from the AKI or NULL. */ static int find_up_external (KEYDB_HANDLE kh, const char *issuer, ksba_sexp_t keyid) { int rc; strlist_t names = NULL; int count = 0; char *pattern; const char *s; if (opt.verbose) log_info (_("looking up issuer at external location\n")); /* The DIRMNGR process is confused about unknown attributes. As a quick and ugly hack we locate the CN and use the issuer string starting at this attribite. Fixme: we should have far better parsing in the dirmngr. */ s = strstr (issuer, "CN="); if (!s || s == issuer || s[-1] != ',') s = issuer; pattern = xtrymalloc (strlen (s)+2); if (!pattern) return gpg_error_from_errno (errno); strcpy (stpcpy (pattern, "/"), s); add_to_strlist (&names, pattern); xfree (pattern); rc = gpgsm_dirmngr_lookup (NULL, names, find_up_store_certs_cb, &count); free_strlist (names); if (opt.verbose) log_info (_("number of issuers matching: %d\n"), count); if (rc) { log_error ("external key lookup failed: %s\n", gpg_strerror (rc)); rc = -1; } else if (!count) rc = -1; else { int old; /* The issuers are currently stored in the ephemeral key DB, so we temporary switch to ephemeral mode. */ old = keydb_set_ephemeral (kh, 1); if (keyid) rc = find_up_search_by_keyid (kh, issuer, keyid); else { keydb_search_reset (kh); rc = keydb_search_subject (kh, issuer); } keydb_set_ephemeral (kh, old); } return rc; } /* Locate issuing certificate for CERT. ISSUER is the name of the issuer used as a fallback if the other methods don't work. If FIND_NEXT is true, the function shall return the next possible issuer. The certificate itself is not directly returned but a keydb_get_cert on the keyDb context KH will return it. Returns 0 on success, -1 if not found or an error code. */ static int find_up (KEYDB_HANDLE kh, ksba_cert_t cert, const char *issuer, int find_next) { ksba_name_t authid; ksba_sexp_t authidno; ksba_sexp_t keyid; int rc = -1; if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno)) { const char *s = ksba_name_enum (authid, 0); if (s && *authidno) { rc = keydb_search_issuer_sn (kh, s, authidno); if (rc) keydb_search_reset (kh); /* In case of an error try the ephemeral DB. We can't do that in find_next mode because we can't keep the search state then. */ if (rc == -1 && !find_next) { int old = keydb_set_ephemeral (kh, 1); if (!old) { rc = keydb_search_issuer_sn (kh, s, authidno); if (rc) keydb_search_reset (kh); } keydb_set_ephemeral (kh, old); } } if (rc == -1 && keyid && !find_next) { /* Not found by AIK.issuer_sn. Lets try the AIY.ki instead. Loop over all certificates with that issuer as subject and stop for the one with a matching subjectKeyIdentifier. */ rc = find_up_search_by_keyid (kh, issuer, keyid); if (rc) { int old = keydb_set_ephemeral (kh, 1); if (!old) rc = find_up_search_by_keyid (kh, issuer, keyid); keydb_set_ephemeral (kh, old); } if (rc) rc = -1; /* Need to make sure to have this error code. */ } /* If we still didn't found it, try an external lookup. */ if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next) rc = find_up_external (kh, issuer, keyid); /* Print a note so that the user does not feel too helpless when an issuer certificate was found and gpgsm prints BAD signature because it is not the correct one. */ if (rc == -1) { log_info ("%sissuer certificate ", find_next?"next ":""); if (keyid) { log_printf ("{"); gpgsm_dump_serial (keyid); log_printf ("} "); } if (authidno) { log_printf ("(#"); gpgsm_dump_serial (authidno); log_printf ("/"); gpgsm_dump_string (s); log_printf (") "); } log_printf ("not found using authorityKeyIdentifier\n"); } else if (rc) log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc); xfree (keyid); ksba_name_release (authid); xfree (authidno); } if (rc) /* Not found via authorithyKeyIdentifier, try regular issuer name. */ rc = keydb_search_subject (kh, issuer); if (rc == -1 && !find_next) { /* Not found, lets see whether we have one in the ephemeral key DB. */ int old = keydb_set_ephemeral (kh, 1); if (!old) { keydb_search_reset (kh); rc = keydb_search_subject (kh, issuer); } keydb_set_ephemeral (kh, old); } /* Still not found. If enabled, try an external lookup. */ if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next) rc = find_up_external (kh, issuer, NULL); return rc; } /* Return the next certificate up in the chain starting at START. Returns -1 when there are no more certificates. */ int gpgsm_walk_cert_chain (ksba_cert_t start, ksba_cert_t *r_next) { int rc = 0; char *issuer = NULL; char *subject = NULL; KEYDB_HANDLE kh = keydb_new (0); *r_next = NULL; if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer = ksba_cert_get_issuer (start, 0); subject = ksba_cert_get_subject (start, 0); if (!issuer) { log_error ("no issuer found in certificate\n"); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (!subject) { log_error ("no subject found in certificate\n"); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (!strcmp (issuer, subject)) { rc = -1; /* we are at the root */ goto leave; } rc = find_up (kh, start, issuer, 0); if (rc) { /* it is quite common not to have a certificate, so better don't print an error here */ if (rc != -1 && opt.verbose > 1) log_error ("failed to find issuer's certificate: rc=%d\n", rc); rc = gpg_error (GPG_ERR_MISSING_CERT); goto leave; } rc = keydb_get_cert (kh, r_next); if (rc) { log_error ("keydb_get_cert() failed: rc=%d\n", rc); rc = gpg_error (GPG_ERR_GENERAL); } leave: xfree (issuer); xfree (subject); keydb_release (kh); return rc; } /* Check whether the CERT is a root certificate. Returns True if this is the case. */ int gpgsm_is_root_cert (ksba_cert_t cert) { char *issuer; char *subject; int yes; issuer = ksba_cert_get_issuer (cert, 0); subject = ksba_cert_get_subject (cert, 0); yes = (issuer && subject && !strcmp (issuer, subject)); xfree (issuer); xfree (subject); return yes; } /* This is a helper for gpgsm_validate_chain. */ static gpg_error_t is_cert_still_valid (ctrl_t ctrl, int lm, FILE *fp, ksba_cert_t subject_cert, ksba_cert_t issuer_cert, int *any_revoked, int *any_no_crl, int *any_crl_too_old) { if (!opt.no_crl_check || ctrl->use_ocsp) { gpg_error_t err; err = gpgsm_dirmngr_isvalid (ctrl, subject_cert, issuer_cert, ctrl->use_ocsp); if (err) { /* Fixme: We should change the wording because we may have used OCSP. */ switch (gpg_err_code (err)) { case GPG_ERR_CERT_REVOKED: do_list (1, lm, fp, _("certificate has been revoked")); *any_revoked = 1; /* Store that in the keybox so that key listings are able to return the revoked flag. We don't care about error, though. */ keydb_set_cert_flags (subject_cert, KEYBOX_FLAG_VALIDITY, 0, VALIDITY_REVOKED); break; case GPG_ERR_NO_CRL_KNOWN: do_list (1, lm, fp, _("no CRL found for certificate")); *any_no_crl = 1; break; case GPG_ERR_CRL_TOO_OLD: do_list (1, lm, fp, _("the available CRL is too old")); if (!lm) log_info (_("please make sure that the " "\"dirmngr\" is properly installed\n")); *any_crl_too_old = 1; break; default: do_list (1, lm, fp, _("checking the CRL failed: %s"), gpg_strerror (err)); return err; } } } return 0; } /* Validate a chain and optionally return the nearest expiration time in R_EXPTIME. With LISTMODE set to 1 a special listmode is activated where only information about the certificate is printed to FP and no output is send to the usual log stream. Defined flag bits: 0 - do not do any dirmngr isvalid checks. */ int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, int listmode, FILE *fp, unsigned int flags) { int rc = 0, depth = 0, maxdepth; char *issuer = NULL; char *subject = NULL; KEYDB_HANDLE kh = NULL; ksba_cert_t subject_cert = NULL, issuer_cert = NULL; ksba_isotime_t current_time; ksba_isotime_t exptime; int any_expired = 0; int any_revoked = 0; int any_no_crl = 0; int any_crl_too_old = 0; int any_no_policy_match = 0; int is_qualified = -1; /* Indicates whether the certificate stems from a qualified root certificate. -1 = unknown, 0 = no, 1 = yes. */ int lm = listmode; gnupg_get_isotime (current_time); if (r_exptime) *r_exptime = 0; *exptime = 0; if (opt.no_chain_validation && !listmode) { log_info ("WARNING: bypassing certificate chain validation\n"); return 0; } kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } if (DBG_X509 && !listmode) gpgsm_dump_cert ("target", cert); subject_cert = cert; maxdepth = 50; for (;;) { xfree (issuer); xfree (subject); issuer = ksba_cert_get_issuer (subject_cert, 0); subject = ksba_cert_get_subject (subject_cert, 0); if (!issuer) { do_list (1, lm, fp, _("no issuer found in certificate")); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } { ksba_isotime_t not_before, not_after; rc = ksba_cert_get_validity (subject_cert, 0, not_before); if (!rc) rc = ksba_cert_get_validity (subject_cert, 1, not_after); if (rc) { do_list (1, lm, fp, _("certificate with invalid validity: %s"), gpg_strerror (rc)); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (*not_after) { if (!*exptime) gnupg_copy_time (exptime, not_after); else if (strcmp (not_after, exptime) < 0 ) gnupg_copy_time (exptime, not_after); } if (*not_before && strcmp (current_time, not_before) < 0 ) { do_list (1, lm, fp, _("certificate not yet valid")); if (!lm) { log_info ("(valid from "); gpgsm_dump_time (not_before); log_printf (")\n"); } rc = gpg_error (GPG_ERR_CERT_TOO_YOUNG); goto leave; } if (*not_after && strcmp (current_time, not_after) > 0 ) { do_list (opt.ignore_expiration?0:1, lm, fp, _("certificate has expired")); if (!lm) { log_info ("(expired at "); gpgsm_dump_time (not_after); log_printf (")\n"); } if (opt.ignore_expiration) log_info ("WARNING: ignoring expiration\n"); else any_expired = 1; } } rc = unknown_criticals (subject_cert, listmode, fp); if (rc) goto leave; if (!opt.no_policy_check) { rc = check_cert_policy (subject_cert, listmode, fp); if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH) { any_no_policy_match = 1; rc = 1; } else if (rc) goto leave; } /* Is this a self-issued certificate? */ if (subject && !strcmp (issuer, subject)) { /* Yes. */ if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) { do_list (1, lm, fp, _("self-signed certificate has a BAD signature")); if (DBG_X509) { gpgsm_dump_cert ("self-signing cert", subject_cert); } rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN : GPG_ERR_BAD_CERT); goto leave; } rc = allowed_ca (subject_cert, NULL, listmode, fp); if (rc) goto leave; /* Set the flag for qualified signatures. This flag is deduced from a list of root certificates allowed for qualified signatures. */ if (is_qualified == -1) { gpg_error_t err; size_t buflen; char buf[1]; if (!ksba_cert_get_user_data (cert, "is_qualified", &buf, sizeof (buf), &buflen) && buflen) { /* We already checked this for this certificate, thus we simply take it from the user data. */ is_qualified = !!*buf; } else { /* Need to consult the list of root certificates for qualified signatures. */ err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL); if (!err) is_qualified = 1; else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND) is_qualified = 0; else log_error ("checking the list of qualified " "root certificates failed: %s\n", gpg_strerror (err)); if ( is_qualified != -1 ) { /* Cache the result but don't care too much about an error. */ buf[0] = !!is_qualified; err = ksba_cert_set_user_data (subject_cert, "is_qualified", buf, 1); if (err) log_error ("set_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); } } } /* Check whether we really trust this root certificate. */ rc = gpgsm_agent_istrusted (ctrl, subject_cert); if (!rc) ; else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) { do_list (0, lm, fp, _("root certificate is not marked trusted")); /* If we already figured out that the certificate is expired it does not make much sense to ask the user whether we wants to trust the root certificate. He should do this only if the certificate under question will then be usable. */ if (!lm && !any_expired) { int rc2; char *fpr = gpgsm_get_fingerprint_string (subject_cert, GCRY_MD_SHA1); log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); xfree (fpr); rc2 = gpgsm_agent_marktrusted (ctrl, subject_cert); if (!rc2) { log_info (_("root certificate has now" " been marked as trusted\n")); rc = 0; } else { gpgsm_dump_cert ("issuer", subject_cert); log_info ("after checking the fingerprint, you may want " "to add it manually to the list of trusted " "certificates.\n"); } } } else { log_error (_("checking the trust list failed: %s\n"), gpg_strerror (rc)); } if (rc) goto leave; /* Check for revocations etc. */ if ((flags & 1)) ; else if (opt.no_trusted_cert_crl_check) ; else rc = is_cert_still_valid (ctrl, lm, fp, subject_cert, subject_cert, &any_revoked, &any_no_crl, &any_crl_too_old); if (rc) goto leave; break; /* Okay: a self-signed certicate is an end-point. */ } depth++; if (depth > maxdepth) { do_list (1, lm, fp, _("certificate chain too long\n")); rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* find the next cert up the tree */ keydb_search_reset (kh); rc = find_up (kh, subject_cert, issuer, 0); if (rc) { if (rc == -1) { do_list (0, lm, fp, _("issuer certificate not found")); if (!lm) { log_info ("issuer certificate: #/"); gpgsm_dump_string (issuer); log_printf ("\n"); } } else log_error ("failed to find issuer's certificate: rc=%d\n", rc); rc = gpg_error (GPG_ERR_MISSING_CERT); goto leave; } ksba_cert_release (issuer_cert); issuer_cert = NULL; rc = keydb_get_cert (kh, &issuer_cert); if (rc) { log_error ("keydb_get_cert() failed: rc=%d\n", rc); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } try_another_cert: if (DBG_X509) { log_debug ("got issuer's certificate:\n"); gpgsm_dump_cert ("issuer", issuer_cert); } rc = gpgsm_check_cert_sig (issuer_cert, subject_cert); if (rc) { do_list (0, lm, fp, _("certificate has a BAD signature")); if (DBG_X509) { gpgsm_dump_cert ("signing issuer", issuer_cert); gpgsm_dump_cert ("signed subject", subject_cert); } if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) { /* We now try to find other issuer certificates which might have been used. This is required because some CAs are reusing the issuer and subject DN for new root certificates. */ /* FIXME: Do this only if we don't have an AKI.keyIdentifier */ rc = find_up (kh, subject_cert, issuer, 1); if (!rc) { ksba_cert_t tmp_cert; rc = keydb_get_cert (kh, &tmp_cert); if (rc || !compare_certs (issuer_cert, tmp_cert)) { /* The find next did not work or returned an identical certificate. We better stop here to avoid infinite checks. */ rc = gpg_error (GPG_ERR_BAD_SIGNATURE); ksba_cert_release (tmp_cert); } else { do_list (0, lm, fp, _("found another possible matching " "CA certificate - trying again")); ksba_cert_release (issuer_cert); issuer_cert = tmp_cert; goto try_another_cert; } } } /* We give a more descriptive error code than the one returned from the signature checking. */ rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } { int chainlen; rc = allowed_ca (issuer_cert, &chainlen, listmode, fp); if (rc) goto leave; if (chainlen >= 0 && (depth - 1) > chainlen) { do_list (1, lm, fp, _("certificate chain longer than allowed by CA (%d)"), chainlen); rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } } if (!listmode) { rc = gpgsm_cert_use_cert_p (issuer_cert); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage", numbuf, NULL); goto leave; } } /* Check for revocations etc. */ if ((flags & 1)) rc = 0; else rc = is_cert_still_valid (ctrl, lm, fp, subject_cert, issuer_cert, &any_revoked, &any_no_crl, &any_crl_too_old); if (rc) goto leave; if (opt.verbose && !listmode) log_info ("certificate is good\n"); keydb_search_reset (kh); subject_cert = issuer_cert; issuer_cert = NULL; } /* End chain traversal. */ if (!listmode) { if (opt.no_policy_check) log_info ("policies not checked due to %s option\n", "--disable-policy-checks"); if (opt.no_crl_check && !ctrl->use_ocsp) log_info ("CRLs not checked due to %s option\n", "--disable-crl-checks"); } if (!rc) { /* If we encountered an error somewhere during the checks, set the error code to the most critical one */ if (any_revoked) rc = gpg_error (GPG_ERR_CERT_REVOKED); else if (any_expired) rc = gpg_error (GPG_ERR_CERT_EXPIRED); else if (any_no_crl) rc = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (any_crl_too_old) rc = gpg_error (GPG_ERR_CRL_TOO_OLD); else if (any_no_policy_match) rc = gpg_error (GPG_ERR_NO_POLICY_MATCH); } leave: if (is_qualified != -1) { /* We figured something about the qualified signature capability of the certificate under question. Store the result as user data in the certificate object. We do this even if the validation itself failed. */ /* Fixme: We should set this flag for all certificates in the chain for optimizing reasons. */ char buf[1]; gpg_error_t err; buf[0] = !!is_qualified; err = ksba_cert_set_user_data (cert, "is_qualified", buf, 1); if (err) { log_error ("set_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); if (!rc) rc = err; } } if (r_exptime) gnupg_copy_time (r_exptime, exptime); xfree (issuer); keydb_release (kh); ksba_cert_release (issuer_cert); if (subject_cert != cert) ksba_cert_release (subject_cert); return rc; } /* Check that the given certificate is valid but DO NOT check any constraints. We assume that the issuers certificate is already in the DB and that this one is valid; which it should be because it has been checked using this function. */ int gpgsm_basic_cert_check (ksba_cert_t cert) { int rc = 0; char *issuer = NULL; char *subject = NULL; KEYDB_HANDLE kh; ksba_cert_t issuer_cert = NULL; if (opt.no_chain_validation) { log_info ("WARNING: bypassing basic certificate checks\n"); return 0; } kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer = ksba_cert_get_issuer (cert, 0); subject = ksba_cert_get_subject (cert, 0); if (!issuer) { log_error ("no issuer found in certificate\n"); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (subject && !strcmp (issuer, subject)) { rc = gpgsm_check_cert_sig (cert, cert); if (rc) { log_error ("self-signed certificate has a BAD signature: %s\n", gpg_strerror (rc)); if (DBG_X509) { gpgsm_dump_cert ("self-signing cert", cert); } rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } } else { /* Find the next cert up the tree. */ keydb_search_reset (kh); rc = find_up (kh, cert, issuer, 0); if (rc) { if (rc == -1) { log_info ("issuer certificate (#/"); gpgsm_dump_string (issuer); log_printf (") not found\n"); } else log_error ("failed to find issuer's certificate: rc=%d\n", rc); rc = gpg_error (GPG_ERR_MISSING_CERT); goto leave; } ksba_cert_release (issuer_cert); issuer_cert = NULL; rc = keydb_get_cert (kh, &issuer_cert); if (rc) { log_error ("keydb_get_cert() failed: rc=%d\n", rc); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } rc = gpgsm_check_cert_sig (issuer_cert, cert); if (rc) { log_error ("certificate has a BAD signature: %s\n", gpg_strerror (rc)); if (DBG_X509) { gpgsm_dump_cert ("signing issuer", issuer_cert); gpgsm_dump_cert ("signed subject", cert); } rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (opt.verbose) log_info ("certificate is good\n"); } leave: xfree (issuer); keydb_release (kh); ksba_cert_release (issuer_cert); return rc; } /* Check whether the certificate CERT has been issued by the German authority for qualified signature. They do not set the basicConstraints and thus we need this workaround. It works by looking up the root certificate and checking whether that one is listed as a qualified certificate for Germany. We also try to cache this data but as long as don't keep a reference to the certificate this won't be used. Returns: True if CERT is a RegTP issued CA cert (i.e. the root certificate itself or one of the CAs). In that case CHAINLEN will receive the length of the chain which is either 0 or 1. */ static int get_regtp_ca_info (ksba_cert_t cert, int *chainlen) { gpg_error_t err; ksba_cert_t next; int rc = 0; int i, depth; char country[3]; ksba_cert_t array[4]; char buf[2]; size_t buflen; int dummy_chainlen; if (!chainlen) chainlen = &dummy_chainlen; *chainlen = 0; err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen", &buf, sizeof (buf), &buflen); if (!err) { /* Got info. */ if (buflen < 2 || !*buf) return 0; /* Nothing found. */ *chainlen = buf[1]; return 1; /* This is a regtp CA. */ } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { log_error ("ksba_cert_get_user_data(%s) failed: %s\n", "regtp_ca_chainlen", gpg_strerror (err)); return 0; /* Nothing found. */ } /* Need to gather the info. This requires to walk up the chain until we have found the root. Because we are only interested in German Bundesnetzagentur (former RegTP) derived certificates 3 levels are enough. (The German signature law demands a 3 tier hierachy; thus there is only one CA between the EE and the Root CA.) */ memset (&array, 0, sizeof array); depth = 0; ksba_cert_ref (cert); array[depth++] = cert; ksba_cert_ref (cert); while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (cert, &next))) { ksba_cert_release (cert); ksba_cert_ref (next); array[depth++] = next; cert = next; } ksba_cert_release (cert); if (rc != -1 || !depth || depth == DIM(array) ) { /* We did not reached the root. */ goto leave; } /* If this is a German signature law issued certificate, we store additional additional information. */ if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country) && !strcmp (country, "de")) { /* Setting the pathlen for the root CA and the CA flag for the next one is all what we need to do. */ err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen", "\x01\x01", 2); if (!err && depth > 1) err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen", "\x01\x00", 2); if (err) log_error ("ksba_set_user_data(%s) failed: %s\n", "regtp_ca_chainlen", gpg_strerror (err)); for (i=0; i < depth; i++) ksba_cert_release (array[i]); *chainlen = (depth>1? 0:1); return 1; } leave: /* Nothing special with this certificate. Mark the target certificate anyway to avoid duplicate lookups. */ err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1); if (err) log_error ("ksba_set_user_data(%s) failed: %s\n", "regtp_ca_chainlen", gpg_strerror (err)); for (i=0; i < depth; i++) ksba_cert_release (array[i]); return 0; } diff --git a/sm/certcheck.c b/sm/certcheck.c index 5fb376712..732356149 100644 --- a/sm/certcheck.c +++ b/sm/certcheck.c @@ -1,363 +1,364 @@ /* certcheck.c - check one certificate * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" static int do_encode_md (gcry_md_hd_t md, int algo, int pkalgo, unsigned int nbits, gcry_mpi_t *r_val) { int n; size_t nframe; unsigned char *frame; if (pkalgo == GCRY_PK_DSA) { nframe = gcry_md_get_algo_dlen (algo); if (nframe != 20) { log_error (_("DSA requires the use of a 160 bit hash algorithm\n")); return gpg_error (GPG_ERR_INTERNAL); } frame = xtrymalloc (nframe); if (!frame) return OUT_OF_CORE (errno); memcpy (frame, gcry_md_read (md, algo), nframe); n = nframe; } else { int i; unsigned char asn[100]; size_t asnlen; size_t len; nframe = (nbits+7) / 8; 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); } len = gcry_md_get_algo_dlen (algo); if ( len + asnlen + 4 > nframe ) { log_error ("can't encode a %d bit MD into a %d bits frame\n", (int)(len*8), (int)nbits); return gpg_error (GPG_ERR_INTERNAL); } /* We encode the MD in this way: * * 0 A PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) * * PAD consists of FF bytes. */ frame = xtrymalloc (nframe); if (!frame) return OUT_OF_CORE (errno); n = 0; frame[n++] = 0; frame[n++] = 1; /* block type */ i = nframe - len - 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, gcry_md_read(md, algo), len ); n += len; assert ( n == nframe ); } if (DBG_CRYPTO) { int j; log_debug ("encoded hash:"); for (j=0; j < nframe; j++) log_printf (" %02X", frame[j]); log_printf ("\n"); } gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe); xfree (frame); return 0; } /* Return the public key algorithm id from the S-expression PKEY. FIXME: libgcrypt should provide such a function. Note that this implementation uses the names as used by libksba. */ static int pk_algo_from_sexp (gcry_sexp_t pkey) { gcry_sexp_t l1, l2; const char *name; size_t n; int algo; l1 = gcry_sexp_find_token (pkey, "public-key", 0); if (!l1) return 0; /* Not found. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); name = gcry_sexp_nth_data (l2, 0, &n); if (!name) algo = 0; /* Not found. */ else if (n==3 && !memcmp (name, "rsa", 3)) algo = GCRY_PK_RSA; else if (n==3 && !memcmp (name, "dsa", 3)) algo = GCRY_PK_DSA; else if (n==13 && !memcmp (name, "ambiguous-rsa", 13)) algo = GCRY_PK_RSA; else algo = 0; gcry_sexp_release (l2); return algo; } /* Check the signature on CERT using the ISSUER-CERT. This function does only test the cryptographic signature and nothing else. It is assumed that the ISSUER_CERT is valid. */ int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) { const char *algoid; gcry_md_hd_t md; int rc, algo; gcry_mpi_t frame; ksba_sexp_t p; size_t n; gcry_sexp_t s_sig, s_hash, s_pkey; algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert))); if (!algo) { log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); if (algoid && ( !strcmp (algoid, "1.2.840.113549.1.1.2") ||!strcmp (algoid, "1.2.840.113549.2.2"))) log_info (_("(this is the MD2 algorithm)\n")); return gpg_error (GPG_ERR_GENERAL); } rc = gcry_md_open (&md, algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); return rc; } if (DBG_HASHING) gcry_md_start_debug (md, "hash.cert"); rc = ksba_cert_hash (cert, 1, HASH_FNC, md); if (rc) { log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); return rc; } gcry_md_final (md); p = ksba_cert_get_sig_val (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); return gpg_error (GPG_ERR_BUG); } if (DBG_CRYPTO) { int j; log_debug ("signature value:"); for (j=0; j < n; j++) log_printf (" %02X", p[j]); log_printf ("\n"); } rc = gcry_sexp_sscan ( &s_sig, NULL, (char*)p, n); ksba_free (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); return rc; } p = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); ksba_free (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); gcry_sexp_release (s_sig); return rc; } rc = do_encode_md (md, algo, pk_algo_from_sexp (s_pkey), gcry_pk_get_nbits (s_pkey), &frame); if (rc) { gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_pkey); return rc; } /* put hash into the S-Exp s_hash */ if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) BUG (); gcry_mpi_release (frame); rc = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return rc; } int gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, gcry_md_hd_t md, int algo) { int rc; ksba_sexp_t p; gcry_mpi_t frame; gcry_sexp_t s_sig, s_hash, s_pkey; size_t n; n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan (&s_sig, NULL, (char*)sigval, n); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return rc; } p = ksba_cert_get_public_key (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); ksba_free (p); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } if (DBG_CRYPTO) log_printhex ("public key: ", p, n); rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); ksba_free (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); gcry_sexp_release (s_sig); return rc; } rc = do_encode_md (md, algo, pk_algo_from_sexp (s_pkey), gcry_pk_get_nbits (s_pkey), &frame); if (rc) { gcry_sexp_release (s_sig); gcry_sexp_release (s_pkey); return rc; } /* put hash into the S-Exp s_hash */ if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) BUG (); gcry_mpi_release (frame); rc = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return rc; } int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, unsigned char **r_sigval) { int rc; char *grip, *desc; size_t siglen; grip = gpgsm_get_keygrip_hexstring (cert); if (!grip) return gpg_error (GPG_ERR_BAD_CERT); desc = gpgsm_format_keydesc (cert); rc = gpgsm_agent_pksign (ctrl, grip, desc, gcry_md_read(md, mdalgo), gcry_md_get_algo_dlen (mdalgo), mdalgo, r_sigval, &siglen); xfree (desc); xfree (grip); return rc; } diff --git a/sm/certdump.c b/sm/certdump.c index 1f2ea7b18..0d5146abc 100644 --- a/sm/certdump.c +++ b/sm/certdump.c @@ -1,771 +1,772 @@ /* certdump.c - Dump a certificate for debugging * Copyright (C) 2001, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" struct dn_array_s { char *key; char *value; int multivalued; int done; }; /* print the first element of an S-Expression */ void gpgsm_print_serial (FILE *fp, ksba_const_sexp_t sn) { const char *p = (const char *)sn; unsigned long n; char *endp; if (!p) fputs (_("none"), fp); else if (*p != '(') fputs ("[Internal error - not an S-expression]", fp); else { p++; n = strtoul (p, &endp, 10); p = endp; if (*p!=':') fputs ("[Internal Error - invalid S-expression]", fp); else { for (p++; n; n--, p++) fprintf (fp, "%02X", *(const unsigned char*)p); } } } /* Dump the serial number or any other simple S-expression. */ void gpgsm_dump_serial (ksba_const_sexp_t sn) { const char *p = (const char *)sn; unsigned long n; char *endp; if (!p) log_printf ("none"); else if (*p != '(') log_printf ("ERROR - not an S-expression"); else { p++; n = strtoul (p, &endp, 10); p = endp; if (*p!=':') log_printf ("ERROR - invalid S-expression"); else { for (p++; n; n--, p++) log_printf ("%02X", *(const unsigned char *)p); } } } char * gpgsm_format_serial (ksba_const_sexp_t sn) { const char *p = (const char *)sn; unsigned long n; char *endp; char *buffer; int i; if (!p) return NULL; if (*p != '(') BUG (); /* Not a valid S-expression. */ p++; n = strtoul (p, &endp, 10); p = endp; if (*p!=':') BUG (); /* Not a valid S-expression. */ p++; buffer = xtrymalloc (n*2+1); if (buffer) { for (i=0; n; n--, p++, i+=2) sprintf (buffer+i, "%02X", *(unsigned char *)p); buffer[i] = 0; } return buffer; } void gpgsm_print_time (FILE *fp, ksba_isotime_t t) { if (!t || !*t) fputs (_("none"), fp); else fprintf (fp, "%.4s-%.2s-%.2s %.2s:%.2s:%s", t, t+4, t+6, t+9, t+11, t+13); } void gpgsm_dump_time (ksba_isotime_t t) { if (!t || !*t) log_printf (_("[none]")); else log_printf ("%.4s-%.2s-%.2s %.2s:%.2s:%s", t, t+4, t+6, t+9, t+11, t+13); } void gpgsm_dump_string (const char *string) { if (!string) log_printf ("[error]"); else { const unsigned char *s; for (s=(const unsigned char*)string; *s; s++) { if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) break; } if (!*s && *string != '[') log_printf ("%s", string); else { log_printf ( "[ "); log_printhex (NULL, string, strlen (string)); log_printf ( " ]"); } } } /* This simple dump function is mainly used for debugging purposes. */ void gpgsm_dump_cert (const char *text, ksba_cert_t cert) { ksba_sexp_t sexp; char *p; char *dn; ksba_isotime_t t; log_debug ("BEGIN Certificate `%s':\n", text? text:""); if (cert) { sexp = ksba_cert_get_serial (cert); log_debug (" serial: "); gpgsm_dump_serial (sexp); ksba_free (sexp); log_printf ("\n"); ksba_cert_get_validity (cert, 0, t); log_debug (" notBefore: "); gpgsm_dump_time (t); log_printf ("\n"); ksba_cert_get_validity (cert, 1, t); log_debug (" notAfter: "); gpgsm_dump_time (t); log_printf ("\n"); dn = ksba_cert_get_issuer (cert, 0); log_debug (" issuer: "); gpgsm_dump_string (dn); ksba_free (dn); log_printf ("\n"); dn = ksba_cert_get_subject (cert, 0); log_debug (" subject: "); gpgsm_dump_string (dn); ksba_free (dn); log_printf ("\n"); log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); p = gpgsm_get_fingerprint_string (cert, 0); log_debug (" SHA1 Fingerprint: %s\n", p); xfree (p); } log_debug ("END Certificate\n"); } /* helper for the rfc2253 string parser */ static const unsigned char * parse_dn_part (struct dn_array_s *array, const unsigned char *string) { static struct { const char *label; const char *oid; } label_map[] = { /* Warning: When adding new labels, make sure that the buffer below we be allocated large enough. */ {"EMail", "1.2.840.113549.1.9.1" }, {"T", "2.5.4.12" }, {"GN", "2.5.4.42" }, {"SN", "2.5.4.4" }, {"NameDistinguisher", "0.2.262.1.10.7.20"}, {"ADDR", "2.5.4.16" }, {"BC", "2.5.4.15" }, {"D", "2.5.4.13" }, {"PostalCode", "2.5.4.17" }, {"Pseudo", "2.5.4.65" }, {"SerialNumber", "2.5.4.5" }, {NULL, NULL} }; const unsigned char *s, *s1; size_t n; char *p; int i; /* Parse attributeType */ for (s = string+1; *s && *s != '='; s++) ; if (!*s) return NULL; /* error */ n = s - string; if (!n) return NULL; /* empty key */ /* We need to allocate a few bytes more due to the possible mapping from the shorter OID to the longer label. */ array->key = p = xtrymalloc (n+10); if (!array->key) return NULL; memcpy (p, string, n); p[n] = 0; trim_trailing_spaces (p); if (digitp (p)) { for (i=0; label_map[i].label; i++ ) if ( !strcmp (p, label_map[i].oid) ) { strcpy (p, label_map[i].label); break; } } string = s + 1; if (*string == '#') { /* hexstring */ string++; for (s=string; hexdigitp (s); s++) s++; n = s - string; if (!n || (n & 1)) return NULL; /* Empty or odd number of digits. */ n /= 2; array->value = p = xtrymalloc (n+1); if (!p) return NULL; for (s1=string; n; s1 += 2, n--, p++) { *(unsigned char *)p = xtoi_2 (s1); if (!*p) *p = 0x01; /* Better print a wrong value than truncating the string. */ } *p = 0; } else { /* regular v3 quoted string */ for (n=0, s=string; *s; s++) { if (*s == '\\') { /* pair */ s++; if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' || *s == '\\' || *s == '\"' || *s == ' ') n++; else if (hexdigitp (s) && hexdigitp (s+1)) { s++; n++; } else return NULL; /* invalid escape sequence */ } else if (*s == '\"') return NULL; /* invalid encoding */ else if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' ) break; else n++; } array->value = p = xtrymalloc (n+1); if (!p) return NULL; for (s=string; n; s++, n--) { if (*s == '\\') { s++; if (hexdigitp (s)) { *(unsigned char *)p++ = xtoi_2 (s); s++; } else *p++ = *s; } else *p++ = *s; } *p = 0; } return s; } /* Parse a DN and return an array-ized one. This is not a validating parser and it does not support any old-stylish syntax; KSBA is expected to return only rfc2253 compatible strings. */ static struct dn_array_s * parse_dn (const unsigned char *string) { struct dn_array_s *array; size_t arrayidx, arraysize; int i; arraysize = 7; /* C,ST,L,O,OU,CN,email */ arrayidx = 0; array = xtrymalloc ((arraysize+1) * sizeof *array); if (!array) return NULL; while (*string) { while (*string == ' ') string++; if (!*string) break; /* ready */ if (arrayidx >= arraysize) { struct dn_array_s *a2; arraysize += 5; a2 = xtryrealloc (array, (arraysize+1) * sizeof *array); if (!a2) goto failure; array = a2; } array[arrayidx].key = NULL; array[arrayidx].value = NULL; string = parse_dn_part (array+arrayidx, string); if (!string) goto failure; while (*string == ' ') string++; array[arrayidx].multivalued = (*string == '+'); array[arrayidx].done = 0; arrayidx++; if (*string && *string != ',' && *string != ';' && *string != '+') goto failure; /* invalid delimiter */ if (*string) string++; } array[arrayidx].key = NULL; array[arrayidx].value = NULL; return array; failure: for (i=0; i < arrayidx; i++) { xfree (array[i].key); xfree (array[i].value); } xfree (array); return NULL; } static void print_dn_part (FILE *fp, struct dn_array_s *dn, const char *key, int translate) { struct dn_array_s *first_dn = dn; for (; dn->key; dn++) { if (!dn->done && !strcmp (dn->key, key)) { /* Forward to the last multi-valued RDN, so that we can print them all in reverse in the correct order. Note that this overrides the the standard sequence but that seems to a reasonable thing to do with multi-valued RDNs. */ while (dn->multivalued && dn[1].key) dn++; next: if (!dn->done && dn->value && *dn->value) { fprintf (fp, "/%s=", dn->key); if (translate) print_sanitized_utf8_string (fp, dn->value, '/'); else print_sanitized_string (fp, dn->value, '/'); } dn->done = 1; if (dn > first_dn && dn[-1].multivalued) { dn--; goto next; } } } } /* Print all parts of a DN in a "standard" sequence. We first print all the known parts, followed by the uncommon ones */ static void print_dn_parts (FILE *fp, struct dn_array_s *dn, int translate) { const char *stdpart[] = { "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL }; int i; for (i=0; stdpart[i]; i++) print_dn_part (fp, dn, stdpart[i], translate); /* Now print the rest without any specific ordering */ for (; dn->key; dn++) print_dn_part (fp, dn, dn->key, translate); } /* Print the S-Expression in BUF, which has a valid length of BUFLEN, as a human readable string in one line to FP. */ static void pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen) { size_t len; gcry_sexp_t sexp; char *result, *p; if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) ) { fputs (_("[Error - invalid encoding]"), fp); return; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); assert (len); result = xtrymalloc (len); if (!result) { fputs (_("[Error - out of core]"), fp); gcry_sexp_release (sexp); return; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); assert (len); for (p = result; len; len--, p++) { if (*p == '\n') { if (len > 1) /* Avoid printing the trailing LF. */ fputs ("\\n", fp); } else if (*p == '\r') fputs ("\\r", fp); else if (*p == '\v') fputs ("\\v", fp); else if (*p == '\t') fputs ("\\t", fp); else putc (*p, fp); } xfree (result); gcry_sexp_release (sexp); } void gpgsm_print_name2 (FILE *fp, const char *name, int translate) { const unsigned char *s = (const unsigned char *)name; int i; if (!s) { fputs (_("[Error - No name]"), fp); } else if (*s == '<') { const char *s2 = strchr ( (char*)s+1, '>'); if (s2) { if (translate) print_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, 0); else print_sanitized_buffer (fp, s + 1, s2 - (char*)s - 1, 0); } } else if (*s == '(') { pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL)); } else if (!((*s >= '0' && *s < '9') || (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))) fputs (_("[Error - invalid encoding]"), fp); else { struct dn_array_s *dn = parse_dn (s); if (!dn) fputs (_("[Error - invalid DN]"), fp); else { print_dn_parts (fp, dn, translate); for (i=0; dn[i].key; i++) { xfree (dn[i].key); xfree (dn[i].value); } xfree (dn); } } } void gpgsm_print_name (FILE *fp, const char *name) { gpgsm_print_name2 (fp, name, 1); } /* A cookie structure used for the memory stream. */ struct format_name_cookie { char *buffer; /* Malloced buffer with the data to deliver. */ size_t size; /* Allocated size of this buffer. */ size_t len; /* strlen (buffer). */ int error; /* system error code if any. */ }; /* The writer function for the memory stream. */ static int format_name_writer (void *cookie, const char *buffer, size_t size) { struct format_name_cookie *c = cookie; char *p; if (c->buffer) p = xtryrealloc (c->buffer, c->size + size + 1); else p = xtrymalloc (size + 1); if (!p) { c->error = errno; xfree (c->buffer); errno = c->error; return -1; } c->buffer = p; memcpy (p + c->len, buffer, size); c->len += size; p[c->len] = 0; /* Terminate string. */ return size; } /* Format NAME which is expected to be in rfc2253 format into a better human readable format. Caller must free the returned string. NULL is returned in case of an error. With TRANSLATE set to true the name will be translated to the native encoding. Note that NAME is internally always UTF-8 encoded. */ char * gpgsm_format_name2 (const char *name, int translate) { #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN) FILE *fp; struct format_name_cookie cookie; memset (&cookie, 0, sizeof cookie); #ifdef HAVE_FOPENCOOKIE { cookie_io_functions_t io = { NULL }; io.write = format_name_writer; fp = fopencookie (&cookie, "w", io); } #else /*!HAVE_FOPENCOOKIE*/ { fp = funopen (&cookie, NULL, format_name_writer, NULL, NULL); } #endif /*!HAVE_FOPENCOOKIE*/ if (!fp) { int save_errno = errno; log_error ("error creating memory stream: %s\n", strerror (errno)); errno = save_errno; return NULL; } gpgsm_print_name2 (fp, name, translate); fclose (fp); if (cookie.error || !cookie.buffer) { xfree (cookie.buffer); errno = cookie.error; return NULL; } return cookie.buffer; #else /* No fun - use the name verbatim. */ return xtrystrdup (name); #endif /* No fun. */ } char * gpgsm_format_name (const char *name) { return gpgsm_format_name2 (name, 1); } /* Create a key description for the CERT, this may be passed to the pinentry. The caller must free the returned string. NULL may be returned on error. */ char * gpgsm_format_keydesc (ksba_cert_t cert) { int rc; char *name, *subject, *buffer, *p; const char *s; ksba_isotime_t t; char created[20]; char *sn; ksba_sexp_t sexp; char *orig_codeset = NULL; name = ksba_cert_get_subject (cert, 0); subject = name? gpgsm_format_name2 (name, 0) : NULL; ksba_free (name); name = NULL; sexp = ksba_cert_get_serial (cert); sn = sexp? gpgsm_format_serial (sexp) : NULL; ksba_free (sexp); ksba_cert_get_validity (cert, 0, t); if (t && *t) sprintf (created, "%.4s-%.2s-%.2s", t, t+4, t+6); else *created = 0; #ifdef ENABLE_NLS /* The Assuan agent protocol requires us to transmit utf-8 strings */ orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); #ifdef HAVE_LANGINFO_CODESET if (!orig_codeset) orig_codeset = nl_langinfo (CODESET); #endif if (orig_codeset) { /* We only switch when we are able to restore the codeset later. Note that bind_textdomain_codeset does only return on memory errors but not if a codeset is not available. Thus we don't bother printing a diagnostic here. */ orig_codeset = xstrdup (orig_codeset); if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8")) orig_codeset = NULL; } #endif rc = asprintf (&name, _("Please enter the passphrase to unlock the" " secret key for:\n" "\"%s\"\n" "S/N %s, ID %08lX, created %s" ), subject? subject:"?", sn? sn: "?", gpgsm_get_short_fingerprint (cert), created); #ifdef ENABLE_NLS if (orig_codeset) bind_textdomain_codeset (PACKAGE_GT, orig_codeset); #endif xfree (orig_codeset); if (rc < 0) { int save_errno = errno; xfree (subject); xfree (sn); errno = save_errno; return NULL; } xfree (subject); xfree (sn); buffer = p = xtrymalloc (strlen (name) * 3 + 1); for (s=name; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *(unsigned char *)s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; free (name); return buffer; } diff --git a/sm/certlist.c b/sm/certlist.c index b036a85d7..cde2930ec 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -1,484 +1,485 @@ /* certlist.c - build list of certificates * Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; /* Return 0 if the cert is usable for encryption. A MODE of 0 checks for signing a MODE of 1 checks for encryption, a MODE of 2 checks for verification and a MODE of 3 for decryption (just for debugging). MODE 4 is for certificate signing, MODE for COSP response signing. */ static int cert_usage_p (ksba_cert_t cert, int mode) { gpg_error_t err; unsigned int use; char *extkeyusages; int have_ocsp_signing = 0; err = ksba_cert_get_ext_key_usages (cert, &extkeyusages); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; /* no policy given */ if (!err) { unsigned int extusemask = ~0; /* Allow all. */ if (extkeyusages) { char *p, *pend; int any_critical = 0; extusemask = 0; p = extkeyusages; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; /* Only care about critical flagged usages. */ if ( *pend == 'C' ) { any_critical = 1; if ( !strcmp (p, oid_kp_serverAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_clientAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_codeSigning)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE); else if ( !strcmp (p, oid_kp_emailProtection)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_timeStamping)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION); } /* This is a hack to cope with OCSP. Note that we do not yet fully comply with the requirements and that the entire CRL/OCSP checking thing should undergo a thorough review and probably redesign. */ if ( !strcmp (p, oid_kp_ocspSigning)) have_ocsp_signing = 1; if ((p = strchr (pend, '\n'))) p++; } xfree (extkeyusages); extkeyusages = NULL; if (!any_critical) extusemask = ~0; /* Reset to the don't care mask. */ } err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (opt.verbose && mode < 2) log_info (_("no key usage specified - assuming all usages\n")); use = ~0; } /* Apply extKeyUsage. */ use &= extusemask; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); xfree (extkeyusages); return err; } if (mode == 4) { if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) return 0; log_info (_("certificate should have not " "been used for certification\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (mode == 5) { if (use != ~0 && (have_ocsp_signing || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN |KSBA_KEYUSAGE_CRL_SIGN)))) return 0; log_info (_("certificate should have not " "been used for OCSP response signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if ((use & ((mode&1)? (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT): (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) ) return 0; log_info (mode==3? _("certificate should have not been used for encryption\n"): mode==2? _("certificate should have not been used for signing\n"): mode==1? _("certificate is not usable for encryption\n"): _("certificate is not usable for signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Return 0 if the cert is usable for signing */ int gpgsm_cert_use_sign_p (ksba_cert_t cert) { return cert_usage_p (cert, 0); } /* Return 0 if the cert is usable for encryption */ int gpgsm_cert_use_encrypt_p (ksba_cert_t cert) { return cert_usage_p (cert, 1); } int gpgsm_cert_use_verify_p (ksba_cert_t cert) { return cert_usage_p (cert, 2); } int gpgsm_cert_use_decrypt_p (ksba_cert_t cert) { return cert_usage_p (cert, 3); } int gpgsm_cert_use_cert_p (ksba_cert_t cert) { return cert_usage_p (cert, 4); } int gpgsm_cert_use_ocsp_p (ksba_cert_t cert) { return cert_usage_p (cert, 5); } static int same_subject_issuer (const char *subject, const char *issuer, ksba_cert_t cert) { char *subject2 = ksba_cert_get_subject (cert, 0); char *issuer2 = ksba_cert_get_subject (cert, 0); int tmp; tmp = (subject && subject2 && !strcmp (subject, subject2) && issuer && issuer2 && !strcmp (issuer, issuer2)); xfree (subject2); xfree (issuer2); return tmp; } /* Return true if CERT is already contained in CERTLIST. */ static int is_cert_in_certlist (ksba_cert_t cert, certlist_t certlist) { const unsigned char *img_a, *img_b; size_t len_a, len_b; img_a = ksba_cert_get_image (cert, &len_a); if (img_a) { for ( ; certlist; certlist = certlist->next) { img_b = ksba_cert_get_image (certlist->cert, &len_b); if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a)) return 1; /* Already contained. */ } } return 0; } /* Add CERT to the list of certificates at CERTADDR but avoid duplicates. */ int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, certlist_t *listaddr, int is_encrypt_to) { if (!is_cert_in_certlist (cert, *listaddr)) { certlist_t cl = xtrycalloc (1, sizeof *cl); if (!cl) return OUT_OF_CORE (errno); cl->cert = cert; ksba_cert_ref (cert); cl->next = *listaddr; cl->is_encrypt_to = is_encrypt_to; *listaddr = cl; } return 0; } /* Add a certificate to a list of certificate and make sure that it is a valid certificate. With SECRET set to true a secret key must be available for the certificate. IS_ENCRYPT_TO sets the corresponding flag in the new create LISTADDR item. */ int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, CERTLIST *listaddr, int is_encrypt_to) { int rc; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; ksba_cert_t cert = NULL; rc = keydb_classify_name (name, &desc); if (!rc) { kh = keydb_new (0); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); else { int wrong_usage = 0; char *subject = NULL; char *issuer = NULL; get_next: rc = keydb_search (kh, &desc, 1); if (!rc) rc = keydb_get_cert (kh, &cert); if (!rc) { rc = secret? gpgsm_cert_use_sign_p (cert) : gpgsm_cert_use_encrypt_p (cert); if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE) { /* There might be another certificate with the correct usage, so we try again */ if (!wrong_usage) { /* save the first match */ wrong_usage = rc; subject = ksba_cert_get_subject (cert, 0); issuer = ksba_cert_get_subject (cert, 0); ksba_cert_release (cert); cert = NULL; goto get_next; } else if (same_subject_issuer (subject, issuer, cert)) { wrong_usage = rc; ksba_cert_release (cert); cert = NULL; goto get_next; } else wrong_usage = rc; } } /* We want the error code from the first match in this case. */ if (rc && wrong_usage) rc = wrong_usage; if (!rc) { next_ambigious: rc = keydb_search (kh, &desc, 1); if (rc == -1) rc = 0; else if (!rc) { ksba_cert_t cert2 = NULL; /* We have to ignore ambigious names as long as there only fault is a bad key usage */ if (!keydb_get_cert (kh, &cert2)) { int tmp = (same_subject_issuer (subject, issuer, cert2) && ((gpg_err_code ( secret? gpgsm_cert_use_sign_p (cert2) : gpgsm_cert_use_encrypt_p (cert2) ) ) == GPG_ERR_WRONG_KEY_USAGE)); ksba_cert_release (cert2); if (tmp) goto next_ambigious; } rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); } } xfree (subject); xfree (issuer); if (!rc && !is_cert_in_certlist (cert, *listaddr)) { if (!rc && secret) { char *p; rc = gpg_error (GPG_ERR_NO_SECKEY); p = gpgsm_get_keygrip_hexstring (cert); if (p) { if (!gpgsm_agent_havekey (ctrl, p)) rc = 0; xfree (p); } } if (!rc) rc = gpgsm_validate_chain (ctrl, cert, NULL, 0, NULL, 0); if (!rc) { CERTLIST cl = xtrycalloc (1, sizeof *cl); if (!cl) rc = OUT_OF_CORE (errno); else { cl->cert = cert; cert = NULL; cl->next = *listaddr; cl->is_encrypt_to = is_encrypt_to; *listaddr = cl; } } } } } keydb_release (kh); ksba_cert_release (cert); return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; } void gpgsm_release_certlist (CERTLIST list) { while (list) { CERTLIST cl = list->next; ksba_cert_release (list->cert); xfree (list); list = cl; } } /* Like gpgsm_add_to_certlist, but look only for one certificate. No chain validation is done. If KEYID is not NULL it is take as an additional filter value which must match the subjectKeyIdentifier. */ int gpgsm_find_cert (const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert) { int rc; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; *r_cert = NULL; rc = keydb_classify_name (name, &desc); if (!rc) { kh = keydb_new (0); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); else { nextone: rc = keydb_search (kh, &desc, 1); if (!rc) { rc = keydb_get_cert (kh, r_cert); if (!rc && keyid) { ksba_sexp_t subj; rc = ksba_cert_get_subj_key_id (*r_cert, NULL, &subj); if (!rc) { if (cmp_simple_canon_sexp (keyid, subj)) { xfree (subj); goto nextone; } xfree (subj); /* Okay: Here we know that the certificate's subjectKeyIdentifier matches the requested one. */ } else if (gpg_err_code (rc) == GPG_ERR_NO_DATA) goto nextone; } } /* If we don't have the KEYID filter we need to check for ambigious search results. Note, that it is somehwat reasonable to assume that a specification of a KEYID won't lead to ambiguous names. */ if (!rc && !keyid) { rc = keydb_search (kh, &desc, 1); if (rc == -1) rc = 0; else { if (!rc) rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); ksba_cert_release (*r_cert); *r_cert = NULL; } } } } keydb_release (kh); return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; } diff --git a/sm/certreqgen.c b/sm/certreqgen.c index c523c992a..744969719 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -1,828 +1,829 @@ /* certreqgen.c - Generate a key and a certification request * Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* The format of the native parameter file is follows: o Text only, line length is limited to about 1000 chars. o You must use UTF-8 encoding to specify non-ascii characters. o Empty lines are ignored. o Leading and trailing spaces are ignored. o A hash sign as the first non white space character is a comment line. o Control statements are indicated by a leading percent sign, the arguments are separated by white space from the keyword. o Parameters are specified by a keyword, followed by a colon. Arguments are separated by white space. o The first parameter must be "Key-Type", control statements may be placed anywhere. o Key generation takes place when either the end of the parameter file is reached, the next "Key-Type" parameter is encountered or at the controlstatement "%commit" o Control statements: %echo Print . %dry-run Suppress actual key generation (useful for syntax checking). %commit Perform the key generation. Note that an implicit commit is done at the next "Key-Type" parameter. %certfile Do not write the certificate to the keyDB but to . This must be given before the first commit to take place, duplicate specification of the same filename is ignored, the last filename before a commit is used. The filename is used until a new filename is used (at commit points) and all keys are written to that file. If a new filename is given, this file is created (and overwrites an existing one). Both control statements must be given. o The order of the parameters does not matter except for "Key-Type" which must be the first parameter. The parameters are only for the generated keyblock and parameters from previous key generations are not used. Some syntactically checks may be performed. The currently defined parameters are: Key-Type: Starts a new parameter block by giving the type of the primary key. The algorithm must be capable of signing. This is a required parameter. For now the only supported algorithm is "rsa". Key-Length: Length of the key in bits. Default is 1024. Key-Grip: hexstring This is optional and used to generate a request for an already existsing key. Key-Length will be ignored when given, Key-Usage: Space or comma delimited list of key usage, allowed values are "encrypt" and "sign". This is used to generate the KeyUsage extension. Please make sure that the algorithm is capable of this usage. Default is to allow encrypt and sign. Name-DN: subject name This is the DN name of the subject in rfc2253 format. Name-Email: The is an email address for the altSubjectName Name-DNS: The is an DNS name for the altSubjectName Name-URI: The is an URI for the altSubjectName Here is an example: $ cat >foo < #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" enum para_name { pKEYTYPE, pKEYLENGTH, pKEYGRIP, pKEYUSAGE, pNAMEDN, pNAMEEMAIL, pNAMEDNS, pNAMEURI }; struct para_data_s { struct para_data_s *next; int lnr; enum para_name key; union { unsigned int usage; char value[1]; } u; }; struct reqgen_ctrl_s { int lnr; int dryrun; ksba_writer_t writer; }; static const char oidstr_keyUsage[] = "2.5.29.15"; static int proc_parameters (ctrl_t ctrl, struct para_data_s *para, struct reqgen_ctrl_s *outctrl); static int create_request (ctrl_t ctrl, struct para_data_s *para, ksba_const_sexp_t public, struct reqgen_ctrl_s *outctrl); static void release_parameter_list (struct para_data_s *r) { struct para_data_s *r2; for (; r ; r = r2) { r2 = r->next; xfree(r); } } static struct para_data_s * get_parameter (struct para_data_s *para, enum para_name key, int seq) { struct para_data_s *r; for (r = para; r ; r = r->next) if ( r->key == key && !seq--) return r; return NULL; } static const char * get_parameter_value (struct para_data_s *para, enum para_name key, int seq) { struct para_data_s *r = get_parameter (para, key, seq); return (r && *r->u.value)? r->u.value : NULL; } static int get_parameter_algo (struct para_data_s *para, enum para_name key) { struct para_data_s *r = get_parameter (para, key, 0); if (!r) return -1; if (digitp (r->u.value)) return atoi( r->u.value ); return gcry_pk_map_name (r->u.value); } /* Parse the usage parameter. Returns 0 on success. Note that we only care about sign and encrypt and don't (yet) allow all the other X.509 usage to be specified; instead we will use a fixed mapping to the X.509 usage flags. */ static int parse_parameter_usage (struct para_data_s *para, enum para_name key) { struct para_data_s *r = get_parameter (para, key, 0); char *p, *pn; unsigned int use; if (!r) return 0; /* none (this is an optional parameter)*/ use = 0; pn = r->u.value; while ( (p = strsep (&pn, " \t,")) ) { if (!*p) ; else if ( !ascii_strcasecmp (p, "sign") ) use |= GCRY_PK_USAGE_SIGN; else if ( !ascii_strcasecmp (p, "encrypt") ) use |= GCRY_PK_USAGE_ENCR; else { log_error ("line %d: invalid usage list\n", r->lnr); return -1; /* error */ } } r->u.usage = use; return 0; } static unsigned int get_parameter_uint (struct para_data_s *para, enum para_name key) { struct para_data_s *r = get_parameter (para, key, 0); if (!r) return 0; if (r->key == pKEYUSAGE) return r->u.usage; return (unsigned int)strtoul (r->u.value, NULL, 10); } /* Read the certificate generation parameters from FP and generate (all) certificate requests. */ static int read_parameters (ctrl_t ctrl, FILE *fp, ksba_writer_t writer) { static struct { const char *name; enum para_name key; int allow_dups; } keywords[] = { { "Key-Type", pKEYTYPE}, { "Key-Length", pKEYLENGTH }, { "Key-Grip", pKEYGRIP }, { "Key-Usage", pKEYUSAGE }, { "Name-DN", pNAMEDN }, { "Name-Email", pNAMEEMAIL, 1 }, { "Name-DNS", pNAMEDNS, 1 }, { "Name-URI", pNAMEURI, 1 }, { NULL, 0 } }; char line[1024], *p; const char *err = NULL; struct para_data_s *para, *r; int i, rc = 0, any = 0; struct reqgen_ctrl_s outctrl; memset (&outctrl, 0, sizeof (outctrl)); outctrl.writer = writer; err = NULL; para = NULL; while (fgets (line, DIM(line)-1, fp) ) { char *keyword, *value; outctrl.lnr++; if (*line && line[strlen(line)-1] != '\n') { err = "line too long"; break; } for (p=line; spacep (p); p++) ; if (!*p || *p == '#') continue; keyword = p; if (*keyword == '%') { for (; *p && !spacep (p); p++) ; if (*p) *p++ = 0; for (; spacep (p); p++) ; value = p; trim_trailing_spaces (value); if (!ascii_strcasecmp (keyword, "%echo")) log_info ("%s\n", value); else if (!ascii_strcasecmp (keyword, "%dry-run")) outctrl.dryrun = 1; else if (!ascii_strcasecmp( keyword, "%commit")) { rc = proc_parameters (ctrl, para, &outctrl); if (rc) goto leave; any = 1; release_parameter_list (para); para = NULL; } else log_info ("skipping control `%s' (%s)\n", keyword, value); continue; } if (!(p = strchr (p, ':')) || p == keyword) { err = "missing colon"; break; } if (*p) *p++ = 0; for (; spacep (p); p++) ; if (!*p) { err = "missing argument"; break; } value = p; trim_trailing_spaces (value); for (i=0; (keywords[i].name && ascii_strcasecmp (keywords[i].name, keyword)); i++) ; if (!keywords[i].name) { err = "unknown keyword"; break; } if (keywords[i].key != pKEYTYPE && !para) { err = "parameter block does not start with \"Key-Type\""; break; } if (keywords[i].key == pKEYTYPE && para) { rc = proc_parameters (ctrl, para, &outctrl); if (rc) goto leave; any = 1; release_parameter_list (para); para = NULL; } else if (!keywords[i].allow_dups) { for (r = para; r && r->key != keywords[i].key; r = r->next) ; if (r) { err = "duplicate keyword"; break; } } r = xtrycalloc (1, sizeof *r + strlen( value )); if (!r) { err = "out of core"; break; } r->lnr = outctrl.lnr; r->key = keywords[i].key; strcpy (r->u.value, value); r->next = para; para = r; } if (err) { log_error ("line %d: %s\n", outctrl.lnr, err); rc = gpg_error (GPG_ERR_GENERAL); } else if (ferror(fp)) { log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) ); rc = gpg_error (GPG_ERR_GENERAL); } else if (para) { rc = proc_parameters (ctrl, para, &outctrl); if (rc) goto leave; any = 1; } if (!rc && !any) rc = gpg_error (GPG_ERR_NO_DATA); leave: release_parameter_list (para); return rc; } /* check whether there are invalid characters in the email address S */ static int has_invalid_email_chars (const char *s) { int at_seen=0; static char valid_chars[] = "01234567890_-." "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (; *s; s++) { if (*s & 0x80) return 1; if (*s == '@') at_seen++; else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+')) return 1; else if (at_seen && !strchr (valid_chars, *s)) return 1; } return at_seen != 1; } /* Check that all required parameters are given and perform the action */ static int proc_parameters (ctrl_t ctrl, struct para_data_s *para, struct reqgen_ctrl_s *outctrl) { struct para_data_s *r; const char *s; int i; unsigned int nbits; char numbuf[20]; unsigned char keyparms[100]; int rc; ksba_sexp_t public; int seq; /* check that we have all required parameters */ assert (get_parameter (para, pKEYTYPE, 0)); /* We can only use RSA for now. There is a with pkcs-10 on how to use ElGamal because it is expected that a PK algorithm can always be used for signing. */ i = get_parameter_algo (para, pKEYTYPE); if (i < 1 || i != GCRY_PK_RSA ) { r = get_parameter (para, pKEYTYPE, 0); log_error (_("line %d: invalid algorithm\n"), r->lnr); return gpg_error (GPG_ERR_INV_PARAMETER); } /* check the keylength */ if (!get_parameter (para, pKEYLENGTH, 0)) nbits = 1024; else nbits = get_parameter_uint (para, pKEYLENGTH); if (nbits < 1024 || nbits > 4096) { /* The BSI specs dated 2002-11-25 don't allow lengths below 1024. */ r = get_parameter (para, pKEYLENGTH, 0); log_error (_("line %d: invalid key length %u (valid are %d to %d)\n"), r->lnr, nbits, 1024, 4096); return gpg_error (GPG_ERR_INV_PARAMETER); } /* check the usage */ if (parse_parameter_usage (para, pKEYUSAGE)) return gpg_error (GPG_ERR_INV_PARAMETER); /* check that there is a subject name and that this DN fits our requirements */ if (!(s=get_parameter_value (para, pNAMEDN, 0))) { r = get_parameter (para, pKEYTYPE, 0); log_error (_("line %d: no subject name given\n"), r->lnr); return gpg_error (GPG_ERR_INV_PARAMETER); } /* fixme check s */ /* check that the optional email address is okay */ for (seq=0; (s=get_parameter_value (para, pNAMEEMAIL, seq)); seq++) { if (has_invalid_email_chars (s) || *s == '@' || s[strlen(s)-1] == '@' || s[strlen(s)-1] == '.' || strstr(s, "..")) { r = get_parameter (para, pNAMEEMAIL, seq); log_error (_("line %d: not a valid email address\n"), r->lnr); return gpg_error (GPG_ERR_INV_PARAMETER); } } s = get_parameter_value (para, pKEYGRIP, 0); if (s) /* Use existing key. */ { rc = gpgsm_agent_readkey (ctrl, s, &public); if (rc) { r = get_parameter (para, pKEYTYPE, 0); log_error (_("line %d: error getting key by keygrip `%s': %s\n"), r->lnr, s, gpg_strerror (rc)); return rc; } } else /* Generate new key. */ { sprintf (numbuf, "%u", nbits); snprintf ((char*)keyparms, DIM (keyparms)-1, "(6:genkey(3:rsa(5:nbits%d:%s)))", (int)strlen (numbuf), numbuf); rc = gpgsm_agent_genkey (ctrl, keyparms, &public); if (rc) { r = get_parameter (para, pKEYTYPE, 0); log_error (_("line %d: key generation failed: %s\n"), r->lnr, gpg_strerror (rc)); return rc; } } rc = create_request (ctrl, para, public, outctrl); xfree (public); return rc; } /* Parameters are checked, the key pair has been created. Now generate the request and write it out */ static int create_request (ctrl_t ctrl, struct para_data_s *para, ksba_const_sexp_t public, struct reqgen_ctrl_s *outctrl) { ksba_certreq_t cr; gpg_error_t err; gcry_md_hd_t md; ksba_stop_reason_t stopreason; int rc = 0; const char *s; unsigned int use; int seq; char *buf, *p; size_t len; char numbuf[30]; err = ksba_certreq_new (&cr); if (err) return err; rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_start_debug (md, "cr.cri"); ksba_certreq_set_hash_function (cr, HASH_FNC, md); ksba_certreq_set_writer (cr, outctrl->writer); err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN, 0)); if (err) { log_error ("error setting the subject's name: %s\n", gpg_strerror (err)); rc = err; goto leave; } for (seq=0; (s = get_parameter_value (para, pNAMEEMAIL, seq)); seq++) { buf = xtrymalloc (strlen (s) + 3); if (!buf) { rc = OUT_OF_CORE (errno); goto leave; } *buf = '<'; strcpy (buf+1, s); strcat (buf+1, ">"); err = ksba_certreq_add_subject (cr, buf); xfree (buf); if (err) { log_error ("error setting the subject's alternate name: %s\n", gpg_strerror (err)); rc = err; goto leave; } } for (seq=0; (s = get_parameter_value (para, pNAMEDNS, seq)); seq++) { len = strlen (s); assert (len); snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); buf = p = xtrymalloc (11 + strlen (numbuf) + len + 3); if (!buf) { rc = OUT_OF_CORE (errno); goto leave; } p = stpcpy (p, "(8:dns-name"); p = stpcpy (p, numbuf); p = stpcpy (p, s); strcpy (p, ")"); err = ksba_certreq_add_subject (cr, buf); xfree (buf); if (err) { log_error ("error setting the subject's alternate name: %s\n", gpg_strerror (err)); rc = err; goto leave; } } for (seq=0; (s = get_parameter_value (para, pNAMEURI, seq)); seq++) { len = strlen (s); assert (len); snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); buf = p = xtrymalloc (6 + strlen (numbuf) + len + 3); if (!buf) { rc = OUT_OF_CORE (errno); goto leave; } p = stpcpy (p, "(3:uri"); p = stpcpy (p, numbuf); p = stpcpy (p, s); strcpy (p, ")"); err = ksba_certreq_add_subject (cr, buf); xfree (buf); if (err) { log_error ("error setting the subject's alternate name: %s\n", gpg_strerror (err)); rc = err; goto leave; } } err = ksba_certreq_set_public_key (cr, public); if (err) { log_error ("error setting the public key: %s\n", gpg_strerror (err)); rc = err; goto leave; } use = get_parameter_uint (para, pKEYUSAGE); if (use == GCRY_PK_USAGE_SIGN) { /* For signing only we encode the bits: KSBA_KEYUSAGE_DIGITAL_SIGNATURE KSBA_KEYUSAGE_NON_REPUDIATION */ err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, "\x03\x02\x06\xC0", 4); } else if (use == GCRY_PK_USAGE_ENCR) { /* For encrypt only we encode the bits: KSBA_KEYUSAGE_KEY_ENCIPHERMENT KSBA_KEYUSAGE_DATA_ENCIPHERMENT */ err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, "\x03\x02\x04\x30", 4); } else err = 0; /* Both or none given: don't request one. */ if (err) { log_error ("error setting the key usage: %s\n", gpg_strerror (err)); rc = err; goto leave; } do { err = ksba_certreq_build (cr, &stopreason); if (err) { log_error ("ksba_certreq_build failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } if (stopreason == KSBA_SR_NEED_SIG) { gcry_sexp_t s_pkey; size_t n; unsigned char grip[20]; char hexgrip[41]; unsigned char *sigval; size_t siglen; n = gcry_sexp_canon_len (public, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); err = gpg_error (GPG_ERR_BUG); goto leave; } rc = gcry_sexp_sscan (&s_pkey, NULL, (const char*)public, n); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); goto leave; } if ( !gcry_pk_get_keygrip (s_pkey, grip) ) { rc = gpg_error (GPG_ERR_GENERAL); log_error ("can't figure out the keygrip\n"); gcry_sexp_release (s_pkey); goto leave; } gcry_sexp_release (s_pkey); for (n=0; n < 20; n++) sprintf (hexgrip+n*2, "%02X", grip[n]); rc = gpgsm_agent_pksign (ctrl, hexgrip, NULL, gcry_md_read(md, GCRY_MD_SHA1), gcry_md_get_algo_dlen (GCRY_MD_SHA1), GCRY_MD_SHA1, &sigval, &siglen); if (rc) { log_error ("signing failed: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_certreq_set_sig_val (cr, sigval); xfree (sigval); if (err) { log_error ("failed to store the sig_val: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } while (stopreason != KSBA_SR_READY); leave: gcry_md_close (md); ksba_certreq_release (cr); return rc; } /* Create a new key by reading the parameters from in_fd. Multiple keys may be created */ int gpgsm_genkey (ctrl_t ctrl, int in_fd, FILE *out_fp) { int rc; FILE *in_fp; Base64Context b64writer = NULL; ksba_writer_t writer; in_fp = fdopen (dup (in_fd), "rb"); if (!in_fp) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen() failed: %s\n", strerror (errno)); return tmperr; } ctrl->pem_name = "CERTIFICATE REQUEST"; rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } rc = read_parameters (ctrl, in_fp, writer); if (rc) { log_error ("error creating certificate request: %s\n", gpg_strerror (rc)); goto leave; } rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } gpgsm_status (ctrl, STATUS_KEY_CREATED, "P"); log_info ("certificate request created\n"); leave: gpgsm_destroy_writer (b64writer); fclose (in_fp); return rc; } diff --git a/sm/decrypt.c b/sm/decrypt.c index 9e5518b0f..70d48c983 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -1,512 +1,513 @@ /* decrypt.c - Decrypt a message * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" struct decrypt_filter_parm_s { int algo; int mode; int blklen; gcry_cipher_hd_t hd; char iv[16]; size_t ivlen; int any_data; /* dod we push anything through the filter at all? */ unsigned char lastblock[16]; /* to strip the padding we have to keep this one */ char helpblock[16]; /* needed because there is no block buffering in libgcrypt (yet) */ int helpblocklen; }; /* Decrypt the session key and fill in the parm structure. The algo and the IV is expected to be already in PARM. */ static int prepare_decryption (ctrl_t ctrl, const char *hexkeygrip, const char *desc, ksba_const_sexp_t enc_val, struct decrypt_filter_parm_s *parm) { char *seskey = NULL; size_t n, seskeylen; int rc; rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val, &seskey, &seskeylen); if (rc) { log_error ("error decrypting session key: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) log_printhex ("pkcs1 encoded session key:", seskey, seskeylen); n=0; if (seskeylen == 24) { /* Smells like a 3-des key. This might happen because a SC has already done the unpacking. */ } else { if (n + 7 > seskeylen ) { rc = gpg_error (GPG_ERR_INV_SESSION_KEY); goto leave; } /* FIXME: Actually the leading zero is required but due to the way we encode the output in libgcrypt as an MPI we are not able to encode that leading zero. However, when using a Smartcard we are doing it the right way and therefore we have to skip the zero. This should be fixed in gpg-agent of course. */ if (!seskey[n]) n++; if (seskey[n] != 2 ) /* Wrong block type version. */ { rc = gpg_error (GPG_ERR_INV_SESSION_KEY); goto leave; } for (n++; n < seskeylen && seskey[n]; n++) /* Skip the random bytes. */ ; n++; /* and the zero byte */ if (n >= seskeylen ) { rc = gpg_error (GPG_ERR_INV_SESSION_KEY); goto leave; } } if (DBG_CRYPTO) log_printhex ("session key:", seskey+n, seskeylen-n); rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0); if (rc) { log_error ("error creating decryptor: %s\n", gpg_strerror (rc)); goto leave; } rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n); if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) { log_info (_("WARNING: message was encrypted with " "a weak key in the symmetric cipher.\n")); rc = 0; } if (rc) { log_error("key setup failed: %s\n", gpg_strerror(rc) ); goto leave; } gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen); leave: xfree (seskey); return rc; } /* This function is called by the KSBA writer just before the actual write is done. The function must take INLEN bytes from INBUF, decrypt it and store it inoutbuf which has a maximum size of maxoutlen. The valid bytes in outbuf should be return in outlen. Due to different buffer sizes or different length of input and output, it may happen that fewer bytes are process or fewer bytes are written. */ static gpg_error_t decrypt_filter (void *arg, const void *inbuf, size_t inlen, size_t *inused, void *outbuf, size_t maxoutlen, size_t *outlen) { struct decrypt_filter_parm_s *parm = arg; int blklen = parm->blklen; size_t orig_inlen = inlen; /* fixme: Should we issue an error when we have not seen one full block? */ if (!inlen) return gpg_error (GPG_ERR_BUG); if (maxoutlen < 2*parm->blklen) return gpg_error (GPG_ERR_BUG); /* make some space becuase we will later need an extra block at the end */ maxoutlen -= blklen; if (parm->helpblocklen) { int i, j; for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++) parm->helpblock[i] = ((const char*)inbuf)[j]; inlen -= j; if (blklen > maxoutlen) return gpg_error (GPG_ERR_BUG); if (i < blklen) { parm->helpblocklen = i; *outlen = 0; } else { parm->helpblocklen = 0; if (parm->any_data) { memcpy (outbuf, parm->lastblock, blklen); *outlen =blklen; } else *outlen = 0; gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen, parm->helpblock, blklen); parm->any_data = 1; } *inused = orig_inlen - inlen; return 0; } if (inlen > maxoutlen) inlen = maxoutlen; if (inlen % blklen) { /* store the remainder away */ parm->helpblocklen = inlen%blklen; inlen = inlen/blklen*blklen; memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen); } *inused = inlen + parm->helpblocklen; if (inlen) { assert (inlen >= blklen); if (parm->any_data) { gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen, inbuf, inlen); memcpy (outbuf, parm->lastblock, blklen); memcpy (parm->lastblock,(char*)outbuf+inlen, blklen); *outlen = inlen; } else { gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen); *outlen = inlen - blklen; parm->any_data = 1; } } else *outlen = 0; return 0; } /* Perform a decrypt operation. */ int gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp) { int rc; Base64Context b64reader = NULL; Base64Context b64writer = NULL; ksba_reader_t reader; ksba_writer_t writer; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh; int recp; FILE *in_fp = NULL; struct decrypt_filter_parm_s dfparm; memset (&dfparm, 0, sizeof dfparm); kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } in_fp = fdopen ( dup (in_fd), "rb"); if (!in_fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gpgsm_create_reader (&b64reader, ctrl, in_fp, 0, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } rc = ksba_cms_new (&cms); if (rc) goto leave; rc = ksba_cms_set_reader_writer (cms, reader, writer); if (rc) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (rc)); goto leave; } /* parser loop */ do { rc = ksba_cms_parse (cms, &stopreason); if (rc) { log_debug ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA || stopreason == KSBA_SR_DETACHED_DATA) { int algo, mode; const char *algoid; int any_key = 0; algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/); algo = gcry_cipher_map_name (algoid); mode = gcry_cipher_mode_from_oid (algoid); if (!algo || !mode) { rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); log_error ("unsupported algorithm `%s'\n", algoid? algoid:"?"); if (algoid && !strcmp (algoid, "1.2.840.113549.3.2")) log_info (_("(this is the RC2 algorithm)\n")); else if (!algoid) log_info (_("(this does not seem to be an encrypted" " message)\n")); { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm", numbuf, algoid?algoid:"?", NULL); } /* If it seems that this is not an encrypted message we return a more sensible error code. */ if (!algoid) rc = gpg_error (GPG_ERR_NO_DATA); goto leave; } dfparm.algo = algo; dfparm.mode = mode; dfparm.blklen = gcry_cipher_get_algo_blklen (algo); if (dfparm.blklen > sizeof (dfparm.helpblock)) return gpg_error (GPG_ERR_BUG); rc = ksba_cms_get_content_enc_iv (cms, dfparm.iv, sizeof (dfparm.iv), &dfparm.ivlen); if (rc) { log_error ("error getting IV: %s\n", gpg_strerror (rc)); goto leave; } for (recp=0; !any_key; recp++) { char *issuer; ksba_sexp_t serial; ksba_sexp_t enc_val; char *hexkeygrip = NULL; char *desc = NULL; rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); if (rc == -1 && recp) break; /* no more recipients */ if (rc) log_error ("recp %d - error getting info: %s\n", recp, gpg_strerror (rc)); else { ksba_cert_t cert = NULL; log_debug ("recp %d - issuer: `%s'\n", recp, issuer? issuer:"[NONE]"); log_debug ("recp %d - serial: ", recp); gpgsm_dump_serial (serial); log_printf ("\n"); keydb_search_reset (kh); rc = keydb_search_issuer_sn (kh, issuer, serial); if (rc) { log_error ("failed to find the certificate: %s\n", gpg_strerror(rc)); goto oops; } rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("failed to get cert: %s\n", gpg_strerror (rc)); goto oops; } /* Just in case there is a problem with the own certificate we print this message - should never happen of course */ rc = gpgsm_cert_use_decrypt_p (cert); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage", numbuf, NULL); rc = 0; } hexkeygrip = gpgsm_get_keygrip_hexstring (cert); desc = gpgsm_format_keydesc (cert); oops: xfree (issuer); xfree (serial); ksba_cert_release (cert); } if (!hexkeygrip) ; else if (!(enc_val = ksba_cms_get_enc_val (cms, recp))) log_error ("recp %d - error getting encrypted session key\n", recp); else { rc = prepare_decryption (ctrl, hexkeygrip, desc, enc_val, &dfparm); xfree (enc_val); if (rc) { log_info ("decrypting session key failed: %s\n", gpg_strerror (rc)); } else { /* setup the bulk decrypter */ any_key = 1; ksba_writer_set_filter (writer, decrypt_filter, &dfparm); } } xfree (hexkeygrip); xfree (desc); } if (!any_key) { rc = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } } else if (stopreason == KSBA_SR_END_DATA) { ksba_writer_set_filter (writer, NULL, NULL); if (dfparm.any_data) { /* write the last block with padding removed */ int i, npadding = dfparm.lastblock[dfparm.blklen-1]; if (!npadding || npadding > dfparm.blklen) { log_error ("invalid padding with value %d\n", npadding); rc = gpg_error (GPG_ERR_INV_DATA); goto leave; } rc = ksba_writer_write (writer, dfparm.lastblock, dfparm.blklen - npadding); if (rc) goto leave; for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++) { if (dfparm.lastblock[i] != npadding) { log_error ("inconsistent padding\n"); rc = gpg_error (GPG_ERR_INV_DATA); goto leave; } } } } } while (stopreason != KSBA_SR_READY); rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL); leave: if (rc) { gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL); log_error ("message decryption failed: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); } ksba_cms_release (cms); gpgsm_destroy_reader (b64reader); gpgsm_destroy_writer (b64writer); keydb_release (kh); if (in_fp) fclose (in_fp); if (dfparm.hd) gcry_cipher_close (dfparm.hd); return rc; } diff --git a/sm/delete.c b/sm/delete.c index 7533f7291..0d2f1fd9d 100644 --- a/sm/delete.c +++ b/sm/delete.c @@ -1,172 +1,173 @@ /* delete.c * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" /* Delete a certificate or an secret key from a key database. */ static int delete_one (CTRL ctrl, const char *username) { int rc = 0; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; ksba_cert_t cert = NULL; int duplicates = 0; rc = keydb_classify_name (username, &desc); if (rc) { log_error (_("certificate `%s' not found: %s\n"), username, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL); goto leave; } kh = keydb_new (0); if (!kh) { log_error ("keydb_new failed\n"); goto leave; } rc = keydb_search (kh, &desc, 1); if (!rc) rc = keydb_get_cert (kh, &cert); if (!rc) { unsigned char fpr[20]; gpgsm_get_fingerprint (cert, 0, fpr, NULL); next_ambigious: rc = keydb_search (kh, &desc, 1); if (rc == -1) rc = 0; else if (!rc) { ksba_cert_t cert2 = NULL; unsigned char fpr2[20]; /* We ignore all duplicated certificates which might have been inserted due to program bugs. */ if (!keydb_get_cert (kh, &cert2)) { gpgsm_get_fingerprint (cert2, 0, fpr2, NULL); ksba_cert_release (cert2); if (!memcmp (fpr, fpr2, 20)) { duplicates++; goto next_ambigious; } } rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); } } if (rc) { if (rc == -1) rc = gpg_error (GPG_ERR_NO_PUBKEY); log_error (_("certificate `%s' not found: %s\n"), username, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "3", NULL); goto leave; } /* We need to search again to get back to the right position. */ rc = keydb_lock (kh); if (rc) { log_error (_("error locking keybox: %s\n"), gpg_strerror (rc)); goto leave; } do { keydb_search_reset (kh); rc = keydb_search (kh, &desc, 1); if (rc) { log_error ("problem re-searching certificate: %s\n", gpg_strerror (rc)); goto leave; } rc = keydb_delete (kh, duplicates ? 0 : 1); if (rc) goto leave; if (opt.verbose) { if (duplicates) log_info (_("duplicated certificate `%s' deleted\n"), username); else log_info (_("certificate `%s' deleted\n"), username); } } while (duplicates--); leave: keydb_release (kh); ksba_cert_release (cert); return rc; } /* Delete the certificates specified by NAMES. */ int gpgsm_delete (CTRL ctrl, STRLIST names) { int rc; if (!names) { log_error ("nothing to delete\n"); return gpg_error (GPG_ERR_NO_DATA); } for (; names; names=names->next ) { rc = delete_one (ctrl, names->d); if (rc) { log_error (_("deleting certificate \"%s\" failed: %s\n"), names->d, gpg_strerror (rc) ); return rc; } } return 0; } diff --git a/sm/encrypt.c b/sm/encrypt.c index e4c0d5437..07c2ba8ce 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -1,511 +1,512 @@ /* encrypt.c - Encrypt a message * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" struct dek_s { const char *algoid; int algo; gcry_cipher_hd_t chd; char key[32]; int keylen; char iv[32]; int ivlen; }; typedef struct dek_s *DEK; struct encrypt_cb_parm_s { FILE *fp; DEK dek; int eof_seen; int ready; int readerror; int bufsize; unsigned char *buffer; int buflen; }; /* Initialize the data encryption key (session key). */ static int init_dek (DEK dek) { int rc=0, mode, i; dek->algo = gcry_cipher_map_name (dek->algoid); mode = gcry_cipher_mode_from_oid (dek->algoid); if (!dek->algo || !mode) { log_error ("unsupported algorithm `%s'\n", dek->algoid); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } /* Extra check for algorithms we considere to be to weak for encryption, qlthough we suppor them fro decryption. Note that there is another check below discriminating on the key length. */ switch (dek->algo) { case GCRY_CIPHER_DES: case GCRY_CIPHER_RFC2268_40: log_error ("cipher algorithm `%s' not allowed: too weak\n", gcry_cipher_algo_name (dek->algo)); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); default: break; } dek->keylen = gcry_cipher_get_algo_keylen (dek->algo); if (!dek->keylen || dek->keylen > sizeof (dek->key)) return gpg_error (GPG_ERR_BUG); dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo); if (!dek->ivlen || dek->ivlen > sizeof (dek->iv)) return gpg_error (GPG_ERR_BUG); /* Make sure we don't use weak keys. */ if (dek->keylen < 100/8) { log_error ("key length of `%s' too small\n", dek->algoid); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE); if (rc) { log_error ("failed to create cipher context: %s\n", gpg_strerror (rc)); return rc; } for (i=0; i < 8; i++) { gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen); if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) break; log_info(_("weak key created - retrying\n") ); } if (rc) { log_error ("failed to set the key: %s\n", gpg_strerror (rc)); gcry_cipher_close (dek->chd); dek->chd = NULL; return rc; } gcry_create_nonce (dek->iv, dek->ivlen); rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen); if (rc) { log_error ("failed to set the IV: %s\n", gpg_strerror (rc)); gcry_cipher_close (dek->chd); dek->chd = NULL; return rc; } return 0; } static int encode_session_key (DEK dek, gcry_sexp_t * r_data) { gcry_sexp_t data; char * p, tmp[3]; int i; int rc; p = xmalloc (64 + 2 * dek->keylen); strcpy (p, "(data\n (flags pkcs1)\n (value #"); for (i=0; i < dek->keylen; i++) { sprintf (tmp, "%02x", (unsigned char) dek->key[i]); strcat (p, tmp); } strcat (p, "#))\n"); rc = gcry_sexp_sscan (&data, NULL, p, strlen (p)); xfree (p); *r_data = data; return rc; } /* Encrypt the DEK under the key contained in CERT and return it as a canonical S-Exp in encval. */ static int encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval) { gcry_sexp_t s_ciph, s_data, s_pkey; int rc; ksba_sexp_t buf; size_t len; *encval = NULL; /* get the key from the cert */ buf = ksba_cert_get_public_key (cert); if (!buf) { log_error ("no public key for recipient\n"); return gpg_error (GPG_ERR_NO_PUBKEY); } len = gcry_sexp_canon_len (buf, 0, NULL, NULL); if (!len) { log_error ("libksba did not return a proper S-Exp\n"); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)buf, len); xfree (buf); buf = NULL; if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return rc; } /* put the encoded cleartext into a simple list */ rc = encode_session_key (dek, &s_data); if (rc) { log_error ("encode_session_key failed: %s\n", gpg_strerror (rc)); return rc; } /* pass it to libgcrypt */ rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); gcry_sexp_release (s_data); gcry_sexp_release (s_pkey); /* reformat it */ len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { gpg_error_t tmperr = OUT_OF_CORE (errno); gcry_sexp_release (s_ciph); return tmperr; } len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, (char*)buf, len); assert (len); *encval = buf; return 0; } /* do the actual encryption */ static int encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct encrypt_cb_parm_s *parm = cb_value; int blklen = parm->dek->ivlen; unsigned char *p; size_t n; *nread = 0; if (!buffer) return -1; /* not supported */ if (parm->ready) return -1; if (count < blklen) BUG (); if (!parm->eof_seen) { /* fillup the buffer */ p = parm->buffer; for (n=parm->buflen; n < parm->bufsize; n++) { int c = getc (parm->fp); if (c == EOF) { if (ferror (parm->fp)) { parm->readerror = errno; return -1; } parm->eof_seen = 1; break; } p[n] = c; } parm->buflen = n; } n = parm->buflen < count? parm->buflen : count; n = n/blklen * blklen; if (n) { /* encrypt the stuff */ gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); *nread = n; /* Who cares about cycles, take the easy way and shift the buffer */ parm->buflen -= n; memmove (parm->buffer, parm->buffer+n, parm->buflen); } else if (parm->eof_seen) { /* no complete block but eof: add padding */ /* fixme: we should try to do this also in the above code path */ int i, npad = blklen - (parm->buflen % blklen); p = parm->buffer; for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++) p[n] = npad; gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); *nread = n; parm->ready = 1; } return 0; } /* Perform an encrypt operation. Encrypt the data received on DATA-FD and write it to OUT_FP. The recipients are take from the certificate given in recplist; if this is NULL it will be encrypted for a default recipient */ int gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) { int rc = 0; Base64Context b64writer = NULL; gpg_error_t err; ksba_writer_t writer; ksba_reader_t reader = NULL; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; struct encrypt_cb_parm_s encparm; DEK dek = NULL; int recpno; FILE *data_fp = NULL; CERTLIST cl; memset (&encparm, 0, sizeof encparm); /* Check that the certificate list is not empty and that at least one certificate is not flagged as encrypt_to; i.e. is a real recipient. */ for (cl = recplist; cl; cl = cl->next) if (!cl->is_encrypt_to) break; if (!cl) { log_error(_("no valid recipients given\n")); gpgsm_status (ctrl, STATUS_NO_RECP, "0"); rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } data_fp = fdopen ( dup (data_fd), "rb"); if (!data_fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } err = ksba_reader_new (&reader); if (err) rc = err; if (!rc) rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); if (rc) goto leave; encparm.fp = data_fp; ctrl->pem_name = "ENCRYPTED MESSAGE"; rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_cms_new (&cms); if (err) { rc = err; goto leave; } err = ksba_cms_set_reader_writer (cms, reader, writer); if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* We are going to create enveloped data with uninterpreted data as inner content */ err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA); if (!err) err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* Create a session key */ dek = xtrycalloc_secure (1, sizeof *dek); if (!dek) rc = OUT_OF_CORE (errno); else { dek->algoid = opt.def_cipher_algoid; rc = init_dek (dek); } if (rc) { log_error ("failed to create the session key: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen); if (err) { log_error ("ksba_cms_set_content_enc_algo failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } encparm.dek = dek; /* Use a ~8k (AES) or ~4k (3DES) buffer */ encparm.bufsize = 500 * dek->ivlen; encparm.buffer = xtrymalloc (encparm.bufsize); if (!encparm.buffer) { rc = OUT_OF_CORE (errno); goto leave; } /* Gather certificates of recipients, encrypt the session key for each and store them in the CMS object */ for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) { unsigned char *encval; rc = encrypt_dek (dek, cl->cert, &encval); if (rc) { log_error ("encryption failed for recipient no. %d: %s\n", recpno, gpg_strerror (rc)); goto leave; } err = ksba_cms_add_recipient (cms, cl->cert); if (err) { log_error ("ksba_cms_add_recipient failed: %s\n", gpg_strerror (err)); rc = err; xfree (encval); goto leave; } err = ksba_cms_set_enc_val (cms, recpno, encval); xfree (encval); if (err) { log_error ("ksba_cms_set_enc_val failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } /* Main control loop for encryption. */ recpno = 0; do { err = ksba_cms_build (cms, &stopreason); if (err) { log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } while (stopreason != KSBA_SR_READY); if (encparm.readerror) { log_error ("error reading input: %s\n", strerror (encparm.readerror)); rc = gpg_error (gpg_err_code_from_errno (encparm.readerror)); goto leave; } rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } log_info ("encrypted data created\n"); leave: ksba_cms_release (cms); gpgsm_destroy_writer (b64writer); ksba_reader_release (reader); keydb_release (kh); xfree (dek); if (data_fp) fclose (data_fp); xfree (encparm.buffer); return rc; } diff --git a/sm/export.c b/sm/export.c index f9d6dac62..b08a017d2 100644 --- a/sm/export.c +++ b/sm/export.c @@ -1,671 +1,672 @@ /* export.c * Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "exechelp.h" #include "i18n.h" /* A table to store a fingerprint as used in a duplicates table. We don't need to hash here because a fingerprint is alrady a perfect hash value. This we use the most significant bits to index the table and then use a linked list for the overflow. Possible enhancement for very large number of certictates: Add a second level table and then resort to a linked list. */ struct duptable_s { struct duptable_s *next; /* Note that we only need to store 19 bytes because the first byte is implictly given by the table index (we require at least 8 bits). */ unsigned char fpr[19]; }; typedef struct duptable_s *duptable_t; #define DUPTABLE_BITS 12 #define DUPTABLE_SIZE (1 << DUPTABLE_BITS) static void print_short_info (ksba_cert_t cert, FILE *fp); static gpg_error_t export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, const char *prompt, const char *keygrip, FILE **retfp); /* Create a table used to indetify duplicated certificates. */ static duptable_t * create_duptable (void) { return xtrycalloc (DUPTABLE_SIZE, sizeof (duptable_t)); } static void destroy_duptable (duptable_t *table) { int idx; duptable_t t, t2; if (table) { for (idx=0; idx < DUPTABLE_SIZE; idx++) for (t = table[idx]; t; t = t2) { t2 = t->next; xfree (t); } xfree (table); } } /* Insert the 20 byte fingerprint FPR into TABLE. Sets EXITS to true if the fingerprint already exists in the table. */ static gpg_error_t insert_duptable (duptable_t *table, unsigned char *fpr, int *exists) { size_t idx; duptable_t t; *exists = 0; idx = fpr[0]; #if DUPTABLE_BITS > 16 || DUPTABLE_BITS < 8 #error cannot handle a table larger than 16 bits or smaller than 8 bits #elif DUPTABLE_BITS > 8 idx <<= (DUPTABLE_BITS - 8); idx |= (fpr[1] & ~(~0 << 4)); #endif for (t = table[idx]; t; t = t->next) if (!memcmp (t->fpr, fpr+1, 19)) break; if (t) { *exists = 1; return 0; } /* Insert that fingerprint. */ t = xtrymalloc (sizeof *t); if (!t) return gpg_error_from_errno (errno); memcpy (t->fpr, fpr+1, 19); t->next = table[idx]; table[idx] = t; return 0; } /* Export all certificates or just those given in NAMES. */ void gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp) { KEYDB_HANDLE hd = NULL; KEYDB_SEARCH_DESC *desc = NULL; int ndesc; Base64Context b64writer = NULL; ksba_writer_t writer; STRLIST sl; ksba_cert_t cert = NULL; int rc=0; int count = 0; int i; duptable_t *dtable; dtable = create_duptable (); if (!dtable) { log_error ("creating duplicates table failed: %s\n", strerror (errno)); goto leave; } hd = keydb_new (0); if (!hd) { log_error ("keydb_new failed\n"); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { log_error ("allocating memory for export failed: %s\n", gpg_strerror (OUT_OF_CORE (errno))); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = keydb_classify_name (sl->d, desc+ndesc); if (rc) { log_error ("key `%s' not found: %s\n", sl->d, gpg_strerror (rc)); rc = 0; } else ndesc++; } } /* If all specifications are done by fingerprint, we switch to ephemeral mode so that _all_ currently available and matching certificates are exported. fixme: we should in this case keep a list of certificates to avoid accidential export of duplicate certificates. */ if (names && ndesc) { for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 || desc[i].mode == KEYDB_SEARCH_MODE_FPR16)); i++) ; if (i == ndesc) keydb_set_ephemeral (hd, 1); } while (!(rc = keydb_search (hd, desc, ndesc))) { unsigned char fpr[20]; int exists; if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); goto leave; } gpgsm_get_fingerprint (cert, 0, fpr, NULL); rc = insert_duptable (dtable, fpr, &exists); if (rc) { log_error ("inserting into duplicates table fauiled: %s\n", gpg_strerror (rc)); goto leave; } if (!exists && count && !ctrl->create_pem) { log_info ("exporting more than one certificate " "is not possible in binary mode\n"); log_info ("ignoring other certificates\n"); break; } if (!exists) { const unsigned char *image; size_t imagelen; image = ksba_cert_get_image (cert, &imagelen); if (!image) { log_error ("ksba_cert_get_image failed\n"); goto leave; } if (ctrl->create_pem) { if (count) putc ('\n', fp); print_short_info (cert, fp); putc ('\n', fp); } count++; if (!b64writer) { ctrl->pem_name = "CERTIFICATE"; rc = gpgsm_create_writer (&b64writer, ctrl, fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } } rc = ksba_writer_write (writer, image, imagelen); if (rc) { log_error ("write error: %s\n", gpg_strerror (rc)); goto leave; } if (ctrl->create_pem) { /* We want one certificate per PEM block */ rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } gpgsm_destroy_writer (b64writer); b64writer = NULL; } } ksba_cert_release (cert); cert = NULL; } if (rc && rc != -1) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); else if (b64writer) { rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } } leave: gpgsm_destroy_writer (b64writer); ksba_cert_release (cert); xfree (desc); keydb_release (hd); destroy_duptable (dtable); } /* Export a certificates and its private key. */ void gpgsm_p12_export (ctrl_t ctrl, const char *name, FILE *fp) { KEYDB_HANDLE hd; KEYDB_SEARCH_DESC *desc = NULL; Base64Context b64writer = NULL; ksba_writer_t writer; ksba_cert_t cert = NULL; int rc=0; const unsigned char *image; size_t imagelen; char *keygrip = NULL; char *prompt; char buffer[1024]; int nread; FILE *datafp = NULL; hd = keydb_new (0); if (!hd) { log_error ("keydb_new failed\n"); goto leave; } desc = xtrycalloc (1, sizeof *desc); if (!desc) { log_error ("allocating memory for export failed: %s\n", gpg_strerror (OUT_OF_CORE (errno))); goto leave; } rc = keydb_classify_name (name, desc); if (rc) { log_error ("key `%s' not found: %s\n", name, gpg_strerror (rc)); goto leave; } /* Lookup the certificate an make sure that it is unique. */ rc = keydb_search (hd, desc, 1); if (!rc) { rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); goto leave; } rc = keydb_search (hd, desc, 1); if (!rc) rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) rc = 0; if (rc) { log_error ("key `%s' not found: %s\n", name, gpg_strerror (rc)); goto leave; } } keygrip = gpgsm_get_keygrip_hexstring (cert); if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip)) { /* Note, that the !keygrip case indicates a bad certificate. */ rc = gpg_error (GPG_ERR_NO_SECKEY); log_error ("can't export key `%s': %s\n", name, gpg_strerror (rc)); goto leave; } image = ksba_cert_get_image (cert, &imagelen); if (!image) { log_error ("ksba_cert_get_image failed\n"); goto leave; } if (ctrl->create_pem) { print_short_info (cert, fp); putc ('\n', fp); } ctrl->pem_name = "PKCS12"; rc = gpgsm_create_writer (&b64writer, ctrl, fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } prompt = gpgsm_format_keydesc (cert); rc = export_p12 (ctrl, image, imagelen, prompt, keygrip, &datafp); xfree (prompt); if (rc) goto leave; rewind (datafp); while ( (nread = fread (buffer, 1, sizeof buffer, datafp)) > 0 ) if ((rc = ksba_writer_write (writer, buffer, nread))) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } if (ferror (datafp)) { rc = gpg_error_from_errno (rc); log_error ("error reading temporary file: %s\n", gpg_strerror (rc)); goto leave; } if (ctrl->create_pem) { /* We want one certificate per PEM block */ rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } gpgsm_destroy_writer (b64writer); b64writer = NULL; } ksba_cert_release (cert); cert = NULL; leave: if (datafp) fclose (datafp); gpgsm_destroy_writer (b64writer); ksba_cert_release (cert); xfree (desc); keydb_release (hd); } /* Print some info about the certifciate CERT to FP */ static void print_short_info (ksba_cert_t cert, FILE *fp) { char *p; ksba_sexp_t sexp; int idx; for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++) { fputs (!idx? "Issuer ...: " : "\n aka ...: ", fp); gpgsm_print_name (fp, p); xfree (p); } putc ('\n', fp); fputs ("Serial ...: ", fp); sexp = ksba_cert_get_serial (cert); if (sexp) { int len; const unsigned char *s = sexp; if (*s == '(') { s++; for (len=0; *s && *s != ':' && digitp (s); s++) len = len*10 + atoi_1 (s); if (*s == ':') for (s++; len; len--, s++) fprintf (fp, "%02X", *s); } xfree (sexp); } putc ('\n', fp); for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++) { fputs (!idx? "Subject ..: " : "\n aka ..: ", fp); gpgsm_print_name (fp, p); xfree (p); } putc ('\n', fp); } static gpg_error_t popen_protect_tool (const char *pgmname, FILE *infile, FILE *outfile, FILE **statusfile, const char *prompt, const char *keygrip, pid_t *pid) { const char *argv[20]; int i=0; argv[i++] = "--homedir"; argv[i++] = opt.homedir; argv[i++] = "--p12-export"; argv[i++] = "--have-cert"; argv[i++] = "--prompt"; argv[i++] = prompt?prompt:""; argv[i++] = "--enable-status-msg"; argv[i++] = "--", argv[i++] = keygrip, argv[i] = NULL; assert (i < sizeof argv); return gnupg_spawn_process (pgmname, argv, infile, outfile, setup_pinentry_env, statusfile, pid); } static gpg_error_t export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, const char *prompt, const char *keygrip, FILE **retfp) { const char *pgmname; gpg_error_t err = 0, child_err = 0; int c, cont_line; unsigned int pos; FILE *infp = NULL, *outfp = NULL, *fp = NULL; char buffer[1024]; pid_t pid = -1; int bad_pass = 0; if (!opt.protect_tool_program || !*opt.protect_tool_program) pgmname = GNUPG_DEFAULT_PROTECT_TOOL; else pgmname = opt.protect_tool_program; infp = tmpfile (); if (!infp) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary file: %s\n"), strerror (errno)); goto cleanup; } if (fwrite (certimg, certimglen, 1, infp) != 1) { err = gpg_error_from_errno (errno); log_error (_("error writing to temporary file: %s\n"), strerror (errno)); goto cleanup; } outfp = tmpfile (); if (!outfp) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary file: %s\n"), strerror (errno)); goto cleanup; } err = popen_protect_tool (pgmname, infp, outfp, &fp, prompt, keygrip, &pid); if (err) { pid = -1; goto cleanup; } fclose (infp); infp = NULL; /* Read stderr of the protect tool. */ pos = 0; cont_line = 0; while ((c=getc (fp)) != EOF) { /* fixme: We could here grep for status information of the protect tool to figure out better error codes for CHILD_ERR. */ buffer[pos++] = c; if (pos >= sizeof buffer - 5 || c == '\n') { buffer[pos - (c == '\n')] = 0; if (cont_line) log_printf ("%s", buffer); else { if (!strncmp (buffer, "gpg-protect-tool: [PROTECT-TOOL:] ",34)) { char *p, *pend; p = buffer + 34; pend = strchr (p, ' '); if (pend) *pend = 0; if ( !strcmp (p, "bad-passphrase")) bad_pass++; } else log_info ("%s", buffer); } pos = 0; cont_line = (c != '\n'); } } if (pos) { buffer[pos] = 0; if (cont_line) log_printf ("%s\n", buffer); else log_info ("%s\n", buffer); } else if (cont_line) log_printf ("\n"); /* If we found no error in the output of the child, setup a suitable error code, which will later be reset if the exit status of the child is 0. */ if (!child_err) child_err = gpg_error (GPG_ERR_DECRYPT_FAILED); cleanup: if (infp) fclose (infp); if (fp) fclose (fp); if (pid != -1) { if (!gnupg_wait_process (pgmname, pid)) child_err = 0; } if (!err) err = child_err; if (err) { if (outfp) fclose (outfp); } else *retfp = outfp; if (bad_pass) { /* During export this is the passphrase used to unprotect the key and not the pkcs#12 thing as in export. Therefore we can issue the regular passphrase status. FIXME: replace the all zero keyid by a regular one. */ gpgsm_status (ctrl, STATUS_BAD_PASSPHRASE, "0000000000000000"); } return err; } diff --git a/sm/fingerprint.c b/sm/fingerprint.c index 9441483bf..d6a3900f0 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -1,330 +1,331 @@ /* fingerprint.c - Get the fingerprint * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include /* Return the fingerprint of the certificate (we can't put this into libksba because we need libgcrypt support). The caller must provide an array of sufficient length or NULL so that the function allocates the array. If r_len is not NULL, the length of the digest is returned; well, this can also be done by using gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used. If there is a problem , the function does never return NULL but a digest of all 0xff. */ unsigned char * gpgsm_get_fingerprint (ksba_cert_t cert, int algo, unsigned char *array, int *r_len) { gcry_md_hd_t md; int rc, len; if (!algo) algo = GCRY_MD_SHA1; len = gcry_md_get_algo_dlen (algo); assert (len); if (!array) array = xmalloc (len); if (r_len) *r_len = len; rc = gcry_md_open (&md, algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); memset (array, 0xff, len); /* better return an invalid fpr than NULL */ return array; } rc = ksba_cert_hash (cert, 0, HASH_FNC, md); if (rc) { log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); memset (array, 0xff, len); /* better return an invalid fpr than NULL */ return array; } gcry_md_final (md); memcpy (array, gcry_md_read(md, algo), len ); return array; } /* Return an allocated buffer with the formatted fingerprint */ char * gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo) { unsigned char digest[MAX_DIGEST_LEN]; char *buf; int len, i; if (!algo) algo = GCRY_MD_SHA1; len = gcry_md_get_algo_dlen (algo); assert (len <= MAX_DIGEST_LEN ); gpgsm_get_fingerprint (cert, algo, digest, NULL); buf = xmalloc (len*3+1); *buf = 0; for (i=0; i < len; i++ ) sprintf (buf+strlen(buf), i? ":%02X":"%02X", digest[i]); return buf; } /* Return an allocated buffer with the formatted fingerprint as one large hexnumber */ char * gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo) { unsigned char digest[MAX_DIGEST_LEN]; char *buf; int len, i; if (!algo) algo = GCRY_MD_SHA1; len = gcry_md_get_algo_dlen (algo); assert (len <= MAX_DIGEST_LEN ); gpgsm_get_fingerprint (cert, algo, digest, NULL); buf = xmalloc (len*3+1); *buf = 0; for (i=0; i < len; i++ ) sprintf (buf+strlen(buf), "%02X", digest[i]); return buf; } /* Return a certificate ID. These are the last 4 bytes of the SHA-1 fingerprint. */ unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert) { unsigned char digest[20]; gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); return ((digest[16]<<24)|(digest[17]<<16)|(digest[18]<< 8)|digest[19]); } /* Return the so called KEYGRIP which is the SHA-1 hash of the public key parameters expressed as an canoncial encoded S-Exp. array must be 20 bytes long. returns the array or a newly allocated one if the passed one was NULL */ unsigned char * gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array) { gcry_sexp_t s_pkey; int rc; ksba_sexp_t p; size_t n; p = ksba_cert_get_public_key (cert); if (!p) return NULL; /* oops */ if (DBG_X509) log_debug ("get_keygrip for public key\n"); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); return NULL; } rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); xfree (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return NULL; } array = gcry_pk_get_keygrip (s_pkey, array); gcry_sexp_release (s_pkey); if (!array) { rc = gpg_error (GPG_ERR_GENERAL); log_error ("can't calculate keygrip\n"); return NULL; } if (DBG_X509) log_printhex ("keygrip=", array, 20); return array; } /* Return an allocated buffer with the keygrip of CERT in from of an hexstring. NULL is returned in case of error */ char * gpgsm_get_keygrip_hexstring (ksba_cert_t cert) { unsigned char grip[20]; char *buf, *p; int i; gpgsm_get_keygrip (cert, grip); buf = p = xmalloc (20*2+1); for (i=0; i < 20; i++, p += 2 ) sprintf (p, "%02X", grip[i]); return buf; } /* Return the PK algorithm used by CERT as well as the length in bits of the public key at NBITS. */ int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits) { gcry_sexp_t s_pkey; int rc; ksba_sexp_t p; size_t n; gcry_sexp_t l1, l2; const char *name; char namebuf[128]; if (nbits) *nbits = 0; p = ksba_cert_get_public_key (cert); if (!p) return 0; n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { xfree (p); return 0; } rc = gcry_sexp_sscan (&s_pkey, NULL, (char *)p, n); xfree (p); if (rc) return 0; if (nbits) *nbits = gcry_pk_get_nbits (s_pkey); /* Breaking the algorithm out of the S-exp is a bit of a challenge ... */ l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); if (!l1) { gcry_sexp_release (s_pkey); return 0; } l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); l1 = l2; name = gcry_sexp_nth_data (l1, 0, &n); if (name) { if (n > sizeof namebuf -1) n = sizeof namebuf -1; memcpy (namebuf, name, n); namebuf[n] = 0; } else *namebuf = 0; gcry_sexp_release (l1); gcry_sexp_release (s_pkey); return gcry_pk_map_name (namebuf); } /* For certain purposes we need a certificate id which has an upper limit of the size. We use the hash of the issuer name and the serial number for this. In most cases the serial number is not that large and the resulting string can be passed on an assuan command line. Everything is hexencoded with the serialnumber delimited from the hash by a dot. The caller must free the string. */ char * gpgsm_get_certid (ksba_cert_t cert) { ksba_sexp_t serial; char *p; char *endp; unsigned char hash[20]; unsigned long n; char *certid; int i; p = ksba_cert_get_issuer (cert, 0); if (!p) return NULL; /* Ooops: No issuer */ gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p)); xfree (p); serial = ksba_cert_get_serial (cert); if (!serial) return NULL; /* oops: no serial number */ p = (char *)serial; if (*p != '(') { log_error ("Ooops: invalid serial number\n"); xfree (serial); return NULL; } p++; n = strtoul (p, &endp, 10); p = endp; if (*p != ':') { log_error ("Ooops: invalid serial number (no colon)\n"); xfree (serial); return NULL; } p++; certid = xtrymalloc ( 40 + 1 + n*2 + 1); if (!certid) { xfree (serial); return NULL; /* out of core */ } for (i=0, endp = certid; i < 20; i++, endp += 2 ) sprintf (endp, "%02X", hash[i]); *endp++ = '.'; for (i=0; i < n; i++, endp += 2) sprintf (endp, "%02X", ((unsigned char*)p)[i]); *endp = 0; xfree (serial); return certid; } diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 7347bf575..5363b8ad6 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1,1773 +1,1781 @@ /* gpgsm.c - GnuPG for S/MIME - * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #ifdef USE_GNU_PTH # include #endif #include "gpgsm.h" #include #include /* malloc hooks */ #include "../kbx/keybox.h" /* malloc hooks */ #include "i18n.h" #include "keydb.h" #include "sysutils.h" #ifndef O_BINARY #define O_BINARY 0 #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', oInteractive = 'i', aListKeys = 'k', aListSecretKeys = 'K', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', aSign = 's', oTextmodeShort= 't', oUser = 'u', oVerbose = 'v', oCompress = 'z', oNotation = 'N', oBatch = 500, aClearsign, aStore, aKeygen, aSignEncr, aSignKey, aLSignKey, aListPackets, aEditKey, aDeleteKey, aImport, aVerify, aVerifyFiles, aListExternalKeys, aListSigs, aSendKeys, aRecvKeys, aExport, aExportSecretKeyP12, aCheckKeys, /* nyi */ aServer, aLearnCard, aCallDirmngr, aCallProtectTool, aPasswd, aGPGConfList, aDumpKeys, aDumpSecretKeys, aDumpExternalKeys, aKeydbClearSomeCertFlags, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugNone, oDebugWait, oDebugAllowCoreDump, oDebugNoChainValidation, oDebugIgnoreExpiration, oFixedPassphrase, oLogFile, oNoLogFile, oEnableSpecialFilenames, oAgentProgram, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oPreferSystemDirmngr, oDirmngrProgram, oProtectToolProgram, oFakedSystemTime, oAssumeArmor, oAssumeBase64, oAssumeBinary, oBase64, oNoArmor, oDisableCRLChecks, oEnableCRLChecks, oDisableTrustedCertCRLCheck, oEnableTrustedCertCRLCheck, oForceCRLRefresh, oDisableOCSP, oEnableOCSP, oIncludeCerts, oPolicyFile, oDisablePolicyChecks, oEnablePolicyChecks, oAutoIssuerKeyRetrieve, oTextmode, oFingerprint, oWithFingerprint, oWithMD5Fingerprint, oAnswerYes, oAnswerNo, oKeyring, oSecretKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oStatusFD, oNoComment, oNoVersion, oEmitVersion, oCompletesNeeded, oMarginalsNeeded, oMaxCertDepth, oLoadExtension, oRFC1991, oOpenPGP, oCipherAlgo, oDigestAlgo, oCompressAlgo, oCommandFD, oNoVerbose, oTrustDBName, oNoSecmemWarn, oNoDefKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oWithColons, oWithKeyData, oWithValidation, oWithEphemeralKeys, oSkipVerify, oCompressKeys, oCompressSigs, oAlwaysTrust, oRunAsShmCP, oSetFilename, oSetPolicyURL, oUseEmbeddedFilename, oComment, oDefaultComment, oThrowKeyid, oForceV3Sigs, oForceMDC, oS2KMode, oS2KDigest, oS2KCipher, oCharset, oNotDashEscaped, oEscapeFrom, oLockOnce, oLockMultiple, oLockNever, oKeyServer, oEncryptTo, oNoEncryptTo, oLoggerFD, oUtf8Strings, oNoUtf8Strings, oDisableCipherAlgo, oDisablePubkeyAlgo, oAllowNonSelfsignedUID, oAllowFreeformUID, oNoLiteral, oSetFilesize, oHonorHttpProxy, oFastListMode, oListOnly, oIgnoreTimeConflict, oNoRandomSeedFile, oNoAutoKeyRetrieve, oUseAgent, oMergeOnly, oTryAllSecrets, oTrustedKey, oEmuMDEncodeBug, aDummy }; static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aSign, "sign", 256, N_("|[FILE]|make a signature")}, { aClearsign, "clearsign", 256, N_("|[FILE]|make a clear text signature") }, { aDetachedSign, "detach-sign", 256, N_("make a detached signature")}, { aEncr, "encrypt", 256, N_("encrypt data")}, { aSym, "symmetric", 256, N_("encryption only with symmetric cipher")}, { aDecrypt, "decrypt", 256, N_("decrypt data (default)")}, { aVerify, "verify" , 256, N_("verify a signature")}, { aVerifyFiles, "verify-files" , 256, "@" }, { aListKeys, "list-keys", 256, N_("list keys")}, { aListExternalKeys, "list-external-keys", 256, N_("list external keys")}, { aListSecretKeys, "list-secret-keys", 256, N_("list secret keys")}, { aListSigs, "list-sigs", 256, N_("list certificate chain")}, { aListSigs, "check-sigs",256, "@"}, { oFingerprint, "fingerprint", 256, N_("list keys and fingerprints")}, { aKeygen, "gen-key", 256, N_("generate a new key pair")}, { aDeleteKey, "delete-key",256, N_("remove key from the public keyring")}, { aSendKeys, "send-keys" , 256, N_("export keys to a key server") }, { aRecvKeys, "recv-keys" , 256, N_("import keys from a key server") }, { aImport, "import", 256 , N_("import certificates")}, { aExport, "export", 256 , N_("export certificates")}, { aLearnCard, "learn-card", 256 ,N_("register a smartcard")}, { aServer, "server", 256, N_("run in server mode")}, { aCallDirmngr, "call-dirmngr", 256, N_("pass a command to the dirmngr")}, { aCallProtectTool, "call-protect-tool", 256, N_("invoke gpg-protect-tool")}, { aPasswd, "passwd", 256, N_("change a passphrase")}, { aGPGConfList, "gpgconf-list", 256, "@" }, { aDumpKeys, "dump-keys", 256, "@"}, { aDumpExternalKeys, "dump-external-keys", 256, "@"}, { aDumpSecretKeys, "dump-secret-keys", 256, "@"}, { aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", 256, "@"}, { 301, NULL, 0, N_("@\nOptions:\n ") }, { oArmor, "armor", 0, N_("create ascii armored output")}, { oArmor, "armour", 0, "@" }, { oBase64, "base64", 0, N_("create base-64 encoded output")}, { oAssumeArmor, "assume-armor", 0, N_("assume input is in PEM format")}, { oAssumeBase64, "assume-base64", 0, N_("assume input is in base-64 format")}, { oAssumeBinary, "assume-binary", 0, N_("assume input is in binary format")}, { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")}, { oPreferSystemDirmngr,"prefer-system-dirmngr", 0, N_("use system's dirmngr if available")}, { oDisableCRLChecks, "disable-crl-checks", 0, N_("never consult a CRL")}, { oEnableCRLChecks, "enable-crl-checks", 0, "@"}, { oDisableTrustedCertCRLCheck, "disable-trusted-cert-crl-check", 0, "@"}, { oEnableTrustedCertCRLCheck, "enable-trusted-cert-crl-check", 0, "@"}, { oForceCRLRefresh, "force-crl-refresh", 0, "@"}, { oDisableOCSP, "disable-ocsp", 0, "@" }, { oEnableOCSP, "enable-ocsp", 0, N_("check validity using OCSP")}, { oIncludeCerts, "include-certs", 1, N_("|N|number of certificates to include") }, { oPolicyFile, "policy-file", 2, N_("|FILE|take policy information from FILE") }, { oDisablePolicyChecks, "disable-policy-checks", 0, N_("do not check certificate policies")}, { oEnablePolicyChecks, "enable-policy-checks", 0, "@"}, { oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", 0, N_("fetch missing issuer certificates")}, #if 0 { oDefRecipient, "default-recipient" ,2, N_("|NAME|use NAME as default recipient")}, { oDefRecipientSelf, "default-recipient-self" ,0, N_("use the default key as default recipient")}, { oNoDefRecipient, "no-default-recipient", 0, "@" }, #endif { oEncryptTo, "encrypt-to", 2, "@" }, { oNoEncryptTo, "no-encrypt-to", 0, "@" }, { oUser, "local-user",2, N_("use this user-id to sign or decrypt")}, #if 0 { oCompress, NULL, 1, N_("|N|set compress level N (0 disables)") }, { oTextmodeShort, NULL, 0, "@"}, { oTextmode, "textmode", 0, N_("use canonical text mode")}, #endif { oOutput, "output", 2, N_("use as output file")}, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oNoTTY, "no-tty", 0, N_("don't use the terminal at all") }, { oLogFile, "log-file" ,2, N_("use a log file for the server")}, { oNoLogFile, "no-log-file" ,0, "@"}, #if 0 { oForceV3Sigs, "force-v3-sigs", 0, N_("force v3 signatures") }, { oForceMDC, "force-mdc", 0, N_("always use a MDC for encryption") }, #endif { oDryRun, "dry-run", 0, N_("do not make any changes") }, /*{ oInteractive, "interactive", 0, N_("prompt before overwriting") }, */ /*{ oUseAgent, "use-agent",0, N_("use the gpg-agent")},*/ { oBatch, "batch", 0, N_("batch mode: never ask")}, { oAnswerYes, "yes", 0, N_("assume yes on most questions")}, { oAnswerNo, "no", 0, N_("assume no on most questions")}, { oKeyring, "keyring" ,2, N_("add this keyring to the list of keyrings")}, { oSecretKeyring, "secret-keyring" ,2, N_("add this secret keyring to the list")}, { oDefaultKey, "default-key" ,2, N_("|NAME|use NAME as default secret key")}, { oKeyServer, "keyserver",2, N_("|HOST|use this keyserver to lookup keys")}, { oCharset, "charset" , 2, N_("|NAME|set terminal charset to NAME") }, { oOptions, "options" , 2, N_("read options from file")}, { oDebug, "debug" ,4|16, "@"}, { oDebugLevel, "debug-level" ,2, N_("|LEVEL|set the debugging level to LEVEL")}, { oDebugAll, "debug-all" ,0, "@"}, { oDebugNone, "debug-none" ,0, "@"}, { oDebugWait, "debug-wait" ,1, "@"}, { oDebugAllowCoreDump, "debug-allow-core-dump", 0, "@" }, { oDebugNoChainValidation, "debug-no-chain-validation", 0, "@"}, { oDebugIgnoreExpiration, "debug-ignore-expiration", 0, "@"}, { oFixedPassphrase, "fixed-passphrase", 2, "@"}, { oStatusFD, "status-fd" ,1, N_("|FD|write status info to this FD") }, { aDummy, "no-comment", 0, "@"}, { aDummy, "completes-needed", 1, "@"}, { aDummy, "marginals-needed", 1, "@"}, { oMaxCertDepth, "max-cert-depth", 1, "@" }, { aDummy, "trusted-key", 2, "@"}, { oLoadExtension, "load-extension" ,2, N_("|FILE|load extension module FILE")}, { aDummy, "rfc1991", 0, "@"}, { aDummy, "openpgp", 0, "@"}, { aDummy, "s2k-mode", 1, "@"}, { aDummy, "s2k-digest-algo",2, "@"}, { aDummy, "s2k-cipher-algo",2, "@"}, { oCipherAlgo, "cipher-algo", 2 , N_("|NAME|use cipher algorithm NAME")}, { oDigestAlgo, "digest-algo", 2 , N_("|NAME|use message digest algorithm NAME")}, #if 0 { oCompressAlgo, "compress-algo", 1 , N_("|N|use compress algorithm N")}, #endif { aDummy, "throw-keyid", 0, "@"}, { aDummy, "notation-data", 2, "@"}, { aExportSecretKeyP12, "export-secret-key-p12", 256, "@"}, { 302, NULL, 0, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )}, { 303, NULL, 0, N_("@\nExamples:\n\n" " -se -r Bob [file] sign and encrypt for user Bob\n" " --clearsign [file] make a clear text signature\n" " --detach-sign [file] make a detached signature\n" " --list-keys [names] show keys\n" " --fingerprint [names] show fingerprints\n" ) }, /* hidden options */ { oNoVerbose, "no-verbose", 0, "@"}, { oEnableSpecialFilenames, "enable-special-filenames", 0, "@" }, { oTrustDBName, "trustdb-name", 2, "@" }, { oNoSecmemWarn, "no-secmem-warning", 0, "@" }, { oNoArmor, "no-armor", 0, "@"}, { oNoArmor, "no-armour", 0, "@"}, { oNoDefKeyring, "no-default-keyring", 0, "@" }, { oNoGreeting, "no-greeting", 0, "@" }, { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */ { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ { oAgentProgram, "agent-program", 2 , "@" }, { oDisplay, "display", 2, "@" }, { oTTYname, "ttyname", 2, "@" }, { oTTYtype, "ttytype", 2, "@" }, { oLCctype, "lc-ctype", 2, "@" }, { oLCmessages, "lc-messages", 2, "@" }, { oDirmngrProgram, "dirmngr-program", 2 , "@" }, { oProtectToolProgram, "protect-tool-program", 2 , "@" }, { oFakedSystemTime, "faked-system-time", 4, "@" }, /* (epoch time) */ { oNoBatch, "no-batch", 0, "@" }, { oWithColons, "with-colons", 0, "@"}, { oWithKeyData,"with-key-data", 0, "@"}, { oWithValidation, "with-validation", 0, "@"}, { oWithMD5Fingerprint, "with-md5-fingerprint", 0, "@"}, { oWithEphemeralKeys, "with-ephemeral-keys", 0, "@"}, { aListKeys, "list-key", 0, "@" }, /* alias */ { aListSigs, "list-sig", 0, "@" }, /* alias */ { aListSigs, "check-sig",0, "@" }, /* alias */ { oSkipVerify, "skip-verify",0, "@" }, { oCompressKeys, "compress-keys",0, "@"}, { oCompressSigs, "compress-sigs",0, "@"}, { oAlwaysTrust, "always-trust", 0, "@"}, { oNoVersion, "no-version", 0, "@"}, { oLockOnce, "lock-once", 0, "@" }, { oLockMultiple, "lock-multiple", 0, "@" }, { oLockNever, "lock-never", 0, "@" }, { oLoggerFD, "logger-fd",1, "@" }, { oWithFingerprint, "with-fingerprint", 0, "@" }, { oDisableCipherAlgo, "disable-cipher-algo", 2, "@" }, { oDisablePubkeyAlgo, "disable-pubkey-algo", 2, "@" }, { oHonorHttpProxy,"honor-http-proxy", 0, "@" }, { oListOnly, "list-only", 0, "@"}, { oIgnoreTimeConflict, "ignore-time-conflict", 0, "@" }, { oNoRandomSeedFile, "no-random-seed-file", 0, "@" }, {0} }; int gpgsm_errors_seen = 0; /* It is possible that we are currentlu running under setuid permissions */ static int maybe_setuid = 1; /* Helper to implement --debug-level and --debug*/ static const char *debug_level; static unsigned int debug_value; /* Option --enable-special-filenames */ static int allow_special_filenames; +/* Default value for include-certs. */ +static int default_include_certs = 1; /* Only include the signer's cert. */ + + static char *build_list (const char *text, const char *(*mapf)(int), int (*chkf)(int)); static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void emergency_cleanup (void); static int check_special_filename (const char *fname); static int open_read (const char *filename); static FILE *open_fwrite (const char *filename); static void run_protect_tool (int argc, char **argv); static int our_pk_test_algo (int algo) { return 1; } static int our_cipher_test_algo (int algo) { return 1; } static int our_md_test_algo (int algo) { return 1; } static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers; const char *p; switch (level) { case 11: p = "gpgsm (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: gpgsm [options] [files] (-h for help)"); break; case 41: p = _("Syntax: gpgsm [options] [files]\n" "sign, check, encrypt or decrypt using the S/MIME protocol\n" "default operation depends on the input data\n"); break; case 31: p = "\nHome: "; break; case 32: p = opt.homedir; break; case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!ciphers) ciphers = build_list ("Cipher: ", gcry_cipher_algo_name, our_cipher_test_algo ); p = ciphers; break; case 35: if (!pubkeys) pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name, our_pk_test_algo ); p = pubkeys; break; case 36: if (!digests) digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo ); p = digests; break; default: p = NULL; break; } return p; } static char * build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int)) { int i; size_t n=strlen(text)+2; char *list, *p; if (maybe_setuid) { gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */ } for (i=1; i < 110; i++ ) if (!chkf(i)) n += strlen(mapf(i)) + 2; list = xmalloc (21 + n); *list = 0; for (p=NULL, i=1; i < 110; i++) { if (!chkf(i)) { if( !p ) p = stpcpy (list, text ); else p = stpcpy (p, ", "); p = stpcpy (p, mapf(i) ); } } if (p) p = stpcpy(p, "\n" ); return list; } /* Set the file pointer into binary mode if required. */ static void set_binary (FILE *fp) { #ifdef HAVE_DOSISH_SYSTEM setmode (fileno (fp), O_BINARY); #endif } static void i18n_init(void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file (PACKAGE_GT); #else # ifdef ENABLE_NLS setlocale (LC_ALL, "" ); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); # endif #endif } static void wrong_args (const char *text) { fputs (_("usage: gpgsm [options] "), stderr); fputs (text, stderr); putc ('\n', stderr); gpgsm_exit (2); } /* Setup the debugging. With a DEBUG_LEVEL of NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; and individual debugging flags will be added on top. */ static void set_debug (void) { if (!debug_level) ; else if (!strcmp (debug_level, "none")) opt.debug = 0; else if (!strcmp (debug_level, "basic")) opt.debug = DBG_ASSUAN_VALUE; else if (!strcmp (debug_level, "advanced")) opt.debug = DBG_ASSUAN_VALUE|DBG_X509_VALUE; else if (!strcmp (debug_level, "expert")) opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru")) opt.debug = ~0; else { log_error (_("invalid debug-level `%s' given\n"), debug_level); gpgsm_exit(2); } opt.debug |= debug_value; if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { enum cmd_and_opt_values cmd = *ret_cmd; if (!cmd || cmd == new_cmd) cmd = new_cmd; else if ( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if ( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if ( (cmd == aSign && new_cmd == aClearsign) || (cmd == aClearsign && new_cmd == aSign) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); gpgsm_exit(2); } *ret_cmd = cmd; } /* Helper to add recipients to a list. */ static void do_add_recipient (ctrl_t ctrl, const char *name, certlist_t *recplist, int is_encrypt_to) { int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to); if (rc) { log_error (_("can't encrypt to `%s': %s\n"), name, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_INV_RECP, gpg_err_code (rc) == -1? "1": gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1": gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2": gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3": gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4": gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5": gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6": gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7": gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8": "0", name, NULL); } } int main ( int argc, char **argv) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; const char *fname; /* char *username;*/ int may_coredump; STRLIST sl, remusr= NULL, locusr=NULL; STRLIST nrings=NULL; int detached_sig = 0; FILE *configfp = NULL; char *configname = NULL; unsigned configlineno; int parse_debug = 0; int no_more_options = 0; int default_config =1; int default_keyring = 1; char *logfile = NULL; int greeting = 0; int nogreeting = 0; int debug_wait = 0; int use_random_seed = 1; int with_fpr = 0; char *def_digest_string = NULL; enum cmd_and_opt_values cmd = 0; struct server_control_s ctrl; CERTLIST recplist = NULL; CERTLIST signerlist = NULL; int do_not_setup_keys = 0; /* trap_unaligned ();*/ set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* We don't need any locking in libgcrypt unless we use any kind of threading. */ gcry_control (GCRYCTL_DISABLE_INTERNAL_LOCKING); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to secmem_init() somewhere after the option parsing */ log_set_prefix ("gpgsm", 1); /* Try to auto set the character set. */ set_native_charset (NULL); /* Check that the libraries are suitable. Do it here because the option parse 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) ); } if (!ksba_check_version (NEED_KSBA_VERSION) ) { log_fatal( _("libksba is too old (need %s, have %s)\n"), NEED_KSBA_VERSION, ksba_check_version (NULL) ); } #ifdef HAVE_W32_SYSTEM /* For W32 we need pth. */ pth_init (); #endif gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); gnupg_init_signals (0, emergency_cleanup); create_dotlock (NULL); /* register locking cleanup */ i18n_init(); opt.def_cipher_algoid = "1.2.840.113549.3.7"; /*des-EDE3-CBC*/ opt.homedir = default_homedir (); #ifdef HAVE_W32_SYSTEM opt.no_crl_check = 1; #endif /* First 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 config 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; else if (pargs.r_opt == aCallProtectTool) break; /* This break makes sure that --version and --help are passed to the protect-tool. */ } /* initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); assuan_set_assuan_log_stream (log_get_stream ()); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); /* Setup a default control structure for command line mode */ memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); ctrl.no_server = 1; ctrl.status_fd = -1; /* not status output */ ctrl.autodetect_encoding = 1; /* Set the default option file */ if (default_config ) configname = make_filename (opt.homedir, "gpgsm.conf", NULL); /* Set the default policy file */ opt.policy_file = make_filename (opt.homedir, "policies.txt", 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)); gpgsm_exit(2); } xfree(configname); configname = NULL; } if (parse_debug && configname) log_info (_("reading options from `%s'\n"), configname); default_config = 0; } while (!no_more_options && optfile_parse (configfp, configname, &configlineno, &pargs, opts)) { switch (pargs.r_opt) { case aGPGConfList: set_cmd (&cmd, pargs.r_opt); do_not_setup_keys = 1; nogreeting = 1; break; case aServer: opt.batch = 1; set_cmd (&cmd, aServer); break; case aCallDirmngr: opt.batch = 1; set_cmd (&cmd, aCallDirmngr); do_not_setup_keys = 1; break; case aCallProtectTool: opt.batch = 1; set_cmd (&cmd, aCallProtectTool); no_more_options = 1; /* Stop parsing. */ do_not_setup_keys = 1; break; case aDeleteKey: set_cmd (&cmd, aDeleteKey); /*greeting=1;*/ do_not_setup_keys = 1; break; case aDetachedSign: detached_sig = 1; set_cmd (&cmd, aSign ); break; case aKeygen: set_cmd (&cmd, aKeygen); greeting=1; do_not_setup_keys = 1; break; case aCheckKeys: case aImport: case aSendKeys: case aRecvKeys: case aExport: case aExportSecretKeyP12: case aDumpKeys: case aDumpExternalKeys: case aDumpSecretKeys: case aListKeys: case aListExternalKeys: case aListSecretKeys: case aListSigs: case aLearnCard: case aPasswd: case aKeydbClearSomeCertFlags: do_not_setup_keys = 1; set_cmd (&cmd, pargs.r_opt); break; case aSym: case aDecrypt: case aEncr: case aSign: case aClearsign: case aVerify: set_cmd (&cmd, pargs.r_opt); break; /* output encoding selection */ case oArmor: ctrl.create_pem = 1; break; case oBase64: ctrl.create_pem = 0; ctrl.create_base64 = 1; break; case oNoArmor: ctrl.create_pem = 0; ctrl.create_base64 = 0; break; /* Input encoding selection */ case oAssumeArmor: ctrl.autodetect_encoding = 0; ctrl.is_pem = 1; ctrl.is_base64 = 0; break; case oAssumeBase64: ctrl.autodetect_encoding = 0; ctrl.is_pem = 0; ctrl.is_base64 = 1; break; case oAssumeBinary: ctrl.autodetect_encoding = 0; ctrl.is_pem = 0; ctrl.is_base64 = 0; break; case oDisableCRLChecks: opt.no_crl_check = 1; break; case oEnableCRLChecks: opt.no_crl_check = 0; break; case oDisableTrustedCertCRLCheck: opt.no_trusted_cert_crl_check = 1; break; case oEnableTrustedCertCRLCheck: opt.no_trusted_cert_crl_check = 0; break; case oForceCRLRefresh: opt.force_crl_refresh = 1; break; case oDisableOCSP: ctrl.use_ocsp = opt.enable_ocsp = 0; break; case oEnableOCSP: ctrl.use_ocsp = opt.enable_ocsp = 1; break; - case oIncludeCerts: ctrl.include_certs = pargs.r.ret_int; break; + case oIncludeCerts: + ctrl.include_certs = default_include_certs = pargs.r.ret_int; + break; case oPolicyFile: xfree (opt.policy_file); if (*pargs.r.ret_str) opt.policy_file = xstrdup (pargs.r.ret_str); else opt.policy_file = NULL; break; case oDisablePolicyChecks: opt.no_policy_check = 1; break; case oEnablePolicyChecks: opt.no_policy_check = 0; break; case oAutoIssuerKeyRetrieve: opt.auto_issuer_key_retrieve = 1; break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oNoTTY: /* fixme:tty_no_terminal(1);*/ break; case oDryRun: opt.dry_run = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oLogFile: logfile = pargs.r.ret_str; break; case oNoLogFile: logfile = NULL; break; case oBatch: opt.batch = 1; greeting = 0; break; case oNoBatch: opt.batch = 0; break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break; case oDebug: debug_value |= pargs.r.ret_ulong; break; case oDebugAll: debug_value = ~0; break; case oDebugNone: debug_value = 0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oDebugAllowCoreDump: may_coredump = enable_core_dumps (); break; case oDebugNoChainValidation: opt.no_chain_validation = 1; break; case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break; case oFixedPassphrase: opt.fixed_passphrase = pargs.r.ret_str; break; case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break; case oLoggerFD: log_set_fd (pargs.r.ret_int ); break; case oWithMD5Fingerprint: opt.with_md5_fingerprint=1; /*fall thru*/ case oWithFingerprint: with_fpr=1; /*fall thru*/ case oFingerprint: opt.fingerprint++; 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 oNoOptions: break; /* no-options */ case oHomedir: opt.homedir = pargs.r.ret_str; break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break; case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break; case oTTYtype: opt.ttytype = xstrdup (pargs.r.ret_str); break; case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oPreferSystemDirmngr: opt.prefer_system_dirmngr = 1; break; case oProtectToolProgram: opt.protect_tool_program = pargs.r.ret_str; break; case oFakedSystemTime: gnupg_set_time ( (time_t)pargs.r.ret_ulong, 0); break; case oNoDefKeyring: default_keyring = 0; break; case oNoGreeting: nogreeting = 1; break; case oDefaultKey: /* fixme:opt.def_secret_key = pargs.r.ret_str;*/ log_info ("WARNING: --default-key has not yet been implemented\n"); break; case oDefRecipient: if (*pargs.r.ret_str) opt.def_recipient = xstrdup (pargs.r.ret_str); break; case oDefRecipientSelf: xfree (opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree (opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; case oWithKeyData: opt.with_key_data=1; /* fall thru */ case oWithColons: ctrl.with_colons = 1; break; case oWithValidation: ctrl.with_validation=1; break; case oWithEphemeralKeys: opt.with_ephemeral_keys=1; break; case oSkipVerify: opt.skip_verify=1; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptTo: /* Store the recipient in the second list */ sl = add_to_strlist (&remusr, pargs.r.ret_str); sl->flags = 1; break; case oRecipient: /* store the recipient */ add_to_strlist ( &remusr, pargs.r.ret_str); break; case oTextmodeShort: /*fixme:opt.textmode = 2;*/ break; case oTextmode: /*fixme:opt.textmode=1;*/ break; case oUser: /* store the local users, the first one is the default */ if (!opt.local_user) opt.local_user = pargs.r.ret_str; add_to_strlist (&locusr, pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oCipherAlgo: opt.def_cipher_algoid = pargs.r.ret_str; break; case oDisableCipherAlgo: { int algo = gcry_cipher_map_name (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo ); } break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oEnableSpecialFilenames: allow_special_filenames =1; break; case aDummy: break; default: pargs.err = configfp? 1:2; break; } } if (configfp) { fclose (configfp); configfp = NULL; /* Keep a copy of the config filename. */ opt.config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (!opt.config_filename) opt.config_filename = make_filename (opt.homedir, "gpgsm.conf", NULL); if (log_get_errorcount(0)) gpgsm_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 if (!opt.batch) { log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n"); log_info ("It is only intended for test purposes and should NOT be\n"); log_info ("used in a production environment or with production keys!\n"); } # endif if (may_coredump && !opt.quiet) log_info (_("WARNING: program may create a core file!\n")); /* if (opt.qualsig_approval && !opt.quiet) */ /* log_info (_("This software has offically been approved to " */ /* "create and verify\n" */ /* "qualified signatures according to German law.\n")); */ if (logfile && cmd == aServer) { log_set_file (logfile); log_set_prefix (NULL, 1|2|4); } if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; log_info (_("WARNING: running with faked system time: ")); gnupg_get_isotime (tbuf); gpgsm_dump_time (tbuf); log_printf ("\n"); } /*FIXME if (opt.batch) */ /* tty_batchmode (1); */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); set_debug (); /* Although we alwasy use gpgsm_exit, we better install a regualr exit handler so that at least the secure memory gets wiped out. */ if (atexit (emergency_cleanup)) { log_error ("atexit failed\n"); gpgsm_exit (2); } /* Must do this after dropping setuid, because the mapping functions may try to load an module and we may have disabled an algorithm. */ if ( !gcry_cipher_map_name (opt.def_cipher_algoid) || !gcry_cipher_mode_from_oid (opt.def_cipher_algoid)) log_error (_("selected cipher algorithm is invalid\n")); if (def_digest_string) { opt.def_digest_algo = gcry_md_map_name (def_digest_string); xfree (def_digest_string); def_digest_string = NULL; if (our_md_test_algo(opt.def_digest_algo) ) log_error (_("selected digest algorithm is invalid\n")); } if (log_get_errorcount(0)) gpgsm_exit(2); /* Set the random seed file. */ if (use_random_seed) { char *p = make_filename (opt.homedir, "random_seed", NULL); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); xfree(p); } if (!cmd && opt.fingerprint && !with_fpr) set_cmd (&cmd, aListKeys); if (!nrings && default_keyring) /* add default keybox */ keydb_add_resource ("pubring.kbx", 0, 0); for (sl = nrings; sl; sl = sl->next) keydb_add_resource (sl->d, 0, 0); FREE_STRLIST(nrings); if (!do_not_setup_keys) { for (sl = locusr; sl ; sl = sl->next) { int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0); if (rc) { log_error (_("can't sign using `%s': %s\n"), sl->d, gpg_strerror (rc)); gpgsm_status2 (&ctrl, STATUS_INV_RECP, gpg_err_code (rc) == -1? "1": gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1": gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2": gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3": gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4": gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5": gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6": gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7": gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8": gpg_err_code (rc) == GPG_ERR_NO_SECKEY? "9": "0", sl->d, NULL); } } /* Build the recipient list. We first add the regular ones and then the encrypt-to ones because the underlying function will silenty ignore duplicates and we can't allow to keep a duplicate which is flagged as encrypt-to as the actually encrypt function would then complain about no (regular) recipients. */ for (sl = remusr; sl; sl = sl->next) if (!(sl->flags & 1)) do_add_recipient (&ctrl, sl->d, &recplist, 0); if (!opt.no_encrypt_to) { for (sl = remusr; sl; sl = sl->next) if ((sl->flags & 1)) do_add_recipient (&ctrl, sl->d, &recplist, 1); } } if (log_get_errorcount(0)) gpgsm_exit(1); /* must stop for invalid recipients */ fname = argc? *argv : NULL; switch (cmd) { case aGPGConfList: { /* List options and default values in the GPG Conf format. */ /* The following list is taken from gnupg/tools/gpgconf-comp.c. */ /* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ #define GC_OPT_FLAG_NONE 0UL /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* The DEFAULT flag for an option indicates that the option has a default value. */ #define GC_OPT_FLAG_DEFAULT (1UL << 4) /* The DEF_DESC flag for an option indicates that the option has a default, which is described by the value of the default field. */ #define GC_OPT_FLAG_DEF_DESC (1UL << 5) /* The NO_ARG_DESC flag for an option indicates that the argument has a default, which is described by the value of the ARGDEF field. */ #define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) printf ("gpgconf-gpgsm.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, opt.config_filename); printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE, GC_OPT_FLAG_NONE, GC_OPT_FLAG_DEFAULT, GC_OPT_FLAG_NONE ); printf ("disable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE ); printf ("disable-trusted-cert-crl-check:%lu:\n", GC_OPT_FLAG_NONE ); printf ("enable-ocsp:%lu:\n", GC_OPT_FLAG_NONE ); printf ("include-certs:%lu:1:\n", GC_OPT_FLAG_DEFAULT ); printf ("disable-policy-checks:%lu:\n", GC_OPT_FLAG_NONE ); printf ("auto-issuer-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE ); printf ("prefer-system-dirmngr:%lu:\n", GC_OPT_FLAG_NONE ); } break; case aServer: if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); sleep (debug_wait); log_debug ("... okay\n"); } gpgsm_server (recplist); break; case aCallDirmngr: if (!argc) wrong_args ("--call-dirmngr {args}"); else if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1)) gpgsm_exit (1); break; case aCallProtectTool: run_protect_tool (argc, argv); break; case aEncr: /* encrypt the given file */ set_binary (stdin); set_binary (stdout); if (!argc) gpgsm_encrypt (&ctrl, recplist, 0, stdout); /* from stdin */ else if (argc == 1) gpgsm_encrypt (&ctrl, recplist, open_read (*argv), stdout); /* from file */ else wrong_args ("--encrypt [datafile]"); break; case aSign: /* sign the given file */ /* FIXME: We don't handle --output yet. We should also allow to concatenate multiple files for signing because that is what gpg does.*/ set_binary (stdin); set_binary (stdout); if (!argc) gpgsm_sign (&ctrl, signerlist, 0, detached_sig, stdout); /* create from stdin */ else if (argc == 1) gpgsm_sign (&ctrl, signerlist, open_read (*argv), detached_sig, stdout); /* from file */ else wrong_args ("--sign [datafile]"); break; case aSignEncr: /* sign and encrypt the given file */ log_error ("this command has not yet been implemented\n"); break; case aClearsign: /* make a clearsig */ log_error ("this command has not yet been implemented\n"); break; case aVerify: { FILE *fp = NULL; set_binary (stdin); if (argc == 2 && opt.outfile) log_info ("option --output ignored for a detached signature\n"); else if (opt.outfile) fp = open_fwrite (opt.outfile); if (!argc) gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */ else if (argc == 1) gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */ else if (argc == 2) /* detached signature (sig, detached) */ gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL); else wrong_args ("--verify [signature [detached_data]]"); if (fp && fp != stdout) fclose (fp); } break; case aVerifyFiles: log_error (_("this command has not yet been implemented\n")); break; case aDecrypt: set_binary (stdin); set_binary (stdout); if (!argc) gpgsm_decrypt (&ctrl, 0, stdout); /* from stdin */ else if (argc == 1) gpgsm_decrypt (&ctrl, open_read (*argv), stdout); /* from file */ else wrong_args ("--decrypt [filename]"); break; case aDeleteKey: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_delete (&ctrl, sl); free_strlist(sl); break; case aListSigs: ctrl.with_chain = 1; case aListKeys: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<6))); free_strlist(sl); break; case aDumpKeys: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, stdout, (256 | (1<<6))); free_strlist(sl); break; case aListExternalKeys: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<7))); free_strlist(sl); break; case aDumpExternalKeys: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, stdout, (256 | (1<<7))); free_strlist(sl); break; case aListSecretKeys: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, stdout, (2 | (1<<6))); free_strlist(sl); break; case aDumpSecretKeys: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, stdout, (256 | 2 | (1<<6))); free_strlist(sl); break; case aKeygen: /* generate a key */ log_error ("this function is not yet available from the commandline\n"); break; case aImport: gpgsm_import_files (&ctrl, argc, argv, open_read); break; case aExport: set_binary (stdout); for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_export (&ctrl, sl, stdout); free_strlist(sl); break; case aExportSecretKeyP12: set_binary (stdout); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, stdout); else wrong_args ("--export-secret-key-p12 KEY-ID"); break; case aSendKeys: case aRecvKeys: log_error ("this command has not yet been implemented\n"); break; case aLearnCard: if (argc) wrong_args ("--learn-card"); else { int rc = gpgsm_agent_learn (&ctrl); if (rc) log_error ("error learning card: %s\n", gpg_strerror (rc)); } break; case aPasswd: if (argc != 1) wrong_args ("--passwd "); else { int rc; ksba_cert_t cert = NULL; char *grip = NULL; rc = gpgsm_find_cert (*argv, NULL, &cert); if (rc) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) rc = gpg_error (GPG_ERR_BUG); else { char *desc = gpgsm_format_keydesc (cert); rc = gpgsm_agent_passwd (&ctrl, grip, desc); xfree (desc); } if (rc) log_error ("error changing passphrase: %s\n", gpg_strerror (rc)); xfree (grip); ksba_cert_release (cert); } break; case aKeydbClearSomeCertFlags: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); keydb_clear_some_cert_flags (&ctrl, sl); free_strlist(sl); break; default: log_error ("invalid command (there is no implicit command)\n"); break; } /* cleanup */ gpgsm_release_certlist (recplist); gpgsm_release_certlist (signerlist); FREE_STRLIST(remusr); FREE_STRLIST(locusr); gpgsm_exit(0); return 8; /*NEVER REACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void gpgsm_exit (int rc) { gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); 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 ); emergency_cleanup (); rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; exit (rc); } void gpgsm_init_default_ctrl (struct server_control_s *ctrl) { - ctrl->include_certs = 1; /* only include the signer's cert */ + ctrl->include_certs = default_include_certs; ctrl->use_ocsp = opt.enable_ocsp; } /* Check whether the filename has the form "-&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 (allow_special_filenames && fname && *fname == '-' && fname[1] == '&' ) { int i; fname += 2; for (i=0; isdigit (fname[i]); i++ ) ; if ( !fname[i] ) return atoi (fname); } return -1; } /* Open the FILENAME for read and return the filedescriptor. Stop with an error message in case of problems. "-" denotes stdin and if special filenames are allowed the given fd is opened instead. */ static int open_read (const char *filename) { int fd; if (filename[0] == '-' && !filename[1]) { set_binary (stdin); return 0; /* stdin */ } fd = check_special_filename (filename); if (fd != -1) return fd; fd = open (filename, O_RDONLY | O_BINARY); if (fd == -1) { log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fd; } /* Open FILENAME for fwrite and return the stream. Stop with an error message in case of problems. "-" denotes stdout and if special filenames are allowed the given fd is opened instead. Caller must close the returned stream unless it is stdout. */ static FILE * open_fwrite (const char *filename) { int fd; FILE *fp; if (filename[0] == '-' && !filename[1]) { set_binary (stdout); return stdout; } fd = check_special_filename (filename); if (fd != -1) { fp = fdopen (dup (fd), "wb"); if (!fp) { log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); gpgsm_exit (2); } set_binary (fp); return fp; } fp = fopen (filename, "wb"); if (!fp) { log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fp; } static void run_protect_tool (int argc, char **argv) { #ifndef HAVE_W32_SYSTEM const char *pgm; char **av; int i; if (!opt.protect_tool_program || !*opt.protect_tool_program) pgm = GNUPG_DEFAULT_PROTECT_TOOL; else pgm = opt.protect_tool_program; av = xcalloc (argc+2, sizeof *av); av[0] = strrchr (pgm, '/'); if (!av[0]) av[0] = xstrdup (pgm); for (i=1; argc; i++, argc--, argv++) av[i] = *argv; av[i] = NULL; execv (pgm, av); log_error ("error executing `%s': %s\n", pgm, strerror (errno)); #endif /*HAVE_W32_SYSTEM*/ gpgsm_exit (2); } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 438252050..b49f34640 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -1,339 +1,340 @@ /* gpgsm.h - Global definitions for GpgSM * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GPGSM_H #define GPGSM_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM #include #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include "../common/util.h" #include "../common/errors.h" #define OUT_OF_CORE(a) (gpg_error (gpg_err_code_from_errno ((a)))) #define MAX_DIGEST_LEN 24 /* A large struct named "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 batch; /* run in batch mode, i.e w/o any user interaction */ int answer_yes; /* assume yes on most questions */ int answer_no; /* assume no on most questions */ int dry_run; /* don't change any persistent data */ const char *homedir; /* Configuration directory name */ const char *config_filename; /* Name of the used config file. */ const char *agent_program; char *display; char *ttyname; char *ttytype; char *lc_ctype; char *lc_messages; const char *dirmngr_program; int prefer_system_dirmngr; /* Prefer using a system wide drimngr. */ const char *protect_tool_program; char *outfile; /* name of output file */ int with_key_data;/* include raw key in the column delimted output */ int fingerprint; /* list fingerprints in all key listings */ int with_md5_fingerprint; /* Also print an MD5 fingerprint for standard key listings. */ int with_ephemeral_keys; /* Include ephemeral flagged keys in the keylisting. */ int armor; /* force base64 armoring (see also ctrl.with_base64) */ int no_armor; /* don't try to figure out whether data is base64 armored*/ const char *def_cipher_algoid; /* cipher algorithm to use if nothing else is specified */ int def_digest_algo; /* Ditto for hash algorithm */ int def_compress_algo; /* Ditto for compress algorithm */ char *def_recipient; /* userID of the default recipient */ int def_recipient_self; /* The default recipient is the default key */ int no_encrypt_to; /* Ignore all as encrypt to marked recipients. */ char *local_user; /* NULL or argument to -u */ int always_trust; /* Trust the given keys even if there is no valid certification chain */ int skip_verify; /* do not check signatures on data */ int lock_once; /* Keep lock once they are set */ int ignore_time_conflict; /* Ignore certain time conflicts */ int no_crl_check; /* Don't do a CRL check */ int no_trusted_cert_crl_check; /* Don't run a CRL check for trusted certs. */ int force_crl_refresh; /* Force refreshing the CRL. */ int enable_ocsp; /* Default to use OCSP checks. */ char *policy_file; /* full pathname of policy file */ int no_policy_check; /* ignore certificate policies */ int no_chain_validation; /* Bypass all cert chain validity tests */ int ignore_expiration; /* Ignore the notAfter validity checks. */ char *fixed_passphrase; /* Passphrase used by regression tests. */ int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */ int qualsig_approval; /* Set to true if this software has officially been approved to create an verify qualified signatures. This is a runtime option in case we want to check the integrity of the software at runtime. */ } opt; #define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */ #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 /* debug assuan communication */ #define DBG_X509 (opt.debug & DBG_X509_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; /* Note that the default values for this are set by gpgsm_init_default_ctrl() */ struct server_control_s { int no_server; /* We are not running under server control */ int status_fd; /* Only for non-server mode */ struct server_local_s *server_local; int with_colons; /* Use column delimited output format */ int with_chain; /* Include the certifying certs in a listing */ int with_validation;/* Validate each key while listing. */ int autodetect_encoding; /* Try to detect the input encoding */ int is_pem; /* Is in PEM format */ int is_base64; /* is in plain base-64 format */ int create_base64; /* Create base64 encoded output */ int create_pem; /* create PEM output */ const char *pem_name; /* PEM name to use */ int include_certs; /* -1 to send all certificates in the chain along with a signature or the number of certificates up the chain (0 = none, 1 = only signer) */ int use_ocsp; /* Set to true if OCSP should be used. */ }; typedef struct server_control_s *CTRL; typedef struct server_control_s *ctrl_t; /* data structure used in base64.c */ typedef struct base64_context_s *Base64Context; struct certlist_s { struct certlist_s *next; ksba_cert_t cert; int is_encrypt_to; /* True if the certificate has been set through the --encrypto-to option. */ }; typedef struct certlist_s *CERTLIST; typedef struct certlist_s *certlist_t; /*-- gpgsm.c --*/ void gpgsm_exit (int rc); void gpgsm_init_default_ctrl (struct server_control_s *ctrl); /*-- server.c --*/ void gpgsm_server (certlist_t default_recplist); gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text); gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...); gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec); /*-- fingerprint --*/ unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo, unsigned char *array, int *r_len); char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo); char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo); unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert); unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array); char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert); int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits); char *gpgsm_get_certid (ksba_cert_t cert); /*-- base64.c --*/ int gpgsm_create_reader (Base64Context *ctx, ctrl_t ctrl, FILE *fp, int allow_multi_pem, ksba_reader_t *r_reader); int gpgsm_reader_eof_seen (Base64Context ctx); void gpgsm_destroy_reader (Base64Context ctx); int gpgsm_create_writer (Base64Context *ctx, ctrl_t ctrl, FILE *fp, ksba_writer_t *r_writer); int gpgsm_finish_writer (Base64Context ctx); void gpgsm_destroy_writer (Base64Context ctx); /*-- certdump.c --*/ void gpgsm_print_serial (FILE *fp, ksba_const_sexp_t p); void gpgsm_print_time (FILE *fp, ksba_isotime_t t); void gpgsm_print_name2 (FILE *fp, const char *string, int translate); void gpgsm_print_name (FILE *fp, const char *string); void gpgsm_dump_cert (const char *text, ksba_cert_t cert); void gpgsm_dump_serial (ksba_const_sexp_t p); void gpgsm_dump_time (ksba_isotime_t t); void gpgsm_dump_string (const char *string); char *gpgsm_format_serial (ksba_const_sexp_t p); char *gpgsm_format_name2 (const char *name, int translate); char *gpgsm_format_name (const char *name); char *gpgsm_format_keydesc (ksba_cert_t cert); /*-- certcheck.c --*/ int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); int gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, gcry_md_hd_t md, int hash_algo); /* fixme: move create functions to another file */ int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, unsigned char **r_sigval); /*-- certchain.c --*/ int gpgsm_walk_cert_chain (ksba_cert_t start, ksba_cert_t *r_next); int gpgsm_is_root_cert (ksba_cert_t cert); int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, int listmode, FILE *listfp, unsigned int flags); int gpgsm_basic_cert_check (ksba_cert_t cert); /*-- certlist.c --*/ int gpgsm_cert_use_sign_p (ksba_cert_t cert); int gpgsm_cert_use_encrypt_p (ksba_cert_t cert); int gpgsm_cert_use_verify_p (ksba_cert_t cert); int gpgsm_cert_use_decrypt_p (ksba_cert_t cert); int gpgsm_cert_use_cert_p (ksba_cert_t cert); int gpgsm_cert_use_ocsp_p (ksba_cert_t cert); int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, certlist_t *listaddr, int is_encrypt_to); int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, certlist_t *listaddr, int is_encrypt_to); void gpgsm_release_certlist (certlist_t list); int gpgsm_find_cert (const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert); /*-- keylist.c --*/ gpg_error_t gpgsm_list_keys (ctrl_t ctrl, STRLIST names, FILE *fp, unsigned int mode); /*-- import.c --*/ int gpgsm_import (ctrl_t ctrl, int in_fd); int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, int (*of)(const char *fname)); /*-- export.c --*/ void gpgsm_export (ctrl_t ctrl, STRLIST names, FILE *fp); void gpgsm_p12_export (ctrl_t ctrl, const char *name, FILE *fp); /*-- delete.c --*/ int gpgsm_delete (ctrl_t ctrl, STRLIST names); /*-- verify.c --*/ int gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, FILE *out_fp); /*-- sign.c --*/ int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert); int gpgsm_sign (ctrl_t ctrl, CERTLIST signerlist, int data_fd, int detached, FILE *out_fp); /*-- encrypt.c --*/ int gpgsm_encrypt (ctrl_t ctrl, CERTLIST recplist, int in_fd, FILE *out_fp); /*-- decrypt.c --*/ int gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp); /*-- certreqgen.c --*/ int gpgsm_genkey (ctrl_t ctrl, int in_fd, FILE *out_fp); /*-- qualified.c --*/ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country); gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert); gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert); /*-- call-agent.c --*/ int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen); int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen); int gpgsm_agent_genkey (ctrl_t ctrl, ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey); int gpgsm_agent_readkey (ctrl_t ctrl, const char *hexkeygrip, ksba_sexp_t *r_pubkey); int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert); int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip); int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert); int gpgsm_agent_learn (ctrl_t ctrl); int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc); gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc); /*-- call-dirmngr.c --*/ int gpgsm_dirmngr_isvalid (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp); int gpgsm_dirmngr_lookup (ctrl_t ctrl, STRLIST names, void (*cb)(void*, ksba_cert_t), void *cb_value); int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, int argc, char **argv); /*-- misc.c --*/ void setup_pinentry_env (void); #endif /*GPGSM_H*/ diff --git a/sm/import.c b/sm/import.c index 6d00e91ea..b56014a1a 100644 --- a/sm/import.c +++ b/sm/import.c @@ -1,662 +1,663 @@ /* import.c - Import certificates * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "exechelp.h" #include "i18n.h" struct stats_s { unsigned long count; unsigned long imported; unsigned long unchanged; unsigned long not_imported; unsigned long secret_read; unsigned long secret_imported; unsigned long secret_dups; }; static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, FILE **retfp, struct stats_s *stats); static void print_imported_status (CTRL ctrl, ksba_cert_t cert, int new_cert) { char *fpr; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (new_cert) gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL); gpgsm_status2 (ctrl, STATUS_IMPORT_OK, new_cert? "1":"0", fpr, NULL); xfree (fpr); } /* Print an IMPORT_PROBLEM status. REASON is one of: 0 := "No specific reason given". 1 := "Invalid Certificate". 2 := "Issuer Certificate missing". 3 := "Certificate Chain too long". 4 := "Error storing certificate". */ static void print_import_problem (CTRL ctrl, ksba_cert_t cert, int reason) { char *fpr = NULL; char buf[25]; int i; sprintf (buf, "%d", reason); if (cert) { fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); /* detetect an error (all high) value */ for (i=0; fpr[i] == 'F'; i++) ; if (!fpr[i]) { xfree (fpr); fpr = NULL; } } gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL); xfree (fpr); } void print_imported_summary (CTRL ctrl, struct stats_s *stats) { char buf[14*25]; if (!opt.quiet) { log_info (_("total number processed: %lu\n"), stats->count); if (stats->imported) { log_info (_(" imported: %lu"), stats->imported ); log_printf ("\n"); } if (stats->unchanged) log_info (_(" unchanged: %lu\n"), stats->unchanged); if (stats->secret_read) log_info (_(" secret keys read: %lu\n"), stats->secret_read ); if (stats->secret_imported) log_info (_(" secret keys imported: %lu\n"), stats->secret_imported ); if (stats->secret_dups) log_info (_(" secret keys unchanged: %lu\n"), stats->secret_dups ); if (stats->not_imported) log_info (_(" not imported: %lu\n"), stats->not_imported); } sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", stats->count, 0l /*stats->no_user_id*/, stats->imported, 0l /*stats->imported_rsa*/, stats->unchanged, 0l /*stats->n_uids*/, 0l /*stats->n_subk*/, 0l /*stats->n_sigs*/, 0l /*stats->n_revoc*/, stats->secret_read, stats->secret_imported, stats->secret_dups, 0l /*stats->skipped_new_keys*/, stats->not_imported ); gpgsm_status (ctrl, STATUS_IMPORT_RES, buf); } static void check_and_store (CTRL ctrl, struct stats_s *stats, ksba_cert_t cert, int depth) { int rc; if (stats) stats->count++; if ( depth >= 50 ) { log_error (_("certificate chain too long\n")); if (stats) stats->not_imported++; print_import_problem (ctrl, cert, 3); return; } /* Some basic checks, but don't care about missing certificates; this is so that we are able to import entire certificate chains w/o requiring a special order (i.e. root-CA first). This used to be different but because gpgsm_verify even imports certificates without any checks, it doesn't matter much and the code gets much cleaner. A housekeeping function to remove certificates w/o an anchor would be nice, though. Optionally we do a full validation in addition to the basic test. */ rc = gpgsm_basic_cert_check (cert); if (!rc && ctrl->with_validation) rc = gpgsm_validate_chain (ctrl, cert, NULL, 0, NULL, 0); if (!rc || (!ctrl->with_validation && gpg_err_code (rc) == GPG_ERR_MISSING_CERT) ) { int existed; if (!keydb_store_cert (cert, 0, &existed)) { ksba_cert_t next = NULL; if (!existed) { print_imported_status (ctrl, cert, 1); if (stats) stats->imported++; } else { print_imported_status (ctrl, cert, 0); if (stats) stats->unchanged++; } if (opt.verbose > 1 && existed) { if (depth) log_info ("issuer certificate already in DB\n"); else log_info ("certificate already in DB\n"); } else if (opt.verbose && !existed) { if (depth) log_info ("issuer certificate imported\n"); else log_info ("certificate imported\n"); } /* Now lets walk up the chain and import all certificates up the chain. This is required in case we already stored parent certificates in the ephemeral keybox. Do not update the statistics, though. */ if (!gpgsm_walk_cert_chain (cert, &next)) { check_and_store (ctrl, NULL, next, depth+1); ksba_cert_release (next); } } else { log_error (_("error storing certificate\n")); if (stats) stats->not_imported++; print_import_problem (ctrl, cert, 4); } } else { log_error (_("basic certificate checks failed - not imported\n")); if (stats) stats->not_imported++; print_import_problem (ctrl, cert, gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 : gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0); } } static int import_one (CTRL ctrl, struct stats_s *stats, int in_fd) { int rc; Base64Context b64reader = NULL; ksba_reader_t reader; ksba_cert_t cert = NULL; ksba_cms_t cms = NULL; FILE *fp = NULL; ksba_content_type_t ct; int any = 0; fp = fdopen ( dup (in_fd), "rb"); if (!fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gpgsm_create_reader (&b64reader, ctrl, fp, 1, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } /* We need to loop here to handle multiple PEM objects in one file. */ do { ksba_cms_release (cms); cms = NULL; ksba_cert_release (cert); cert = NULL; ct = ksba_cms_identify (reader); if (ct == KSBA_CT_SIGNED_DATA) { /* This is probably a signed-only message - import the certs */ ksba_stop_reason_t stopreason; int i; rc = ksba_cms_new (&cms); if (rc) goto leave; rc = ksba_cms_set_reader_writer (cms, reader, NULL); if (rc) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (rc)); goto leave; } do { rc = ksba_cms_parse (cms, &stopreason); if (rc) { log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA) log_info ("not a certs-only message\n"); } while (stopreason != KSBA_SR_READY); for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) { check_and_store (ctrl, stats, cert, 0); ksba_cert_release (cert); cert = NULL; } if (!i) log_error ("no certificate found\n"); else any = 1; } else if (ct == KSBA_CT_PKCS12) { /* This seems to be a pkcs12 message. We use an external tool to parse the message and to store the private keys. We need to use a another reader here to parse the certificate we included in the p12 file; then we continue to look for other pkcs12 files (works only if they are in PEM format. */ FILE *certfp; Base64Context b64p12rdr; ksba_reader_t p12rdr; rc = parse_p12 (ctrl, reader, &certfp, stats); if (!rc) { any = 1; rewind (certfp); rc = gpgsm_create_reader (&b64p12rdr, ctrl, certfp, 1, &p12rdr); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); fclose (certfp); goto leave; } do { ksba_cert_release (cert); cert = NULL; rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_read_der (cert, p12rdr); if (!rc) check_and_store (ctrl, stats, cert, 0); } ksba_reader_clear (p12rdr, NULL, NULL); } while (!rc && !gpgsm_reader_eof_seen (b64p12rdr)); if (gpg_err_code (rc) == GPG_ERR_EOF) rc = 0; gpgsm_destroy_reader (b64p12rdr); fclose (certfp); if (rc) goto leave; } } else if (ct == KSBA_CT_NONE) { /* Failed to identify this message - assume a certificate */ rc = ksba_cert_new (&cert); if (rc) goto leave; rc = ksba_cert_read_der (cert, reader); if (rc) goto leave; check_and_store (ctrl, stats, cert, 0); any = 1; } else { log_error ("can't extract certificates from input\n"); rc = gpg_error (GPG_ERR_NO_DATA); } ksba_reader_clear (reader, NULL, NULL); } while (!gpgsm_reader_eof_seen (b64reader)); leave: if (any && gpg_err_code (rc) == GPG_ERR_EOF) rc = 0; ksba_cms_release (cms); ksba_cert_release (cert); gpgsm_destroy_reader (b64reader); if (fp) fclose (fp); return rc; } int gpgsm_import (CTRL ctrl, int in_fd) { int rc; struct stats_s stats; memset (&stats, 0, sizeof stats); rc = import_one (ctrl, &stats, in_fd); print_imported_summary (ctrl, &stats); /* If we never printed an error message do it now so that a command line invocation will return with an error (log_error keeps a global errorcount) */ if (rc && !log_get_errorcount (0)) log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); return rc; } int gpgsm_import_files (CTRL ctrl, int nfiles, char **files, int (*of)(const char *fname)) { int rc = 0; struct stats_s stats; memset (&stats, 0, sizeof stats); if (!nfiles) rc = import_one (ctrl, &stats, 0); else { for (; nfiles && !rc ; nfiles--, files++) { int fd = of (*files); rc = import_one (ctrl, &stats, fd); close (fd); if (rc == -1) rc = 0; } } print_imported_summary (ctrl, &stats); /* If we never printed an error message do it now so that a command line invocation will return with an error (log_error keeps a global errorcount) */ if (rc && !log_get_errorcount (0)) log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); return rc; } /* Fork and exec the protecttool, connect the file descriptor of INFILE to stdin, return a new stream in STATUSFILE, write the output to OUTFILE and the pid of the process in PID. Returns 0 on success or an error code. */ static gpg_error_t popen_protect_tool (const char *pgmname, FILE *infile, FILE *outfile, FILE **statusfile, pid_t *pid) { const char *argv[20]; int i=0; argv[i++] = "--homedir"; argv[i++] = opt.homedir; argv[i++] = "--p12-import"; argv[i++] = "--store"; argv[i++] = "--no-fail-on-exist"; argv[i++] = "--enable-status-msg"; if (opt.fixed_passphrase) { argv[i++] = "--passphrase"; argv[i++] = opt.fixed_passphrase; } argv[i++] = "--", argv[i] = NULL; assert (i < sizeof argv); return gnupg_spawn_process (pgmname, argv, infile, outfile, setup_pinentry_env, statusfile, pid); } /* Assume that the reader is at a pkcs#12 message and try to import certificates from that stupid format. We will also store secret keys. All of the pkcs#12 parsing and key storing is handled by the gpg-protect-tool, we merely have to take care of receiving the certificates. On success RETFP returns a temporary file with certificates. */ static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, FILE **retfp, struct stats_s *stats) { const char *pgmname; gpg_error_t err = 0, child_err = 0; int c, cont_line; unsigned int pos; FILE *tmpfp, *certfp = NULL, *fp = NULL; char buffer[1024]; size_t nread; pid_t pid = -1; int bad_pass = 0; if (!opt.protect_tool_program || !*opt.protect_tool_program) pgmname = GNUPG_DEFAULT_PROTECT_TOOL; else pgmname = opt.protect_tool_program; *retfp = NULL; /* To avoid an extra feeder process or doing selects and because gpg-protect-tool will anyway parse the entire pkcs#12 message in memory, we simply use tempfiles here and pass them to the gpg-protect-tool. */ tmpfp = tmpfile (); if (!tmpfp) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary file: %s\n"), strerror (errno)); goto cleanup; } while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread))) { if (nread && fwrite (buffer, nread, 1, tmpfp) != 1) { err = gpg_error_from_errno (errno); log_error (_("error writing to temporary file: %s\n"), strerror (errno)); goto cleanup; } } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; if (err) { log_error (_("error reading input: %s\n"), gpg_strerror (err)); goto cleanup; } certfp = tmpfile (); if (!certfp) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary file: %s\n"), strerror (errno)); goto cleanup; } err = popen_protect_tool (pgmname, tmpfp, certfp, &fp, &pid); if (err) { pid = -1; goto cleanup; } fclose (tmpfp); tmpfp = NULL; /* Read stderr of the protect tool. */ pos = 0; cont_line = 0; while ((c=getc (fp)) != EOF) { /* fixme: We could here grep for status information of the protect tool to figure out better error codes for CHILD_ERR. */ buffer[pos++] = c; if (pos >= sizeof buffer - 5 || c == '\n') { buffer[pos - (c == '\n')] = 0; if (cont_line) log_printf ("%s", buffer); else { if (!strncmp (buffer, "gpg-protect-tool: [PROTECT-TOOL:] ",34)) { char *p, *pend; p = buffer + 34; pend = strchr (p, ' '); if (pend) *pend = 0; if ( !strcmp (p, "secretkey-stored")) { stats->count++; stats->secret_read++; stats->secret_imported++; } else if ( !strcmp (p, "secretkey-exists")) { stats->count++; stats->secret_read++; stats->secret_dups++; } else if ( !strcmp (p, "bad-passphrase")) ; } else { log_info ("%s", buffer); if (!strncmp (buffer, "gpg-protect-tool: " "possibly bad passphrase given",46)) bad_pass++; } } pos = 0; cont_line = (c != '\n'); } } if (pos) { buffer[pos] = 0; if (cont_line) log_printf ("%s\n", buffer); else log_info ("%s\n", buffer); } /* If we found no error in the output of the cild, setup a suitable error code, which will later be reset if the exit status of the child is 0. */ if (!child_err) child_err = gpg_error (GPG_ERR_DECRYPT_FAILED); cleanup: if (tmpfp) fclose (tmpfp); if (fp) fclose (fp); if (pid != -1) { if (!gnupg_wait_process (pgmname, pid)) child_err = 0; } if (!err) err = child_err; if (err) { if (certfp) fclose (certfp); } else *retfp = certfp; if (bad_pass) { /* We only write a plain error code and not direct BAD_PASSPHRASE because the pkcs12 parser might issue this message multiple times, BAd_PASSPHRASE in general requires a keyID and parts of the import might actually succeed so that IMPORT_PROBLEM is also not appropriate. */ gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "import.parsep12", GPG_ERR_BAD_PASSPHRASE); } return err; } diff --git a/sm/keydb.c b/sm/keydb.c index d5932135d..81936cf6a 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -1,1508 +1,1509 @@ /* keydb.c - key database dispatcher * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "gpgsm.h" #include "../kbx/keybox.h" #include "keydb.h" #include "i18n.h" static int active_handles; typedef enum { KEYDB_RESOURCE_TYPE_NONE = 0, KEYDB_RESOURCE_TYPE_KEYBOX } KeydbResourceType; #define MAX_KEYDB_RESOURCES 20 struct resource_item { KeydbResourceType type; union { KEYBOX_HANDLE kr; } u; void *token; int secret; DOTLOCK lockhandle; }; static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; static int used_resources; struct keydb_handle { int locked; int found; int current; int is_ephemeral; int used; /* items in active */ struct resource_item active[MAX_KEYDB_RESOURCES]; }; static int lock_all (KEYDB_HANDLE hd); static void unlock_all (KEYDB_HANDLE hd); /* * Register a resource (which currently may only be a keybox file). * The first keybox which is added by this function is * created if it does not exist. * Note: this function may be called before secure memory is * available. */ int keydb_add_resource (const char *url, int force, int secret) { static int any_secret, any_public; const char *resname = url; char *filename = NULL; int rc = 0; FILE *fp; KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; const char *created_fname = NULL; /* Do we have an URL? gnupg-kbx:filename := this is a plain keybox filename := See what is is, but create as plain keybox. */ if (strlen (resname) > 10) { if (!strncmp (resname, "gnupg-kbx:", 10) ) { rt = KEYDB_RESOURCE_TYPE_KEYBOX; resname += 10; } #if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) else if (strchr (resname, ':')) { log_error ("invalid key resource URL `%s'\n", url ); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } #endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ } if (*resname != DIRSEP_C ) { /* do tilde expansion etc */ if (strchr(resname, DIRSEP_C) ) filename = make_filename (resname, NULL); else filename = make_filename (opt.homedir, resname, NULL); } else filename = xstrdup (resname); if (!force) force = secret? !any_secret : !any_public; /* see whether we can determine the filetype */ if (rt == KEYDB_RESOURCE_TYPE_NONE) { FILE *fp2 = fopen( filename, "rb" ); if (fp2) { u32 magic; /* FIXME: check for the keybox magic */ if (fread( &magic, 4, 1, fp2) == 1 ) { if (magic == 0x13579ace || magic == 0xce9a5713) ; /* GDBM magic - no more support */ else rt = KEYDB_RESOURCE_TYPE_KEYBOX; } else /* maybe empty: assume ring */ rt = KEYDB_RESOURCE_TYPE_KEYBOX; fclose (fp2); } else /* no file yet: create ring */ rt = KEYDB_RESOURCE_TYPE_KEYBOX; } switch (rt) { case KEYDB_RESOURCE_TYPE_NONE: log_error ("unknown type of key resource `%s'\n", url ); rc = gpg_error (GPG_ERR_GENERAL); goto leave; case KEYDB_RESOURCE_TYPE_KEYBOX: fp = fopen (filename, "rb"); if (!fp && !force) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } if (!fp) { /* no file */ #if 0 /* no autocreate of the homedirectory yet */ { char *last_slash_in_filename; last_slash_in_filename = strrchr (filename, DIRSEP_C); *last_slash_in_filename = 0; if (access (filename, F_OK)) { /* on the first time we try to create the default homedir and in this case the process will be terminated, so that on the next invocation can read the options file in on startup */ try_make_homedir (filename); rc = gpg_error (GPG_ERR_FILE_OPEN_ERROR); *last_slash_in_filename = DIRSEP_C; goto leave; } *last_slash_in_filename = DIRSEP_C; } #endif fp = fopen (filename, "w"); if (!fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error (_("error creating keybox `%s': %s\n"), filename, strerror(errno)); if (errno == ENOENT) log_info (_("you may want to start the gpg-agent first\n")); goto leave; } if (!opt.quiet) log_info (_("keybox `%s' created\n"), filename); created_fname = filename; } fclose (fp); fp = NULL; /* now register the file */ { void *token = keybox_register_file (filename, secret); if (!token) ; /* already registered - ignore it */ else if (used_resources >= MAX_KEYDB_RESOURCES) rc = gpg_error (GPG_ERR_RESOURCE_LIMIT); else { all_resources[used_resources].type = rt; all_resources[used_resources].u.kr = NULL; /* Not used here */ all_resources[used_resources].token = token; all_resources[used_resources].secret = secret; all_resources[used_resources].lockhandle = create_dotlock (filename); if (!all_resources[used_resources].lockhandle) log_fatal ( _("can't create lock for `%s'\n"), filename); /* Do a compress run if needed and the file is not locked. */ if (!make_dotlock (all_resources[used_resources].lockhandle, 0)) { KEYBOX_HANDLE kbxhd = keybox_new (token, secret); if (kbxhd) { keybox_compress (kbxhd); keybox_release (kbxhd); } release_dotlock (all_resources[used_resources].lockhandle); } used_resources++; } } break; default: log_error ("resource type of `%s' not supported\n", url); rc = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* fixme: check directory permissions and print a warning */ leave: if (rc) log_error ("keyblock resource `%s': %s\n", filename, gpg_strerror(rc)); else if (secret) any_secret = 1; else any_public = 1; xfree (filename); return rc; } KEYDB_HANDLE keydb_new (int secret) { KEYDB_HANDLE hd; int i, j; hd = xcalloc (1, sizeof *hd); hd->found = -1; assert (used_resources <= MAX_KEYDB_RESOURCES); for (i=j=0; i < used_resources; i++) { if (!all_resources[i].secret != !secret) continue; switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: hd->active[j].type = all_resources[i].type; hd->active[j].token = all_resources[i].token; hd->active[j].secret = all_resources[i].secret; hd->active[j].lockhandle = all_resources[i].lockhandle; hd->active[j].u.kr = keybox_new (all_resources[i].token, secret); if (!hd->active[j].u.kr) { xfree (hd); return NULL; /* fixme: release all previously allocated handles*/ } j++; break; } } hd->used = j; active_handles++; return hd; } void keydb_release (KEYDB_HANDLE hd) { int i; if (!hd) return; assert (active_handles > 0); active_handles--; unlock_all (hd); for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_release (hd->active[i].u.kr); break; } } xfree (hd); } /* Return the name of the current resource. This is function first looks for the last found found, then for the current search position, and last returns the first available resource. The returned string is only valid as long as the handle exists. This function does only return NULL if no handle is specified, in all other error cases an empty string is returned. */ const char * keydb_get_resource_name (KEYDB_HANDLE hd) { int idx; const char *s = NULL; if (!hd) return NULL; if ( hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if ( hd->current >= 0 && hd->current < hd->used) idx = hd->current; else idx = 0; switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: s = NULL; break; case KEYDB_RESOURCE_TYPE_KEYBOX: s = keybox_get_resource_name (hd->active[idx].u.kr); break; } return s? s: ""; } /* Switch the handle into ephemeral mode and return the orginal value. */ int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes) { int i; if (!hd) return 0; yes = !!yes; if (hd->is_ephemeral != yes) { for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_set_ephemeral (hd->active[i].u.kr, yes); break; } } } i = hd->is_ephemeral; hd->is_ephemeral = yes; return i; } /* If the keyring has not yet been locked, lock it now. This operation is required before any update opeations; it is optionaly for an insert operation. The lock is released with keydb_released. */ gpg_error_t keydb_lock (KEYDB_HANDLE hd) { if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); if (hd->locked) return 0; /* Already locked. */ return lock_all (hd); } static int lock_all (KEYDB_HANDLE hd) { int i, rc = 0; /* Fixme: This locking scheme may lead to deadlock if the resources are not added in the same order by all processes. We are currently only allowing one resource so it is not a problem. */ for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (hd->active[i].lockhandle) rc = make_dotlock (hd->active[i].lockhandle, -1); break; } if (rc) break; } if (rc) { /* revert the already set locks */ for (i--; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (hd->active[i].lockhandle) release_dotlock (hd->active[i].lockhandle); break; } } } else hd->locked = 1; /* make_dotlock () does not yet guarantee that errno is set, thus we can't rely on the error reason and will simply use EACCES. */ return rc? gpg_error (GPG_ERR_EACCES) : 0; } static void unlock_all (KEYDB_HANDLE hd) { int i; if (!hd->locked) return; for (i=hd->used-1; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (hd->active[i].lockhandle) release_dotlock (hd->active[i].lockhandle); break; } } hd->locked = 0; } #if 0 /* * Return the last found keybox. Caller must free it. * The returned keyblock has the kbode flag bit 0 set for the node with * the public key used to locate the keyblock or flag bit 1 set for * the user ID node. */ int keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) { int rc = 0; if (!hd) return G10ERR_INV_ARG; if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = G10ERR_GENERAL; /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_get_keyblock (hd->active[hd->found].u.kr, ret_kb); break; } return rc; } /* * update the current keyblock with KB */ int keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb) { int rc = 0; if (!hd) return G10ERR_INV_ARG; if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ if( opt.dry_run ) return 0; if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = G10ERR_GENERAL; /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_update_keyblock (hd->active[hd->found].u.kr, kb); break; } unlock_all (hd); return rc; } /* * Insert a new KB into one of the resources. */ int keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb) { int rc = -1; int idx; if (!hd) return G10ERR_INV_ARG; if( opt.dry_run ) return 0; if ( hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if ( hd->current >= 0 && hd->current < hd->used) idx = hd->current; else return G10ERR_GENERAL; rc = lock_all (hd); if (rc) return rc; switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = G10ERR_GENERAL; /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_insert_keyblock (hd->active[idx].u.kr, kb); break; } unlock_all (hd); return rc; } #endif /*disabled code*/ /* Return the last found object. Caller must free it. The returned keyblock has the kbode flag bit 0 set for the node with the public key used to locate the keyblock or flag bit 1 set for the user ID node. */ int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert) { int rc = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_get_cert (hd->active[hd->found].u.kr, r_cert); break; } return rc; } /* Return a flag of the last found object. WHICH is the flag requested; it should be one of the KEYBOX_FLAG_ values. If the operation is successful, the flag value will be stored at the address given by VALUE. Return 0 on success or an error code. */ gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value) { int err = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_NOTHING_FOUND); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value); break; } return err; } /* Set a flag of the last found object. WHICH is the flag to be set; it should be one of the KEYBOX_FLAG_ values. If the operation is successful, the flag value will be stored in the keybox. Note, that some flag values can't be updated and thus may return an error, some other flag values may be masked out before an update. Returns 0 on success or an error code. */ gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value) { int err = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_NOTHING_FOUND); if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value); break; } return err; } /* * Insert a new Certificate into one of the resources. */ int keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { int rc = -1; int idx; unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (opt.dry_run) return 0; if ( hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if ( hd->current >= 0 && hd->current < hd->used) idx = hd->current; else return gpg_error (GPG_ERR_GENERAL); if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest); break; } unlock_all (hd); return rc; } /* update the current keyblock with KB */ int keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { int rc = 0; unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ if (opt.dry_run) return 0; rc = lock_all (hd); if (rc) return rc; gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest); break; } unlock_all (hd); return rc; } /* * The current keyblock or cert will be deleted. */ int keydb_delete (KEYDB_HANDLE hd, int unlock) { int rc = -1; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return -1; /* nothing found */ if( opt.dry_run ) return 0; if (!hd->locked) return gpg_error (GPG_ERR_NOT_LOCKED); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: rc = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_delete (hd->active[hd->found].u.kr); break; } if (unlock) unlock_all (hd); return rc; } /* * Locate the default writable key resource, so that the next * operation (which is only relevant for inserts) will be done on this * resource. */ int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) { int rc; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); rc = keydb_search_reset (hd); /* this does reset hd->current */ if (rc) return rc; for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) { switch (hd->active[hd->current].type) { case KEYDB_RESOURCE_TYPE_NONE: BUG(); break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (keybox_is_writable (hd->active[hd->current].token)) return 0; /* found (hd->current is set to it) */ break; } } return -1; } /* * Rebuild the caches of all key resources. */ void keydb_rebuild_caches (void) { int i; for (i=0; i < used_resources; i++) { if (all_resources[i].secret) continue; switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: /* rc = keybox_rebuild_cache (all_resources[i].token); */ /* if (rc) */ /* log_error (_("failed to rebuild keybox cache: %s\n"), */ /* g10_errstr (rc)); */ break; } } } /* * Start the next search on this handle right at the beginning */ int keydb_search_reset (KEYDB_HANDLE hd) { int i, rc = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); hd->current = 0; hd->found = -1; /* and reset all resources */ for (i=0; !rc && i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_search_reset (hd->active[i].u.kr); break; } } return rc; /* fixme: we need to map error codes or share them with all modules*/ } /* * Search through all keydb resources, starting at the current position, * for a keyblock which contains one of the keys described in the DESC array. */ int keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) { int rc = -1; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); while (rc == -1 && hd->current >= 0 && hd->current < hd->used) { switch (hd->active[hd->current].type) { case KEYDB_RESOURCE_TYPE_NONE: BUG(); /* we should never see it here */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc); break; } if (rc == -1) /* EOF -> switch to next resource */ hd->current++; else if (!rc) hd->found = hd->current; } return rc; } int keydb_search_first (KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; return keydb_search (hd, &desc, 1); } int keydb_search_next (KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_NEXT; return keydb_search (hd, &desc, 1); } int keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_LONG_KID; /* desc.u.kid[0] = kid[0]; */ /* desc.u.kid[1] = kid[1]; */ return keydb_search (hd, &desc, 1); } int keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; memcpy (desc.u.fpr, fpr, 20); return keydb_search (hd, &desc, 1); } int keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer) { KEYDB_SEARCH_DESC desc; int rc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_ISSUER; desc.u.name = issuer; rc = keydb_search (hd, &desc, 1); return rc; } int keydb_search_issuer_sn (KEYDB_HANDLE hd, const char *issuer, ksba_const_sexp_t serial) { KEYDB_SEARCH_DESC desc; int rc; const unsigned char *s; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN; s = serial; if (*s !='(') return gpg_error (GPG_ERR_INV_VALUE); s++; for (desc.snlen = 0; digitp (s); s++) desc.snlen = 10*desc.snlen + atoi_1 (s); if (*s !=':') return gpg_error (GPG_ERR_INV_VALUE); desc.sn = s+1; desc.u.name = issuer; rc = keydb_search (hd, &desc, 1); return rc; } int keydb_search_subject (KEYDB_HANDLE hd, const char *name) { KEYDB_SEARCH_DESC desc; int rc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_SUBJECT; desc.u.name = name; rc = keydb_search (hd, &desc, 1); return rc; } static int classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int *force_exact ) { const char *s; int hexprefix = 0; int hexlength; int mode = 0; /* clear the structure so that the mode field is set to zero unless * we set it to the correct value right at the end of this function */ memset (desc, 0, sizeof *desc); *force_exact = 0; /* Skip leading spaces. Fixme: what about trailing white space? */ for(s = name; *s && spacep (s); s++ ) ; switch (*s) { case 0: /* empty string is an error */ return 0; case '.': /* an email address, compare from end */ mode = KEYDB_SEARCH_MODE_MAILEND; s++; desc->u.name = s; break; case '<': /* an email address */ mode = KEYDB_SEARCH_MODE_MAIL; s++; desc->u.name = s; break; case '@': /* part of an email address */ mode = KEYDB_SEARCH_MODE_MAILSUB; s++; desc->u.name = s; break; case '=': /* exact compare */ mode = KEYDB_SEARCH_MODE_EXACT; s++; desc->u.name = s; break; case '*': /* case insensitive substring search */ mode = KEYDB_SEARCH_MODE_SUBSTR; s++; desc->u.name = s; break; case '+': /* compare individual words */ mode = KEYDB_SEARCH_MODE_WORDS; s++; desc->u.name = s; break; case '/': /* subject's DN */ s++; if (!*s || spacep (s)) return 0; /* no DN or prefixed with a space */ desc->u.name = s; mode = KEYDB_SEARCH_MODE_SUBJECT; break; case '#': { const char *si; s++; if ( *s == '/') { /* "#/" indicates an issuer's DN */ s++; if (!*s || spacep (s)) return 0; /* no DN or prefixed with a space */ desc->u.name = s; mode = KEYDB_SEARCH_MODE_ISSUER; } else { /* serialnumber + optional issuer ID */ for (si=s; *si && *si != '/'; si++) { if (!strchr("01234567890abcdefABCDEF", *si)) return 0; /* invalid digit in serial number*/ } desc->sn = (const unsigned char*)s; desc->snlen = -1; if (!*si) mode = KEYDB_SEARCH_MODE_SN; else { s = si+1; if (!*s || spacep (s)) return 0; /* no DN or prefixed with a space */ desc->u.name = s; mode = KEYDB_SEARCH_MODE_ISSUER_SN; } } } break; case ':': /*Unified fingerprint */ { const char *se, *si; int i; se = strchr (++s,':'); if (!se) return 0; for (i=0,si=s; si < se; si++, i++ ) { if (!strchr("01234567890abcdefABCDEF", *si)) return 0; /* invalid digit */ } if (i != 32 && i != 40) return 0; /* invalid length of fpr*/ for (i=0,si=s; si < se; i++, si +=2) desc->u.fpr[i] = hextobyte(si); for (; i < 20; i++) desc->u.fpr[i]= 0; s = se + 1; mode = KEYDB_SEARCH_MODE_FPR; } break; default: if (s[0] == '0' && s[1] == 'x') { hexprefix = 1; s += 2; } hexlength = strspn(s, "0123456789abcdefABCDEF"); if (hexlength >= 8 && s[hexlength] =='!') { *force_exact = 1; hexlength++; /* just for the following check */ } /* check if a hexadecimal number is terminated by EOS or blank */ if (hexlength && s[hexlength] && !spacep (s+hexlength)) { if (hexprefix) /* a "0x" prefix without correct */ return 0; /* termination is an error */ /* The first chars looked like a hex number, but really is not */ hexlength = 0; } if (*force_exact) hexlength--; /* remove the bang */ if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0')) { /* short keyid */ unsigned long kid; if (hexlength == 9) s++; kid = strtoul( s, NULL, 16 ); desc->u.kid[4] = kid >> 24; desc->u.kid[5] = kid >> 16; desc->u.kid[6] = kid >> 8; desc->u.kid[7] = kid; mode = KEYDB_SEARCH_MODE_SHORT_KID; } else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0')) { /* complete keyid */ unsigned long kid0, kid1; char buf[9]; if (hexlength == 17) s++; mem2str(buf, s, 9 ); kid0 = strtoul (buf, NULL, 16); kid1 = strtoul (s+8, NULL, 16); desc->u.kid[0] = kid0 >> 24; desc->u.kid[1] = kid0 >> 16; desc->u.kid[2] = kid0 >> 8; desc->u.kid[3] = kid0; desc->u.kid[4] = kid1 >> 24; desc->u.kid[5] = kid1 >> 16; desc->u.kid[6] = kid1 >> 8; desc->u.kid[7] = kid1; mode = KEYDB_SEARCH_MODE_LONG_KID; } else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0')) { /* md5 fingerprint */ int i; if (hexlength == 33) s++; memset(desc->u.fpr+16, 0, 4); for (i=0; i < 16; i++, s+=2) { int c = hextobyte(s); if (c == -1) return 0; desc->u.fpr[i] = c; } mode = KEYDB_SEARCH_MODE_FPR16; } else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0')) { /* sha1/rmd160 fingerprint */ int i; if (hexlength == 41) s++; for (i=0; i < 20; i++, s+=2) { int c = hextobyte(s); if (c == -1) return 0; desc->u.fpr[i] = c; } mode = KEYDB_SEARCH_MODE_FPR20; } else if (!hexprefix) { /* The fingerprint in an X.509 listing is often delimited by colons, so we try to single this case out. */ mode = 0; hexlength = strspn (s, ":0123456789abcdefABCDEF"); if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) { int i; for (i=0; i < 20; i++, s += 3) { int c = hextobyte(s); if (c == -1 || (i < 19 && s[2] != ':')) break; desc->u.fpr[i] = c; } if (i == 20) mode = KEYDB_SEARCH_MODE_FPR20; } if (!mode) /* default is substring search */ { *force_exact = 0; desc->u.name = s; mode = KEYDB_SEARCH_MODE_SUBSTR; } } else { /* hex number with a prefix but a wrong length */ return 0; } } desc->mode = mode; return mode; } int keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc) { int dummy; KEYDB_SEARCH_DESC dummy_desc; if (!desc) desc = &dummy_desc; if (!classify_user_id (name, desc, &dummy)) return gpg_error (GPG_ERR_INV_NAME); return 0; } /* Store the certificate in the key DB but make sure that it does not already exists. We do this simply by comparing the fingerprint. If EXISTED is not NULL it will be set to true if the certificate was already in the DB. */ int keydb_store_cert (ksba_cert_t cert, int ephemeral, int *existed) { KEYDB_HANDLE kh; int rc; unsigned char fpr[20]; if (existed) *existed = 0; if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) { log_error (_("failed to get the fingerprint\n")); return gpg_error (GPG_ERR_GENERAL); } kh = keydb_new (0); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); return gpg_error (GPG_ERR_ENOMEM);; } if (ephemeral) keydb_set_ephemeral (kh, 1); rc = lock_all (kh); if (rc) return rc; rc = keydb_search_fpr (kh, fpr); if (rc != -1) { keydb_release (kh); if (!rc) { if (existed) *existed = 1; return 0; /* okay */ } log_error (_("problem looking for existing certificate: %s\n"), gpg_strerror (rc)); return rc; } rc = keydb_locate_writable (kh, 0); if (rc) { log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc)); keydb_release (kh); return rc; } rc = keydb_insert_cert (kh, cert); if (rc) { log_error (_("error storing certificate: %s\n"), gpg_strerror (rc)); keydb_release (kh); return rc; } keydb_release (kh); return 0; } /* This is basically keydb_set_flags but it implements a complete transaction by locating the certificate in the DB and updating the flags. */ gpg_error_t keydb_set_cert_flags (ksba_cert_t cert, int which, int idx, unsigned int value) { KEYDB_HANDLE kh; gpg_error_t err; unsigned char fpr[20]; unsigned int old_value; if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) { log_error (_("failed to get the fingerprint\n")); return gpg_error (GPG_ERR_GENERAL); } kh = keydb_new (0); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); return gpg_error (GPG_ERR_ENOMEM);; } err = keydb_lock (kh); if (err) { log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } err = keydb_search_fpr (kh, fpr); if (err) { log_error (_("problem re-searching certificate: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } err = keydb_get_flags (kh, which, idx, &old_value); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } if (value != old_value) { err = keydb_set_flags (kh, which, idx, value); if (err) { log_error (_("error storing flags: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } } keydb_release (kh); return 0; } /* Reset all the certificate flags we have stored with the certificates for performance reasons. */ void keydb_clear_some_cert_flags (ctrl_t ctrl, STRLIST names) { gpg_error_t err; KEYDB_HANDLE hd = NULL; KEYDB_SEARCH_DESC *desc = NULL; int ndesc; STRLIST sl; int rc=0; unsigned int old_value, value; hd = keydb_new (0); if (!hd) { log_error ("keydb_new failed\n"); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { log_error ("allocating memory failed: %s\n", gpg_strerror (OUT_OF_CORE (errno))); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = keydb_classify_name (sl->d, desc+ndesc); if (rc) { log_error ("key `%s' not found: %s\n", sl->d, gpg_strerror (rc)); rc = 0; } else ndesc++; } } err = keydb_lock (hd); if (err) { log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); goto leave; } while (!(rc = keydb_search (hd, desc, ndesc))) { if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); goto leave; } value = (old_value & ~VALIDITY_REVOKED); if (value != old_value) { err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value); if (err) { log_error (_("error storing flags: %s\n"), gpg_strerror (err)); goto leave; } } } if (rc && rc != -1) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); leave: xfree (desc); keydb_release (hd); } diff --git a/sm/keydb.h b/sm/keydb.h index fb4001b64..814ae9f1e 100644 --- a/sm/keydb.h +++ b/sm/keydb.h @@ -1,85 +1,86 @@ /* keydb.h - Key database * Copyright (C) 1998, 1999, 2000, 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_KEYDB_H #define GNUPG_KEYDB_H #include #include "../kbx/keybox-search-desc.h" typedef struct keydb_handle *KEYDB_HANDLE; /* Flag value used with KEYBOX_FLAG_VALIDITY. */ #define VALIDITY_REVOKED (1<<5) /*-- keydb.c --*/ int keydb_add_resource (const char *url, int force, int secret); KEYDB_HANDLE keydb_new (int secret); void keydb_release (KEYDB_HANDLE hd); int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes); const char *keydb_get_resource_name (KEYDB_HANDLE hd); gpg_error_t keydb_lock (KEYDB_HANDLE hd); #if 0 /* pgp stuff */ int keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb); int keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb); int keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb); #endif gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value); gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value); int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert); int keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert); int keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert); int keydb_delete (KEYDB_HANDLE hd, int unlock); int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved); void keydb_rebuild_caches (void); int keydb_search_reset (KEYDB_HANDLE hd); int keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc); int keydb_search_first (KEYDB_HANDLE hd); int keydb_search_next (KEYDB_HANDLE hd); int keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); int keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); int keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer); int keydb_search_issuer_sn (KEYDB_HANDLE hd, const char *issuer, const unsigned char *serial); int keydb_search_subject (KEYDB_HANDLE hd, const char *issuer); int keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc); int keydb_store_cert (ksba_cert_t cert, int ephemeral, int *existed); gpg_error_t keydb_set_cert_flags (ksba_cert_t cert, int which, int idx, unsigned int value); void keydb_clear_some_cert_flags (ctrl_t ctrl, STRLIST names); #endif /*GNUPG_KEYDB_H*/ diff --git a/sm/keylist.c b/sm/keylist.c index b744a157f..9baf065d0 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -1,1356 +1,1361 @@ /* keylist.c - Print certificates in various formats. * Copyright (C) 1998, 1999, 2000, 2001, 2003, * 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ #include "i18n.h" struct list_external_parm_s { ctrl_t ctrl; FILE *fp; int print_header; int with_colons; int with_chain; int raw_mode; }; /* This table is to map Extended Key Usage OIDs to human readable names. */ struct { const char *oid; const char *name; } key_purpose_map[] = { { "1.3.6.1.5.5.7.3.1", "serverAuth" }, { "1.3.6.1.5.5.7.3.2", "clientAuth" }, { "1.3.6.1.5.5.7.3.3", "codeSigning" }, { "1.3.6.1.5.5.7.3.4", "emailProtection" }, { "1.3.6.1.5.5.7.3.5", "ipsecEndSystem" }, { "1.3.6.1.5.5.7.3.6", "ipsecTunnel" }, { "1.3.6.1.5.5.7.3.7", "ipsecUser" }, { "1.3.6.1.5.5.7.3.8", "timeStamping" }, { "1.3.6.1.5.5.7.3.9", "ocspSigning" }, { "1.3.6.1.5.5.7.3.10", "dvcs" }, { "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" }, { "1.3.6.1.5.5.7.3.13", "eapOverPPP" }, { "1.3.6.1.5.5.7.3.14", "wlanSSID" }, { "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */ { "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */ { NULL, NULL } }; /* A table mapping OIDs to a descriptive string. */ static struct { char *oid; char *name; unsigned int flag; } oidtranstbl[] = { /* Algorithms. */ { "1.2.840.10040.4.1", "dsa" }, { "1.2.840.10040.4.3", "dsaWithSha1" }, { "1.2.840.113549.1.1.1", "rsaEncryption" }, { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, { "1.2.840.113549.1.1.3", "md4WithRSAEncryption" }, { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" }, { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" }, { "1.2.840.113549.1.1.7", "rsaOAEP" }, { "1.2.840.113549.1.1.8", "rsaOAEP-MGF" }, { "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" }, { "1.2.840.113549.1.1.10", "rsaPSS" }, { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" }, { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" }, { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" }, { "1.3.14.3.2.26", "sha1" }, { "1.3.14.3.2.29", "sha-1WithRSAEncryption" }, { "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" }, /* Telesec extensions. */ { "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" }, { "0.2.262.1.10.12.1", "telesecCertIdExt" }, { "0.2.262.1.10.12.2", "telesecPolicyIdentifier" }, { "0.2.262.1.10.12.3", "telesecPolicyQualifierID" }, { "0.2.262.1.10.12.4", "telesecCRLFilteredExt" }, { "0.2.262.1.10.12.5", "telesecCRLFilterExt"}, { "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" }, /* PKIX private extensions. */ { "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" }, { "1.3.6.1.5.5.7.1.2", "biometricInfo" }, { "1.3.6.1.5.5.7.1.3", "qcStatements" }, { "1.3.6.1.5.5.7.1.4", "acAuditIdentity" }, { "1.3.6.1.5.5.7.1.5", "acTargeting" }, { "1.3.6.1.5.5.7.1.6", "acAaControls" }, { "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" }, { "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" }, { "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" }, { "1.3.6.1.5.5.7.1.10", "acProxying" }, { "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" }, /* X.509 id-ce */ { "2.5.29.14", "subjectKeyIdentifier", 1}, { "2.5.29.15", "keyUsage", 1 }, { "2.5.29.16", "privateKeyUsagePeriod" }, { "2.5.29.17", "subjectAltName", 1 }, { "2.5.29.18", "issuerAltName", 1 }, { "2.5.29.19", "basicConstraints", 1}, { "2.5.29.20", "cRLNumber" }, { "2.5.29.21", "cRLReason" }, { "2.5.29.22", "expirationDate" }, { "2.5.29.23", "instructionCode" }, { "2.5.29.24", "invalidityDate" }, { "2.5.29.27", "deltaCRLIndicator" }, { "2.5.29.28", "issuingDistributionPoint" }, { "2.5.29.29", "certificateIssuer" }, { "2.5.29.30", "nameConstraints" }, { "2.5.29.31", "cRLDistributionPoints", 1 }, { "2.5.29.32", "certificatePolicies", 1 }, { "2.5.29.32.0", "anyPolicy" }, { "2.5.29.33", "policyMappings" }, { "2.5.29.35", "authorityKeyIdentifier", 1 }, { "2.5.29.36", "policyConstraints" }, { "2.5.29.37", "extKeyUsage", 1 }, { "2.5.29.46", "freshestCRL" }, { "2.5.29.54", "inhibitAnyPolicy" }, /* Netscape certificate extensions. */ { "2.16.840.1.113730.1.1", "netscape-cert-type" }, { "2.16.840.1.113730.1.2", "netscape-base-url" }, { "2.16.840.1.113730.1.3", "netscape-revocation-url" }, { "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" }, { "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" }, { "2.16.840.1.113730.1.8", "netscape-ca-policy-url" }, { "2.16.840.1.113730.1.9", "netscape-homePage-url" }, { "2.16.840.1.113730.1.10", "netscape-entitylogo" }, { "2.16.840.1.113730.1.11", "netscape-userPicture" }, { "2.16.840.1.113730.1.12", "netscape-ssl-server-name" }, { "2.16.840.1.113730.1.13", "netscape-comment" }, /* GnuPG extensions */ { "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" }, { NULL } }; /* Return the description for OID; if no description is available NULL is returned. */ static const char * get_oid_desc (const char *oid, unsigned int *flag) { int i; if (oid) for (i=0; oidtranstbl[i].oid; i++) if (!strcmp (oidtranstbl[i].oid, oid)) { if (flag) *flag = oidtranstbl[i].flag; return oidtranstbl[i].name; } if (flag) *flag = 0; return NULL; } static void print_key_data (ksba_cert_t cert, FILE *fp) { #if 0 int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; int i; for(i=0; i < n; i++ ) { fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); mpi_print(stdout, pk->pkey[i], 1 ); putchar(':'); putchar('\n'); } #endif } static void print_capabilities (ksba_cert_t cert, FILE *fp) { gpg_error_t err; unsigned int use; size_t buflen; char buffer[1]; err = ksba_cert_get_user_data (cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (!err && buflen) { if (*buffer) putc ('q', fp); } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; /* Don't know - will not get marked as 'q' */ else log_debug ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { putc ('e', fp); putc ('s', fp); putc ('c', fp); putc ('E', fp); putc ('S', fp); putc ('C', fp); return; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); return; } if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) putc ('e', fp); if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) putc ('s', fp); if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) putc ('c', fp); if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) putc ('E', fp); if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) putc ('S', fp); if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) putc ('C', fp); } static void print_time (gnupg_isotime_t t, FILE *fp) { if (!t || !*t) ; else fputs (t, fp); } /* Return an allocated string with the email address extracted from a DN */ static char * email_kludge (const char *name) { const char *p, *string; unsigned char *buf; int n; string = name; for (;;) { p = strstr (string, "1.2.840.113549.1.9.1=#"); if (!p) return NULL; if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\')) { name = p + 22; break; } string = p + 22; } /* This looks pretty much like an email address in the subject's DN we use this to add an additional user ID entry. This way, openSSL generated keys get a nicer and usable listing */ for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) ; if (!n) return NULL; buf = xtrymalloc (n+3); if (!buf) return NULL; /* oops, out of core */ *buf = '<'; for (n=1, p=name; hexdigitp (p); p +=2, n++) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; return (char*)buf; } /* List one certificate in colon mode */ static void list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity, FILE *fp, int have_secret) { int rc; int idx; char truststring[2]; char *p; ksba_sexp_t sexp; char *fpr; ksba_isotime_t t; gpg_error_t valerr; int algo; unsigned int nbits; const char *chain_id; char *chain_id_buffer = NULL; int is_root = 0; if (ctrl->with_validation) valerr = gpgsm_validate_chain (ctrl, cert, NULL, 1, NULL, 0); else valerr = 0; /* We need to get the fingerprint and the chaining ID in advance. */ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); { ksba_cert_t next; rc = gpgsm_walk_cert_chain (cert, &next); if (!rc) /* We known the issuer's certificate. */ { p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1); chain_id_buffer = p; chain_id = chain_id_buffer; ksba_cert_release (next); } else if (rc == -1) /* We have reached the root certificate. */ { chain_id = fpr; is_root = 1; } else chain_id = NULL; } fputs (have_secret? "crs:":"crt:", fp); /* Note: We can't use multiple flags, like "ei", because the validation check does only return one error. */ truststring[0] = 0; truststring[1] = 0; if ((validity & VALIDITY_REVOKED) || gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED) *truststring = 'r'; else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED) *truststring = 'e'; else { /* Lets also check whether the certificate under question expired. This is merely a hack until we found a proper way to store the expiration flag in the keybox. */ ksba_isotime_t current_time, not_after; gnupg_get_isotime (current_time); if (!opt.ignore_expiration && !ksba_cert_get_validity (cert, 1, not_after) && *not_after && strcmp (current_time, not_after) > 0 ) *truststring = 'e'; else if (valerr) *truststring = 'i'; } /* Is we have no truststring yet (i.e. the certificate might be good) and this is a root certificate, we ask the agent whether this is a trusted root certificate. */ if (!*truststring && is_root) { rc = gpgsm_agent_istrusted (ctrl, cert); if (!rc) *truststring = 'u'; /* Yes, we trust this one (ultimately). */ else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) *truststring = 'n'; /* No, we do not trust this one. */ /* (in case of an error we can't tell anything.) */ } if (*truststring) fputs (truststring, fp); algo = gpgsm_get_key_algo_info (cert, &nbits); fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24); /* We assume --fixed-list-mode for gpgsm */ ksba_cert_get_validity (cert, 0, t); print_time (t, fp); putc (':', fp); ksba_cert_get_validity (cert, 1, t); print_time ( t, fp); putc (':', fp); /* Field 8, serial number: */ if ((sexp = ksba_cert_get_serial (cert))) { int len; const unsigned char *s = sexp; if (*s == '(') { s++; for (len=0; *s && *s != ':' && digitp (s); s++) len = len*10 + atoi_1 (s); if (*s == ':') for (s++; len; len--, s++) fprintf (fp,"%02X", *s); } xfree (sexp); } putc (':', fp); /* Field 9, ownertrust - not used here */ putc (':', fp); /* field 10, old user ID - we use it here for the issuer DN */ if ((p = ksba_cert_get_issuer (cert,0))) { print_sanitized_string (fp, p, ':'); xfree (p); } putc (':', fp); /* Field 11, signature class - not used */ putc (':', fp); /* Field 12, capabilities: */ print_capabilities (cert, fp); putc (':', fp); putc ('\n', fp); /* FPR record */ fprintf (fp, "fpr:::::::::%s:::", fpr); /* Print chaining ID (field 13)*/ if (chain_id) fputs (chain_id, fp); putc (':', fp); putc ('\n', fp); xfree (fpr); fpr = NULL; chain_id = NULL; xfree (chain_id_buffer); chain_id_buffer = NULL; if (opt.with_key_data) { if ( (p = gpgsm_get_keygrip_hexstring (cert))) { fprintf (fp, "grp:::::::::%s:\n", p); xfree (p); } print_key_data (cert, fp); } for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++) { fprintf (fp, "uid:%s::::::::", truststring); print_sanitized_string (fp, p, ':'); putc (':', fp); putc (':', fp); putc ('\n', fp); if (!idx) { /* It would be better to get the faked email address from the keydb. But as long as we don't have a way to pass the meta data back, we just check it the same way as the code used to create the keybox meta data does */ char *pp = email_kludge (p); if (pp) { fprintf (fp, "uid:%s::::::::", truststring); print_sanitized_string (fp, pp, ':'); putc (':', fp); putc (':', fp); putc ('\n', fp); xfree (pp); } } xfree (p); } } static void print_name_raw (FILE *fp, const char *string) { if (!string) fputs ("[error]", fp); else print_sanitized_string (fp, string, 0); } static void print_names_raw (FILE *fp, int indent, ksba_name_t name) { int idx; const char *s; int indent_all; if ((indent_all = (indent < 0))) indent = - indent; if (!name) { fputs ("none\n", fp); return; } for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); printf ("%*s", idx||indent_all?indent:0, ""); print_sanitized_string (fp, p?p:s, 0); putc ('\n', fp); xfree (p); } } /* List one certificate in raw mode useful to have a closer look at the certificate. This one does no beautification and only minimal output sanitation. It is mainly useful for debugging. */ static void list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, ksba_cert_t cert, FILE *fp, int have_secret, int with_validation) { gpg_error_t err; size_t off, len; ksba_sexp_t sexp, keyid; char *dn; ksba_isotime_t t; int idx, i; int is_ca, chainlen; unsigned int kusage; char *string, *p, *pend; const char *oid, *s; ksba_name_t name, name2; unsigned int reason; sexp = ksba_cert_get_serial (cert); fputs ("Serial number: ", fp); gpgsm_print_serial (fp, sexp); ksba_free (sexp); putc ('\n', fp); dn = ksba_cert_get_issuer (cert, 0); fputs (" Issuer: ", fp); print_name_raw (fp, dn); ksba_free (dn); putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) { fputs (" aka: ", fp); print_name_raw (fp, dn); ksba_free (dn); putc ('\n', fp); } dn = ksba_cert_get_subject (cert, 0); fputs (" Subject: ", fp); print_name_raw (fp, dn); ksba_free (dn); putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) { fputs (" aka: ", fp); print_name_raw (fp, dn); ksba_free (dn); putc ('\n', fp); } dn = gpgsm_get_fingerprint_string (cert, 0); fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); fprintf (fp, " md5_fpr: %s\n", dn?dn:"error"); xfree (dn); + dn = gpgsm_get_certid (cert); + fprintf (fp, " certid: %s\n", dn?dn:"error"); + xfree (dn); + dn = gpgsm_get_keygrip_hexstring (cert); fprintf (fp, " keygrip: %s\n", dn?dn:"error"); xfree (dn); ksba_cert_get_validity (cert, 0, t); fputs (" notBefore: ", fp); gpgsm_print_time (fp, t); putc ('\n', fp); fputs (" notAfter: ", fp); ksba_cert_get_validity (cert, 1, t); gpgsm_print_time (fp, t); putc ('\n', fp); oid = ksba_cert_get_digest_algo (cert); s = get_oid_desc (oid, NULL); fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":""); { const char *algoname; unsigned int nbits; algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits)); fprintf (fp, " keyType: %u bit %s\n", nbits, algoname? algoname:"?"); } /* subjectKeyIdentifier */ fputs (" subjKeyId: ", fp); err = ksba_cert_get_subj_key_id (cert, NULL, &keyid); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) fputs ("[none]\n", fp); else { gpgsm_print_serial (fp, keyid); ksba_free (keyid); putc ('\n', fp); } } else fputs ("[?]\n", fp); /* authorityKeyIdentifier */ fputs (" authKeyId: ", fp); err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name) fputs ("[none]\n", fp); else { gpgsm_print_serial (fp, sexp); ksba_free (sexp); putc ('\n', fp); print_names_raw (fp, -15, name); ksba_name_release (name); } if (keyid) { fputs (" authKeyId.ki: ", fp); gpgsm_print_serial (fp, keyid); ksba_free (keyid); putc ('\n', fp); } } else fputs ("[?]\n", fp); fputs (" keyUsage:", fp); err = ksba_cert_get_key_usage (cert, &kusage); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) fprintf (fp, " [error: %s]", gpg_strerror (err)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) fputs (" digitalSignature", fp); if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) fputs (" nonRepudiation", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) fputs (" keyEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) fputs (" dataEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) fputs (" keyAgreement", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) fputs (" certSign", fp); if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) fputs (" crlSign", fp); if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) fputs (" encipherOnly", fp); if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) fputs (" decipherOnly", fp); } putc ('\n', fp); } else fputs ("[none]\n", fp); fputs (" extKeyUsage: ", fp); err = ksba_cert_get_ext_key_usages (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; for (i=0; key_purpose_map[i].oid; i++) if ( !strcmp (key_purpose_map[i].oid, p) ) break; fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp); p = pend; if (*p != 'C') fputs (" (suggested)", fp); if ((p = strchr (p, '\n'))) { p++; fputs ("\n ", fp); } } xfree (string); } putc ('\n', fp); } else fputs ("[none]\n", fp); fputs (" policies: ", fp); err = ksba_cert_get_cert_policies (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; for (i=0; key_purpose_map[i].oid; i++) if ( !strcmp (key_purpose_map[i].oid, p) ) break; fputs (p, fp); p = pend; if (*p == 'C') fputs (" (critical)", fp); if ((p = strchr (p, '\n'))) { p++; fputs ("\n ", fp); } } xfree (string); } putc ('\n', fp); } else fputs ("[none]\n", fp); fputs (" chainLength: ", fp); err = ksba_cert_is_ca (cert, &is_ca, &chainlen); if (err || is_ca) { if (err) fprintf (fp, "[error: %s]", gpg_strerror (err)); else if (chainlen == -1) fputs ("unlimited", fp); else fprintf (fp, "%d", chainlen); putc ('\n', fp); } else fputs ("not a CA\n", fp); /* CRL distribution point */ for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2, &reason)) ;idx++) { fputs (" crlDP: ", fp); print_names_raw (fp, 15, name); if (reason) { fputs (" reason: ", fp); if ( (reason & KSBA_CRLREASON_UNSPECIFIED)) fputs (" unused", stdout); if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE)) fputs (" keyCompromise", stdout); if ( (reason & KSBA_CRLREASON_CA_COMPROMISE)) fputs (" caCompromise", stdout); if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED)) fputs (" affiliationChanged", stdout); if ( (reason & KSBA_CRLREASON_SUPERSEDED)) fputs (" superseded", stdout); if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION)) fputs (" cessationOfOperation", stdout); if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD)) fputs (" certificateHold", stdout); putchar ('\n'); } fputs (" issuer: ", fp); print_names_raw (fp, 23, name2); ksba_name_release (name); ksba_name_release (name2); } if (err && gpg_err_code (err) != GPG_ERR_EOF) fputs (" crlDP: [error]\n", fp); else if (!idx) fputs (" crlDP: [none]\n", fp); /* authorityInfoAccess. */ for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string, &name)); idx++) { fputs (" authInfo: ", fp); s = get_oid_desc (string, NULL); fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); print_names_raw (fp, -15, name); ksba_name_release (name); ksba_free (string); } if (err && gpg_err_code (err) != GPG_ERR_EOF) fputs (" authInfo: [error]\n", fp); else if (!idx) fputs (" authInfo: [none]\n", fp); /* subjectInfoAccess. */ for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string, &name)); idx++) { fputs (" subjectInfo: ", fp); s = get_oid_desc (string, NULL); fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); print_names_raw (fp, -15, name); ksba_name_release (name); ksba_free (string); } if (err && gpg_err_code (err) != GPG_ERR_EOF) fputs (" subjInfo: [error]\n", fp); else if (!idx) fputs (" subjInfo: [none]\n", fp); for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &i, &off, &len));idx++) { unsigned int flag; s = get_oid_desc (oid, &flag); if (!(flag & 1)) fprintf (fp, " %s: %s%s%s%s [%d octets]\n", i? "critExtn":" extn", oid, s?" (":"", s?s:"", s?")":"", (int)len); } if (with_validation) { err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp, 0); if (!err) fprintf (fp, " [certificate is good]\n"); else fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); } if (opt.with_ephemeral_keys && hd) { unsigned int blobflags; err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags); if (err) fprintf (fp, " [error getting keyflags: %s]\n", gpg_strerror (err)); else if ((blobflags & 2)) fprintf (fp, " [stored as ephemeral]\n"); } } /* List one certificate in standard mode */ static void list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret, int with_validation) { gpg_error_t err; ksba_sexp_t sexp; char *dn; ksba_isotime_t t; int idx, i; int is_ca, chainlen; unsigned int kusage; char *string, *p, *pend; sexp = ksba_cert_get_serial (cert); fputs ("Serial number: ", fp); gpgsm_print_serial (fp, sexp); ksba_free (sexp); putc ('\n', fp); dn = ksba_cert_get_issuer (cert, 0); fputs (" Issuer: ", fp); gpgsm_print_name (fp, dn); ksba_free (dn); putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) { fputs (" aka: ", fp); gpgsm_print_name (fp, dn); ksba_free (dn); putc ('\n', fp); } dn = ksba_cert_get_subject (cert, 0); fputs (" Subject: ", fp); gpgsm_print_name (fp, dn); ksba_free (dn); putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) { fputs (" aka: ", fp); gpgsm_print_name (fp, dn); ksba_free (dn); putc ('\n', fp); } ksba_cert_get_validity (cert, 0, t); fputs (" validity: ", fp); gpgsm_print_time (fp, t); fputs (" through ", fp); ksba_cert_get_validity (cert, 1, t); gpgsm_print_time (fp, t); putc ('\n', fp); { const char *algoname; unsigned int nbits; algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits)); fprintf (fp, " key type: %u bit %s\n", nbits, algoname? algoname:"?"); } err = ksba_cert_get_key_usage (cert, &kusage); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { fputs (" key usage:", fp); if (err) fprintf (fp, " [error: %s]", gpg_strerror (err)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) fputs (" digitalSignature", fp); if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) fputs (" nonRepudiation", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) fputs (" keyEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) fputs (" dataEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) fputs (" keyAgreement", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) fputs (" certSign", fp); if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) fputs (" crlSign", fp); if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) fputs (" encipherOnly", fp); if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) fputs (" decipherOnly", fp); } putc ('\n', fp); } err = ksba_cert_get_ext_key_usages (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { fputs ("ext key usage: ", fp); if (err) fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; for (i=0; key_purpose_map[i].oid; i++) if ( !strcmp (key_purpose_map[i].oid, p) ) break; fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp); p = pend; if (*p != 'C') fputs (" (suggested)", fp); if ((p = strchr (p, '\n'))) { p++; fputs (", ", fp); } } xfree (string); } putc ('\n', fp); } err = ksba_cert_get_cert_policies (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { fputs (" policies: ", fp); if (err) fprintf (fp, "[error: %s]", gpg_strerror (err)); else { for (p=string; *p; p++) { if (*p == '\n') *p = ','; } print_sanitized_string (fp, string, 0); xfree (string); } putc ('\n', fp); } err = ksba_cert_is_ca (cert, &is_ca, &chainlen); if (err || is_ca) { fputs (" chain length: ", fp); if (err) fprintf (fp, "[error: %s]", gpg_strerror (err)); else if (chainlen == -1) fputs ("unlimited", fp); else fprintf (fp, "%d", chainlen); putc ('\n', fp); } if (opt.with_md5_fingerprint) { dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); fprintf (fp, " md5 fpr: %s\n", dn?dn:"error"); xfree (dn); } dn = gpgsm_get_fingerprint_string (cert, 0); fprintf (fp, " fingerprint: %s\n", dn?dn:"error"); xfree (dn); if (with_validation) { gpg_error_t tmperr; size_t buflen; char buffer[1]; err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp, 0); tmperr = ksba_cert_get_user_data (cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (!tmperr && buflen) { if (*buffer) fputs (" [qualified]\n", fp); } else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND) ; /* Don't know - will not get marked as 'q' */ else log_debug ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (tmperr)); if (!err) fprintf (fp, " [certificate is good]\n"); else fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); } } /* Same as standard mode mode list all certifying certs too. */ static void list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd, ksba_cert_t cert, int raw_mode, FILE *fp, int with_validation) { ksba_cert_t next = NULL; if (raw_mode) list_cert_raw (ctrl, hd, cert, fp, 0, with_validation); else list_cert_std (ctrl, cert, fp, 0, with_validation); ksba_cert_ref (cert); while (!gpgsm_walk_cert_chain (cert, &next)) { ksba_cert_release (cert); fputs ("Certified by\n", fp); if (raw_mode) list_cert_raw (ctrl, hd, next, fp, 0, with_validation); else list_cert_std (ctrl, next, fp, 0, with_validation); cert = next; } ksba_cert_release (cert); putc ('\n', fp); } /* List all internal keys or just the keys given as NAMES. MODE is a bit vector to specify what keys are to be included; see gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw output mode will be used intead of the standard beautified one. */ static gpg_error_t list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp, unsigned int mode, int raw_mode) { KEYDB_HANDLE hd; KEYDB_SEARCH_DESC *desc = NULL; STRLIST sl; int ndesc; ksba_cert_t cert = NULL; gpg_error_t rc = 0; const char *lastresname, *resname; int have_secret; hd = keydb_new (0); if (!hd) { log_error ("keydb_new failed\n"); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { rc = gpg_error_from_errno (errno); log_error ("out of core\n"); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = keydb_classify_name (sl->d, desc+ndesc); if (rc) { log_error ("key `%s' not found: %s\n", sl->d, gpg_strerror (rc)); rc = 0; } else ndesc++; } } if (opt.with_ephemeral_keys) keydb_set_ephemeral (hd, 1); /* It would be nice to see which of the given users did actually match one in the keyring. To implement this we need to have a found flag for each entry in desc and to set this we must check all those entries after a match to mark all matched one - currently we stop at the first match. To do this we need an extra flag to enable this feature so */ lastresname = NULL; while (!(rc = keydb_search (hd, desc, ndesc))) { unsigned int validity; if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity); if (rc) { log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc)); goto leave; } rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); goto leave; } resname = keydb_get_resource_name (hd); if (lastresname != resname ) { int i; if (ctrl->no_server) { fprintf (fp, "%s\n", resname ); for (i=strlen(resname); i; i-- ) putchar('-'); putc ('\n', fp); lastresname = resname; } } have_secret = 0; if (mode) { char *p = gpgsm_get_keygrip_hexstring (cert); if (p) { rc = gpgsm_agent_havekey (ctrl, p); if (!rc) have_secret = 1; else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY) goto leave; rc = 0; xfree (p); } } if (!mode || ((mode & 1) && !have_secret) || ((mode & 2) && have_secret) ) { if (ctrl->with_colons) list_cert_colon (ctrl, cert, validity, fp, have_secret); else if (ctrl->with_chain) list_cert_chain (ctrl, hd, cert, raw_mode, fp, ctrl->with_validation); else { if (raw_mode) list_cert_raw (ctrl, hd, cert, fp, have_secret, ctrl->with_validation); else list_cert_std (ctrl, cert, fp, have_secret, ctrl->with_validation); putc ('\n', fp); } } ksba_cert_release (cert); cert = NULL; } if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 ) rc = 0; if (rc) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); leave: ksba_cert_release (cert); xfree (desc); keydb_release (hd); return rc; } static void list_external_cb (void *cb_value, ksba_cert_t cert) { struct list_external_parm_s *parm = cb_value; if (keydb_store_cert (cert, 1, NULL)) log_error ("error storing certificate as ephemeral\n"); if (parm->print_header) { const char *resname = "[external keys]"; int i; fprintf (parm->fp, "%s\n", resname ); for (i=strlen(resname); i; i-- ) putchar('-'); putc ('\n', parm->fp); parm->print_header = 0; } if (parm->with_colons) list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0); else if (parm->with_chain) list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0); else { if (parm->raw_mode) list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0); else list_cert_std (parm->ctrl, cert, parm->fp, 0, 0); putc ('\n', parm->fp); } } /* List external keys similar to internal one. Note: mode does not make sense here because it would be unwise to list external secret keys */ static gpg_error_t list_external_keys (CTRL ctrl, STRLIST names, FILE *fp, int raw_mode) { int rc; struct list_external_parm_s parm; parm.fp = fp; parm.ctrl = ctrl, parm.print_header = ctrl->no_server; parm.with_colons = ctrl->with_colons; parm.with_chain = ctrl->with_chain; parm.raw_mode = raw_mode; rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm); if (rc) log_error ("listing external keys failed: %s\n", gpg_strerror (rc)); return rc; } /* List all keys or just the key given as NAMES. MODE controls the operation mode: Bit 0-2: 0 = list all public keys but don't flag secret ones 1 = list only public keys 2 = list only secret keys 3 = list secret and public keys Bit 6: list internal keys Bit 7: list external keys Bit 8: Do a raw format dump. */ gpg_error_t gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode) { gpg_error_t err = 0; if ((mode & (1<<6))) err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256)); if (!err && (mode & (1<<7))) err = list_external_keys (ctrl, names, fp, (mode&256)); return err; } diff --git a/sm/misc.c b/sm/misc.c index cd072ce6b..86cb506d6 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -1,82 +1,83 @@ /* misc.c - Miscellaneous fucntions * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "gpgsm.h" #include "i18n.h" #include "setenv.h" /* Setup the environment so that the pinentry is able to get all required information. This is used prior to an exec of the protect-tool. */ void setup_pinentry_env (void) { #ifndef HAVE_W32_SYSTEM char *lc; if (opt.display) setenv ("DISPLAY", opt.display, 1); /* Try to make sure that GPG_TTY has been set. This is needed if we call for example the protect-tools with redirected stdin and thus it won't be able to ge a default by itself. Try to do it here but print a warning. */ if (opt.ttyname) setenv ("GPG_TTY", opt.ttyname, 1); else if (!(lc=getenv ("GPG_TTY")) || !*lc) { log_error (_("GPG_TTY has not been set - " "using maybe bogus default\n")); lc = ttyname (0); if (!lc) lc = "/dev/tty"; setenv ("GPG_TTY", lc, 1); } if (opt.ttytype) setenv ("TERM", opt.ttytype, 1); if (opt.lc_ctype) setenv ("LC_CTYPE", opt.lc_ctype, 1); #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) else if ( (lc = setlocale (LC_CTYPE, "")) ) setenv ("LC_CTYPE", lc, 1); #endif if (opt.lc_messages) setenv ("LC_MESSAGES", opt.lc_messages, 1); #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) else if ( (lc = setlocale (LC_MESSAGES, "")) ) setenv ("LC_MESSAGES", lc, 1); #endif #endif /*!HAVE_W32_SYSTEM*/ } diff --git a/sm/qualified.c b/sm/qualified.c index 07abaadc4..474e1488d 100644 --- a/sm/qualified.c +++ b/sm/qualified.c @@ -1,367 +1,368 @@ /* qualified.c - Routines related to qualified signatures * Copyright (C) 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #include "gpgsm.h" #include "i18n.h" #include /* We open the file only once and keep the open file pointer as well as the name of the file here. Note that, a listname not equal to NULL indicates that this module has been intialized and if the LISTFP is also NULL, no list of qualified signatures exists. */ static char *listname; static FILE *listfp; /* Read the trustlist and return entry by entry. KEY must point to a buffer of at least 41 characters. COUNTRY shall be a buffer of at least 3 characters to receive the country code of that qualified signature (i.e. "de" for German and "be" for Belgium). Reading a valid entry returns 0, EOF is indicated by GPG_ERR_EOF and any other error condition is indicated by the appropriate error code. */ static gpg_error_t read_list (char *key, char *country, int *lnr) { gpg_error_t err; int c, i, j; char *p, line[256]; *key = 0; *country = 0; if (!listname) { listname = make_filename (GNUPG_DATADIR, "qualified.txt", NULL); listfp = fopen (listname, "r"); if (!listfp && errno != ENOENT) { err = gpg_error_from_errno (errno); log_error (_("can't open `%s': %s\n"), listname, gpg_strerror (err)); return err; } } if (!listfp) return gpg_error (GPG_ERR_EOF); do { if (!fgets (line, DIM(line)-1, listfp) ) { if (feof (listfp)) return gpg_error (GPG_ERR_EOF); return gpg_error_from_errno (errno); } if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line. */ while ( (c=getc (listfp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } ++*lnr; /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) if ( p[i] != ':' ) key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; key[j] = 0; if (j != 40 || !(spacep (p+i) || p[i] == '\n')) { log_error (_("invalid formatted fingerprint in `%s', line %d\n"), listname, *lnr); return gpg_error (GPG_ERR_BAD_DATA); } assert (p[i]); i++; while (spacep (p+i)) i++; if ( p[i] >= 'a' && p[i] <= 'z' && p[i+1] >= 'a' && p[i+1] <= 'z' && (spacep (p+i+2) || p[i+2] == '\n')) { country[0] = p[i]; country[1] = p[i+1]; country[2] = 0; } else { log_error (_("invalid country code in `%s', line %d\n"), listname, *lnr); return gpg_error (GPG_ERR_BAD_DATA); } return 0; } /* Check whether the certificate CERT is included in the list of qualified certificates. This list is similar to the "trustlist.txt" as maintained by gpg-agent and includes fingerprints of root certificates to be used for qualified (legally binding like handwritten) signatures. We keep this list system wide and not per user because it is not a decision of the user. Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it is not in the list or any other error (e.g. if no list of qualified signatures is available. If COUNTRY has not been passed as NULL a string witha maximum length of 2 will be copied into it; thus the caller needs to provide a buffer of length 3. */ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country) { gpg_error_t err; char *fpr; char key[41]; char mycountry[3]; int lnr = 0; if (country) *country = 0; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (!fpr) return gpg_error (GPG_ERR_GENERAL); if (listfp) rewind (listfp); while (!(err = read_list (key, mycountry, &lnr))) { if (!strcmp (key, fpr)) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); if (!err && country) strcpy (country, mycountry); xfree (fpr); return err; } /* We know that CERT is a qualified certificate. Ask the user for consent to actually create a signature using this certificate. Returns: 0 for yes, GPG_ERR_CANCEL for no or any otehr error code. */ gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; char *name, *subject, *buffer, *p; const char *s; char *orig_codeset = NULL; name = ksba_cert_get_subject (cert, 0); if (!name) return gpg_error (GPG_ERR_GENERAL); subject = gpgsm_format_name2 (name, 0); ksba_free (name); name = NULL; #ifdef ENABLE_NLS /* The Assuan agent protocol requires us to transmit utf-8 strings */ orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); #ifdef HAVE_LANGINFO_CODESET if (!orig_codeset) orig_codeset = nl_langinfo (CODESET); #endif if (orig_codeset) { /* We only switch when we are able to restore the codeset later. Note that bind_textdomain_codeset does only return on memory errors but not if a codeset is not available. Thus we don't bother printing a diagnostic here. */ orig_codeset = xstrdup (orig_codeset); if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8")) orig_codeset = NULL; } #endif if (asprintf (&name, _("You are about to create a signature using your " "certificate:\n" "\"%s\"\n" "This will create a qualified signature by law " "equated to a handwritten signature.\n\n%s%s" "Are you really sure that you want to do this?"), subject? subject:"?", opt.qualsig_approval? "": _("Note, that this software is not officially approved " "to create or verify such signatures.\n"), opt.qualsig_approval? "":"\n" ) < 0 ) err = gpg_error_from_errno (errno); else err = 0; #ifdef ENABLE_NLS if (orig_codeset) bind_textdomain_codeset (PACKAGE_GT, orig_codeset); #endif xfree (orig_codeset); xfree (subject); if (err) return err; buffer = p = xtrymalloc (strlen (name) * 3 + 1); if (!buffer) { err = gpg_error_from_errno (errno); free (name); return err; } for (s=name; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *(unsigned char *)s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; free (name); err = gpgsm_agent_get_confirmation (ctrl, buffer); xfree (buffer); return err; } /* Popup a prompt to inform the user that the signature created is not a qualified one. This is of course only done if we know that we have been approved. */ gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; char *name, *subject, *buffer, *p; const char *s; char *orig_codeset = NULL; if (!opt.qualsig_approval) return 0; name = ksba_cert_get_subject (cert, 0); if (!name) return gpg_error (GPG_ERR_GENERAL); subject = gpgsm_format_name2 (name, 0); ksba_free (name); name = NULL; #ifdef ENABLE_NLS /* The Assuan agent protocol requires us to transmit utf-8 strings */ orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); #ifdef HAVE_LANGINFO_CODESET if (!orig_codeset) orig_codeset = nl_langinfo (CODESET); #endif if (orig_codeset) { /* We only switch when we are able to restore the codeset later. Note that bind_textdomain_codeset does only return on memory errors but not if a codeset is not available. Thus we don't bother printing a diagnostic here. */ orig_codeset = xstrdup (orig_codeset); if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8")) orig_codeset = NULL; } #endif if (asprintf (&name, _("You are about to create a signature using your " "certificate:\n" "\"%s\"\n" "Note, that this certificate will NOT create a " "qualified signature!"), subject? subject:"?") < 0 ) err = gpg_error_from_errno (errno); else err = 0; #ifdef ENABLE_NLS if (orig_codeset) bind_textdomain_codeset (PACKAGE_GT, orig_codeset); #endif xfree (orig_codeset); xfree (subject); if (err) return err; buffer = p = xtrymalloc (strlen (name) * 3 + 1); if (!buffer) { err = gpg_error_from_errno (errno); free (name); return err; } for (s=name; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *(unsigned char *)s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; free (name); err = gpgsm_agent_get_confirmation (ctrl, buffer); xfree (buffer); return err; } diff --git a/sm/server.c b/sm/server.c index 87a06ee4e..57e5d8f38 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1,1124 +1,1125 @@ /* server.c - Server mode and main entry point * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include #include "gpgsm.h" #define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) /* The filepointer for status message used in non-server mode */ static FILE *statusfp; /* Data used to assuciate an Assuan context with local server data */ struct server_local_s { assuan_context_t assuan_ctx; int message_fd; int list_internal; int list_external; certlist_t recplist; certlist_t signerlist; certlist_t default_recplist; /* As set by main() - don't release. */ }; /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 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))); } static void close_message_fd (CTRL ctrl) { if (ctrl->server_local->message_fd != -1) { close (ctrl->server_local->message_fd); ctrl->server_local->message_fd = -1; } } static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) { CTRL ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "include-certs")) { int i = *value? atoi (value) : -1; if (ctrl->include_certs < -2) return ASSUAN_Parameter_Error; ctrl->include_certs = i; } else if (!strcmp (key, "display")) { if (opt.display) free (opt.display); opt.display = strdup (value); if (!opt.display) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "ttyname")) { if (opt.ttyname) free (opt.ttyname); opt.ttyname = strdup (value); if (!opt.ttyname) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "ttytype")) { if (opt.ttytype) free (opt.ttytype); opt.ttytype = strdup (value); if (!opt.ttytype) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "lc-ctype")) { if (opt.lc_ctype) free (opt.lc_ctype); opt.lc_ctype = strdup (value); if (!opt.lc_ctype) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "lc-messages")) { if (opt.lc_messages) free (opt.lc_messages); opt.lc_messages = strdup (value); if (!opt.lc_messages) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "list-mode")) { int i = *value? atoi (value) : 0; if (!i || i == 1) /* default and mode 1 */ { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 0; } else if (i == 2) { ctrl->server_local->list_internal = 0; ctrl->server_local->list_external = 1; } else if (i == 3) { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 1; } else return ASSUAN_Parameter_Error; } else if (!strcmp (key, "with-validation")) { int i = *value? atoi (value) : 0; ctrl->with_validation = i; } else return ASSUAN_Invalid_Option; return 0; } static void reset_notify (ASSUAN_CONTEXT ctx) { CTRL ctrl = assuan_get_pointer (ctx); gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); } static void input_notify (ASSUAN_CONTEXT ctx, const char *line) { CTRL ctrl = assuan_get_pointer (ctx); ctrl->autodetect_encoding = 0; ctrl->is_pem = 0; ctrl->is_base64 = 0; if (strstr (line, "--armor")) ctrl->is_pem = 1; else if (strstr (line, "--base64")) ctrl->is_base64 = 1; else if (strstr (line, "--binary")) ; else ctrl->autodetect_encoding = 1; } static void output_notify (ASSUAN_CONTEXT ctx, const char *line) { CTRL ctrl = assuan_get_pointer (ctx); ctrl->create_pem = 0; ctrl->create_base64 = 0; if (strstr (line, "--armor")) ctrl->create_pem = 1; else if (strstr (line, "--base64")) ctrl->create_base64 = 1; /* just the raw output */ } /* RECIPIENT Set the recipient for the encryption. should be the internal representation of the key; the server may accept any other way of specification [we will support this]. If this is a valid and trusted recipient the server does respond with OK, otherwise the return is an ERR with the reason why the recipient can't be used, the encryption will then not be done for this recipient. If the policy is not to encrypt at all if not all recipients are valid, the client has to take care of this. All RECIPIENT commands are cumulative until a RESET or an successful ENCRYPT command. */ static int cmd_recipient (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist, 0); if (rc) { gpg_err_code_t r = gpg_err_code (rc); gpgsm_status2 (ctrl, STATUS_INV_RECP, r == -1? "1": r == GPG_ERR_NO_PUBKEY? "1": r == GPG_ERR_AMBIGUOUS_NAME? "2": r == GPG_ERR_WRONG_KEY_USAGE? "3": r == GPG_ERR_CERT_REVOKED? "4": r == GPG_ERR_CERT_EXPIRED? "5": r == GPG_ERR_NO_CRL_KNOWN? "6": r == GPG_ERR_CRL_TOO_OLD? "7": r == GPG_ERR_NO_POLICY_MATCH? "8": "0", line, NULL); } return map_to_assuan_status (rc); } /* SIGNER Set the signer's keys for the signature creation. should be the internal representation of the key; the server may accept any other way of specification [we will support this]. If this is a valid and usable signing key the server does respond with OK, otherwise it returns an ERR with the reason why the key can't be used, the signing will then not be done for this key. If the policy is not to sign at all if not all signer keys are valid, the client has to take care of this. All SIGNER commands are cumulative until a RESET but they are *not* reset by an SIGN command becuase it can be expected that set of signers are used for more than one sign operation. Note that this command returns an INV_RECP status which is a bit strange, but they are very similar. */ static int cmd_signer (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist, 0); if (rc) { gpg_err_code_t r = gpg_err_code (rc); gpgsm_status2 (ctrl, STATUS_INV_RECP, r == -1? "1": r == GPG_ERR_NO_PUBKEY? "1": r == GPG_ERR_AMBIGUOUS_NAME? "2": r == GPG_ERR_WRONG_KEY_USAGE? "3": r == GPG_ERR_CERT_REVOKED? "4": r == GPG_ERR_CERT_EXPIRED? "5": r == GPG_ERR_NO_CRL_KNOWN? "6": r == GPG_ERR_CRL_TOO_OLD? "7": r == GPG_ERR_NO_POLICY_MATCH? "8": r == GPG_ERR_NO_SECKEY? "9": "0", line, NULL); } return map_to_assuan_status (rc); } /* ENCRYPT Do the actual encryption process. Takes the plaintext from the INPUT command, writes to the ciphertext to the file descriptor set with the OUTPUT command, take the recipients form all the recipients set so far. If this command fails the clients should try to delete all output currently done or otherwise mark it as invalid. GPGSM does ensure that there won't be any security problem with leftover data on the output in this case. This command should in general not fail, as all necessary checks have been done while setting the recipients. The input and output pipes are closed. */ static int cmd_encrypt (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); certlist_t cl; int inp_fd, out_fd; FILE *out_fp; int rc; inp_fd = assuan_get_input_fd (ctx); if (inp_fd == -1) return set_error (No_Input, NULL); out_fd = assuan_get_output_fd (ctx); if (out_fd == -1) return set_error (No_Output, NULL); out_fp = fdopen ( dup(out_fd), "w"); if (!out_fp) return set_error (General_Error, "fdopen() failed"); /* Now add all encrypt-to marked recipients from the default list. */ rc = 0; if (!opt.no_encrypt_to) { for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) if (cl->is_encrypt_to) rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, &ctrl->server_local->recplist, 1); } if (!rc) rc = gpgsm_encrypt (assuan_get_pointer (ctx), ctrl->server_local->recplist, inp_fd, out_fp); fclose (out_fp); gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } /* DECRYPT This performs the decrypt operation after doing some check on the internal state. (e.g. that only needed data has been set). Because it utilizes the GPG-Agent for the session key decryption, there is no need to ask the client for a protecting passphrase - GpgAgent does take care of this by requesting this from the user. */ static int cmd_decrypt (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; FILE *out_fp; int rc; inp_fd = assuan_get_input_fd (ctx); if (inp_fd == -1) return set_error (No_Input, NULL); out_fd = assuan_get_output_fd (ctx); if (out_fd == -1) return set_error (No_Output, NULL); out_fp = fdopen ( dup(out_fd), "w"); if (!out_fp) return set_error (General_Error, "fdopen() failed"); rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } /* VERIFY This does a verify operation on the message send to the input-FD. The result is written out using status lines. If an output FD was given, the signed text will be written to that. If the signature is a detached one, the server will inquire about the signed material and the client must provide it. */ static int cmd_verify (ASSUAN_CONTEXT ctx, char *line) { int rc; CTRL ctrl = assuan_get_pointer (ctx); int fd = assuan_get_input_fd (ctx); int out_fd = assuan_get_output_fd (ctx); FILE *out_fp = NULL; if (fd == -1) return set_error (No_Input, NULL); if (out_fd != -1) { out_fp = fdopen ( dup(out_fd), "w"); if (!out_fp) return set_error (General_Error, "fdopen() failed"); } rc = gpgsm_verify (assuan_get_pointer (ctx), fd, ctrl->server_local->message_fd, out_fp); if (out_fp) fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } /* SIGN [--detached] Sign the data set with the INPUT command and write it to the sink set by OUTPUT. With "--detached" specified, a detached signature is created (surprise). */ static int cmd_sign (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; FILE *out_fp; int detached; int rc; inp_fd = assuan_get_input_fd (ctx); if (inp_fd == -1) return set_error (No_Input, NULL); out_fd = assuan_get_output_fd (ctx); if (out_fd == -1) return set_error (No_Output, NULL); detached = has_option (line, "--detached"); out_fp = fdopen ( dup(out_fd), "w"); if (!out_fp) return set_error (General_Error, "fdopen() failed"); rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, inp_fd, detached, out_fp); fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } /* IMPORT Import the certificates read form the input-fd, return status message for each imported one. The import checks the validity of the certificate but not of the entire chain. It is possible to import expired certificates. */ static int cmd_import (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; int fd = assuan_get_input_fd (ctx); if (fd == -1) return set_error (No_Input, NULL); rc = gpgsm_import (assuan_get_pointer (ctx), fd); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } static int cmd_export (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int fd = assuan_get_output_fd (ctx); FILE *out_fp; char *p; STRLIST list, sl; if (fd == -1) return set_error (No_Output, NULL); /* break the line down into an STRLIST */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return ASSUAN_Out_Of_Core; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } out_fp = fdopen ( dup(fd), "w"); if (!out_fp) { free_strlist (list); return set_error (General_Error, "fdopen() failed"); } gpgsm_export (ctrl, list, out_fp); fclose (out_fp); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static int cmd_delkeys (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); char *p; STRLIST list, sl; int rc; /* break the line down into an STRLIST */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return ASSUAN_Out_Of_Core; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } rc = gpgsm_delete (ctrl, list); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } /* MESSAGE FD= Set the file descriptor to read a message which is used with detached signatures */ static int cmd_message (ASSUAN_CONTEXT ctx, char *line) { char *endp; int fd; CTRL ctrl = assuan_get_pointer (ctx); if (strncmp (line, "FD=", 3)) return set_error (Syntax_Error, "FD= expected"); line += 3; if (!digitp (line)) return set_error (Syntax_Error, "number required"); fd = strtoul (line, &endp, 10); if (*endp) return set_error (Syntax_Error, "garbage found"); if (fd == -1) return set_error (No_Input, NULL); ctrl->server_local->message_fd = fd; return 0; } static int do_listkeys (ASSUAN_CONTEXT ctx, char *line, int mode) { CTRL ctrl = assuan_get_pointer (ctx); FILE *fp = assuan_get_data_fp (ctx); char *p; STRLIST list, sl; unsigned int listmode; gpg_error_t err; if (!fp) return set_error (General_Error, "no data stream"); /* break the line down into an STRLIST */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return ASSUAN_Out_Of_Core; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } ctrl->with_colons = 1; listmode = mode; if (ctrl->server_local->list_internal) listmode |= (1<<6); if (ctrl->server_local->list_external) listmode |= (1<<7); err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); free_strlist (list); return map_to_assuan_status (err); } static int cmd_listkeys (ASSUAN_CONTEXT ctx, char *line) { return do_listkeys (ctx, line, 3); } static int cmd_listsecretkeys (ASSUAN_CONTEXT ctx, char *line) { return do_listkeys (ctx, line, 2); } /* GENKEY Read the parameters in native format from the input fd and write a certificate request to the output. */ static int cmd_genkey (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; FILE *out_fp; int rc; inp_fd = assuan_get_input_fd (ctx); if (inp_fd == -1) return set_error (No_Input, NULL); out_fd = assuan_get_output_fd (ctx); if (out_fd == -1) return set_error (No_Output, NULL); out_fp = fdopen ( dup(out_fd), "w"); if (!out_fp) return set_error (General_Error, "fdopen() failed"); rc = gpgsm_genkey (ctrl, inp_fd, out_fp); fclose (out_fp); /* close and reset the fds */ assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return map_to_assuan_status (rc); } /* 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[] = { { "RECIPIENT", cmd_recipient }, { "SIGNER", cmd_signer }, { "ENCRYPT", cmd_encrypt }, { "DECRYPT", cmd_decrypt }, { "VERIFY", cmd_verify }, { "SIGN", cmd_sign }, { "IMPORT", cmd_import }, { "EXPORT", cmd_export }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "MESSAGE", cmd_message }, { "LISTKEYS", cmd_listkeys }, { "LISTSECRETKEYS",cmd_listsecretkeys }, { "GENKEY", cmd_genkey }, { "DELKEYS", cmd_delkeys }, { 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; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ void gpgsm_server (certlist_t default_recplist) { int rc; int filedes[2]; ASSUAN_CONTEXT ctx; struct server_control_s ctrl; static const char hello[] = ("GNU Privacy Guard's S/M server " VERSION " ready"); memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); /* For now we use a simple pipe based server so that we can work from scripts. We will later add options to run as a daemon and wait for requests on a Unix domain socket */ filedes[0] = 0; filedes[1] = 1; rc = assuan_init_pipe_server (&ctx, filedes); if (rc) { log_error ("failed to initialize the server: %s\n", assuan_strerror(rc)); gpgsm_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to the register commands with Assuan: %s\n", assuan_strerror(rc)); gpgsm_exit (2); } if (opt.verbose || opt.debug) { char *tmp = NULL; const char *s1 = getenv ("GPG_AGENT_INFO"); const char *s2 = getenv ("DIRMNGR_INFO"); if (asprintf (&tmp, "Home: %s\n" "Config: %s\n" "AgentInfo: %s\n" "DirmngrInfo: %s\n" "%s", opt.homedir, opt.config_filename, s1?s1:"[not set]", s2?s2:"[not set]", hello) > 0) { assuan_set_hello_line (ctx, tmp); free (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_input_notify (ctx, input_notify); assuan_register_output_notify (ctx, output_notify); assuan_register_option_handler (ctx, option_handler); 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->list_internal = 1; ctrl.server_local->list_external = 0; ctrl.server_local->default_recplist = default_recplist; 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; } } gpgsm_release_certlist (ctrl.server_local->recplist); ctrl.server_local->recplist = NULL; gpgsm_release_certlist (ctrl.server_local->signerlist); ctrl.server_local->signerlist = NULL; assuan_deinit_server (ctx); } static const char * get_status_string ( int no ) { const char *s; switch (no) { case STATUS_ENTER : s = "ENTER"; break; case STATUS_LEAVE : s = "LEAVE"; break; case STATUS_ABORT : s = "ABORT"; break; case STATUS_NEWSIG : s = "NEWSIG"; break; case STATUS_GOODSIG: s = "GOODSIG"; break; case STATUS_SIGEXPIRED: s = "SIGEXPIRED"; break; case STATUS_KEYREVOKED: s = "KEYREVOKED"; break; case STATUS_BADSIG : s = "BADSIG"; break; case STATUS_ERRSIG : s = "ERRSIG"; break; case STATUS_BADARMOR : s = "BADARMOR"; break; case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break; case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break; case STATUS_TRUST_NEVER : s = "TRUST_NEVER"; break; case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break; case STATUS_TRUST_FULLY : s = "TRUST_FULLY"; break; case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break; case STATUS_GET_BOOL : s = "GET_BOOL"; break; case STATUS_GET_LINE : s = "GET_LINE"; break; case STATUS_GET_HIDDEN : s = "GET_HIDDEN"; break; case STATUS_GOT_IT : s = "GOT_IT"; break; case STATUS_SHM_INFO : s = "SHM_INFO"; break; case STATUS_SHM_GET : s = "SHM_GET"; break; case STATUS_SHM_GET_BOOL : s = "SHM_GET_BOOL"; break; case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break; case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break; case STATUS_VALIDSIG : s = "VALIDSIG"; break; case STATUS_SIG_ID : s = "SIG_ID"; break; case STATUS_ENC_TO : s = "ENC_TO"; break; case STATUS_NODATA : s = "NODATA"; break; case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break; case STATUS_NO_PUBKEY : s = "NO_PUBKEY"; break; case STATUS_NO_SECKEY : s = "NO_SECKEY"; break; case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break; case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break; case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break; case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break; case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break; case STATUS_GOODMDC : s = "GOODMDC"; break; case STATUS_BADMDC : s = "BADMDC"; break; case STATUS_ERRMDC : s = "ERRMDC"; break; case STATUS_IMPORTED : s = "IMPORTED"; break; case STATUS_IMPORT_OK : s = "IMPORT_OK"; break; case STATUS_IMPORT_RES : s = "IMPORT_RES"; break; case STATUS_FILE_START : s = "FILE_START"; break; case STATUS_FILE_DONE : s = "FILE_DONE"; break; case STATUS_FILE_ERROR : s = "FILE_ERROR"; break; case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break; case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break; case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break; case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break; case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break; case STATUS_PROGRESS : s = "PROGRESS"; break; case STATUS_SIG_CREATED : s = "SIG_CREATED"; break; case STATUS_SESSION_KEY : s = "SESSION_KEY"; break; case STATUS_NOTATION_NAME : s = "NOTATION_NAME" ; break; case STATUS_NOTATION_DATA : s = "NOTATION_DATA" ; break; case STATUS_POLICY_URL : s = "POLICY_URL" ; break; case STATUS_BEGIN_STREAM : s = "BEGIN_STREAM"; break; case STATUS_END_STREAM : s = "END_STREAM"; break; case STATUS_KEY_CREATED : s = "KEY_CREATED"; break; case STATUS_UNEXPECTED : s = "UNEXPECTED"; break; case STATUS_INV_RECP : s = "INV_RECP"; break; case STATUS_NO_RECP : s = "NO_RECP"; break; case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break; case STATUS_EXPSIG : s = "EXPSIG"; break; case STATUS_EXPKEYSIG : s = "EXPKEYSIG"; break; case STATUS_TRUNCATED : s = "TRUNCATED"; break; case STATUS_ERROR : s = "ERROR"; break; case STATUS_IMPORT_PROBLEM : s = "IMPORT_PROBLEM"; break; default: s = "?"; break; } return s; } gpg_error_t gpgsm_status2 (CTRL ctrl, int no, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, no); if (ctrl->no_server && ctrl->status_fd == -1) ; /* No status wanted. */ else if (ctrl->no_server) { if (!statusfp) { if (ctrl->status_fd == 1) statusfp = stdout; else if (ctrl->status_fd == 2) statusfp = stderr; else statusfp = fdopen (ctrl->status_fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", ctrl->status_fd, strerror(errno)); } } fputs ("[GNUPG:] ", statusfp); fputs (get_status_string (no), statusfp); while ( (text = va_arg (arg_ptr, const char*) )) { putc ( ' ', statusfp ); for (; *text; text++) { if (*text == '\n') fputs ( "\\n", statusfp ); else if (*text == '\r') fputs ( "\\r", statusfp ); else putc ( *(const byte *)text, statusfp ); } } putc ('\n', statusfp); fflush (statusfp); } else { ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; p = buf; n = 0; while ( (text = va_arg (arg_ptr, const char *)) ) { if (n) { *p++ = ' '; n++; } for ( ; *text && n < DIM (buf)-2; n++) *p++ = *text++; } *p = 0; err = map_assuan_err (assuan_write_status (ctx, get_status_string (no), buf)); } va_end (arg_ptr); return err; } gpg_error_t gpgsm_status (CTRL ctrl, int no, const char *text) { return gpgsm_status2 (ctrl, no, text, NULL); } gpg_error_t gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text, gpg_err_code_t ec) { char buf[30]; sprintf (buf, "%u", (unsigned int)ec); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } #if 0 /* * Write a status line with a buffer using %XX escapes. If WRAP is > * 0 wrap the line after this length. If STRING is not NULL it will * be prepended to the buffer, no escaping is done for string. * A wrap of -1 forces spaces not to be encoded as %20. */ void write_status_text_and_buffer ( int no, const char *string, const char *buffer, size_t len, int wrap ) { const char *s, *text; int esc, first; int lower_limit = ' '; size_t n, count, dowrap; if( !statusfp ) return; /* not enabled */ if (wrap == -1) { lower_limit--; wrap = 0; } text = get_status_string (no); count = dowrap = first = 1; do { if (dowrap) { fprintf (statusfp, "[GNUPG:] %s ", text ); count = dowrap = 0; if (first && string) { fputs (string, statusfp); count += strlen (string); } first = 0; } for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) { if ( *s == '%' || *(const byte*)s <= lower_limit || *(const byte*)s == 127 ) esc = 1; if ( wrap && ++count > wrap ) { dowrap=1; break; } } if (esc) { s--; n++; } if (s != buffer) fwrite (buffer, s-buffer, 1, statusfp ); if ( esc ) { fprintf (statusfp, "%%%02X", *(const unsigned char*)s ); s++; n--; } buffer = s; len = n; if ( dowrap && len ) putc ( '\n', statusfp ); } while ( len ); putc ('\n',statusfp); fflush (statusfp); } #endif diff --git a/sm/sign.c b/sm/sign.c index 74bfe41aa..d656825e8 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -1,699 +1,700 @@ /* sign.c - Sign a message * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" static void hash_data (int fd, gcry_md_hd_t md) { FILE *fp; char buffer[4096]; int nread; fp = fdopen ( dup (fd), "rb"); if (!fp) { log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return; } do { nread = fread (buffer, 1, DIM(buffer), fp); gcry_md_write (md, buffer, nread); } while (nread); if (ferror (fp)) log_error ("read error on fd %d: %s\n", fd, strerror (errno)); fclose (fp); } static int hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer) { gpg_error_t err; FILE *fp; char buffer[4096]; int nread; int rc = 0; int any = 0; fp = fdopen ( dup (fd), "rb"); if (!fp) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return tmperr; } do { nread = fread (buffer, 1, DIM(buffer), fp); if (nread) { any = 1; gcry_md_write (md, buffer, nread); err = ksba_writer_write_octet_string (writer, buffer, nread, 0); if (err) { log_error ("write failed: %s\n", gpg_strerror (err)); rc = err; } } } while (nread && !rc); if (ferror (fp)) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error ("read error on fd %d: %s\n", fd, strerror (errno)); } fclose (fp); if (!any) { /* We can't allow to sign an empty message because it does not make much sense and more seriously, ksba-cms_build has already written the tag for data and now expects an octet string but an octet string of zeize 0 is illegal. */ log_error ("cannot sign an empty message\n"); rc = gpg_error (GPG_ERR_NO_DATA); } if (!rc) { err = ksba_writer_write_octet_string (writer, NULL, 0, 1); if (err) { log_error ("write failed: %s\n", gpg_strerror (err)); rc = err; } } return rc; } /* Get the default certificate which is defined as the first one our keyDB returns and has a secret key available. */ int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert) { KEYDB_HANDLE hd; ksba_cert_t cert = NULL; int rc; char *p; hd = keydb_new (0); if (!hd) return gpg_error (GPG_ERR_GENERAL); rc = keydb_search_first (hd); if (rc) { keydb_release (hd); return rc; } do { rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); keydb_release (hd); return rc; } p = gpgsm_get_keygrip_hexstring (cert); if (p) { if (!gpgsm_agent_havekey (ctrl, p)) { xfree (p); keydb_release (hd); *r_cert = cert; return 0; /* got it */ } xfree (p); } ksba_cert_release (cert); cert = NULL; } while (!(rc = keydb_search_next (hd))); if (rc && rc != -1) log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); ksba_cert_release (cert); keydb_release (hd); return rc; } static ksba_cert_t get_default_signer (ctrl_t ctrl) { KEYDB_SEARCH_DESC desc; ksba_cert_t cert = NULL; KEYDB_HANDLE kh = NULL; int rc; if (!opt.local_user) { rc = gpgsm_get_default_cert (ctrl, &cert); if (rc) { if (rc != -1) log_debug ("failed to find default certificate: %s\n", gpg_strerror (rc)); return NULL; } return cert; } rc = keydb_classify_name (opt.local_user, &desc); if (rc) { log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); return NULL; } kh = keydb_new (0); if (!kh) return NULL; rc = keydb_search (kh, &desc, 1); if (rc) { log_debug ("failed to find default certificate: rc=%d\n", rc); } else { rc = keydb_get_cert (kh, &cert); if (rc) { log_debug ("failed to get cert: rc=%d\n", rc); } } keydb_release (kh); return cert; } /* Depending on the options in CTRL add the certificate CERT as well as other certificate up in the chain to the Root-CA to the CMS object. */ static int add_certificate_list (CTRL ctrl, ksba_cms_t cms, ksba_cert_t cert) { gpg_error_t err; int rc = 0; ksba_cert_t next = NULL; int n; int not_root = 0; ksba_cert_ref (cert); n = ctrl->include_certs; log_debug ("adding certificates at level %d\n", n); if (n == -2) { not_root = 1; n = -1; } if (n < 0 || n > 50) n = 50; /* We better apply an upper bound */ /* First add my own certificate unless we don't want any certificate included at all. */ if (n) { if (not_root && gpgsm_is_root_cert (cert)) err = 0; else err = ksba_cms_add_cert (cms, cert); if (err) goto ksba_failure; if (n>0) n--; } /* Walk the chain to include all other certificates. Note that a -1 used for N makes sure that there is no limit and all certs get included. */ while ( n-- && !(rc = gpgsm_walk_cert_chain (cert, &next)) ) { if (not_root && gpgsm_is_root_cert (next)) err = 0; else err = ksba_cms_add_cert (cms, next); ksba_cert_release (cert); cert = next; next = NULL; if (err) goto ksba_failure; } ksba_cert_release (cert); return rc == -1? 0: rc; ksba_failure: ksba_cert_release (cert); log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err)); return err; } /* Perform a sign operation. Sign the data received on DATA-FD in embedded mode or in detached mode when DETACHED is true. Write the signature to OUT_FP. The keys used to sign are taken from SIGNERLIST or the default one will be used if the value of this argument is NULL. */ int gpgsm_sign (CTRL ctrl, CERTLIST signerlist, int data_fd, int detached, FILE *out_fp) { int i, rc; gpg_error_t err; Base64Context b64writer = NULL; ksba_writer_t writer; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; gcry_md_hd_t data_md = NULL; int signer; const char *algoid; int algo; ksba_isotime_t signed_at; CERTLIST cl; int release_signerlist = 0; kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } ctrl->pem_name = "SIGNED MESSAGE"; rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_cms_new (&cms); if (err) { rc = err; goto leave; } err = ksba_cms_set_reader_writer (cms, NULL, writer); if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* We are going to create signed data with data as encap. content */ err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA); if (!err) err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* If no list of signers is given, use a default one. */ if (!signerlist) { ksba_cert_t cert = get_default_signer (ctrl); if (!cert) { log_error ("no default signer found\n"); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } /* Although we don't check for ambigious specification we will check that the signer's certificate is is usable and valid. */ rc = gpgsm_cert_use_sign_p (cert); if (!rc) rc = gpgsm_validate_chain (ctrl, cert, NULL, 0, NULL, 0); if (rc) goto leave; /* That one is fine - create signerlist. */ signerlist = xtrycalloc (1, sizeof *signerlist); if (!signerlist) { rc = OUT_OF_CORE (errno); ksba_cert_release (cert); goto leave; } signerlist->cert = cert; release_signerlist = 1; } /* Gather certificates of signers and store them in the CMS object. */ for (cl=signerlist; cl; cl = cl->next) { rc = gpgsm_cert_use_sign_p (cl->cert); if (rc) goto leave; err = ksba_cms_add_signer (cms, cl->cert); if (err) { log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } rc = add_certificate_list (ctrl, cms, cl->cert); if (rc) { log_error ("failed to store list of certificates: %s\n", gpg_strerror(rc)); goto leave; } /* Set the hash algorithm we are going to use */ err = ksba_cms_add_digest_algo (cms, "1.3.14.3.2.26" /*SHA-1*/); if (err) { log_debug ("ksba_cms_add_digest_algo failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } /* Check whether one of the certificates is qualified. Note that we already validated the certificate and thus the user data stored flag must be available. */ for (cl=signerlist; cl; cl = cl->next) { size_t buflen; char buffer[1]; err = ksba_cert_get_user_data (cl->cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (err || !buflen) { log_error (_("checking for qualified certificate failed: %s\n"), gpg_strerror (err)); rc = err; goto leave; } if (*buffer) err = gpgsm_qualified_consent (ctrl, cl->cert); else err = gpgsm_not_qualified_warning (ctrl, cl->cert); if (err) { rc = err; goto leave; } } /* Prepare hashing (actually we are figuring out what we have set above)*/ rc = gcry_md_open (&data_md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_start_debug (data_md, "sign.data"); for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) { algo = gcry_md_map_name (algoid); if (!algo) { log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); if (algoid && ( !strcmp (algoid, "1.2.840.113549.1.1.2") ||!strcmp (algoid, "1.2.840.113549.2.2"))) log_info (_("(this is the MD2 algorithm)\n")); rc = gpg_error (GPG_ERR_BUG); goto leave; } gcry_md_enable (data_md, algo); } if (detached) { /* we hash the data right now so that we can store the message digest. ksba_cms_build() takes this as an flag that detached data is expected. */ unsigned char *digest; size_t digest_len; /* Fixme do this for all signers and get the algo to use from the signer's certificate - does not make mich sense, but we should do this consistent as we have already done it above. */ algo = GCRY_MD_SHA1; hash_data (data_fd, data_md); digest = gcry_md_read (data_md, algo); digest_len = gcry_md_get_algo_dlen (algo); if ( !digest || !digest_len) { log_error ("problem getting the hash of the data\n"); rc = gpg_error (GPG_ERR_BUG); goto leave; } for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } gnupg_get_isotime (signed_at); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { err = ksba_cms_set_signing_time (cms, signer, signed_at); if (err) { log_error ("ksba_cms_set_signing_time failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } /* We need to write at least a minimal list of our capabilities to try to convince some MUAs to use 3DEs and not the crippled RC2. Our list is: aes128-CBC des-EDE3-CBC */ err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL, 0); if (!err) err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0); if (err) { log_error ("ksba_cms_add_smime_capability failed: %s\n", gpg_strerror (err)); goto leave; } /* Main building loop. */ do { err = ksba_cms_build (cms, &stopreason); if (err) { log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA) { /* hash the data and store the message digest */ unsigned char *digest; size_t digest_len; assert (!detached); /* Fixme: get the algo to use from the signer's certificate - does not make much sense, but we should do this consistent as we have already done it above. Code is mostly duplicated above. */ algo = GCRY_MD_SHA1; rc = hash_and_copy_data (data_fd, data_md, writer); if (rc) goto leave; digest = gcry_md_read (data_md, algo); digest_len = gcry_md_get_algo_dlen (algo); if ( !digest || !digest_len) { log_error ("problem getting the hash of the data\n"); rc = gpg_error (GPG_ERR_BUG); goto leave; } for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } else if (stopreason == KSBA_SR_NEED_SIG) { /* calculate the signature for all signers */ gcry_md_hd_t md; algo = GCRY_MD_SHA1; rc = gcry_md_open (&md, algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_start_debug (md, "sign.attr"); ksba_cms_set_hash_function (cms, HASH_FNC, md); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { unsigned char *sigval = NULL; char *buf, *fpr; if (signer) gcry_md_reset (md); rc = ksba_cms_hash_signed_attrs (cms, signer); if (rc) { log_debug ("hashing signed attrs failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); goto leave; } rc = gpgsm_create_cms_signature (ctrl, cl->cert, md, algo, &sigval); if (rc) { gcry_md_close (md); goto leave; } err = ksba_cms_set_sig_val (cms, signer, sigval); xfree (sigval); if (err) { log_error ("failed to store the signature: %s\n", gpg_strerror (err)); rc = err; gcry_md_close (md); goto leave; } /* write a status message */ fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1); if (!fpr) { rc = gpg_error (GPG_ERR_ENOMEM); gcry_md_close (md); goto leave; } { int pkalgo = gpgsm_get_key_algo_info (cl->cert, NULL); rc = asprintf (&buf, "%c %d %d 00 %s %s", detached? 'D':'S', pkalgo, algo, signed_at, fpr); } xfree (fpr); if (rc < 0) { rc = gpg_error (GPG_ERR_ENOMEM); gcry_md_close (md); goto leave; } rc = 0; gpgsm_status (ctrl, STATUS_SIG_CREATED, buf); free (buf); /* yes, we must use the regular free() here */ } gcry_md_close (md); } } while (stopreason != KSBA_SR_READY); rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } log_info ("signature created\n"); leave: if (rc) log_error ("error creating signature: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc) ); if (release_signerlist) gpgsm_release_certlist (signerlist); ksba_cms_release (cms); gpgsm_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); return rc; } diff --git a/sm/verify.c b/sm/verify.c index f37cf4a75..4e6574078 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -1,546 +1,547 @@ /* verify.c - Verify a messages signature * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" static char * strtimestamp_r (ksba_isotime_t atime) { char *buffer = xmalloc (15); if (!atime || !*atime) strcpy (buffer, "none"); else sprintf (buffer, "%.4s-%.2s-%.2s", atime, atime+4, atime+6); return buffer; } /* Hash the data for a detached signature */ static void hash_data (int fd, gcry_md_hd_t md) { FILE *fp; char buffer[4096]; int nread; fp = fdopen ( dup (fd), "rb"); if (!fp) { log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return; } do { nread = fread (buffer, 1, DIM(buffer), fp); gcry_md_write (md, buffer, nread); } while (nread); if (ferror (fp)) log_error ("read error on fd %d: %s\n", fd, strerror (errno)); fclose (fp); } /* Perform a verify operation. To verify detached signatures, data_fd must be different than -1. With OUT_FP given and a non-detached signature, the signed material is written to that stream. */ int gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) { int i, rc; Base64Context b64reader = NULL; Base64Context b64writer = NULL; ksba_reader_t reader; ksba_writer_t writer = NULL; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; ksba_cert_t cert; KEYDB_HANDLE kh; gcry_md_hd_t data_md = NULL; int signer; const char *algoid; int algo; int is_detached; FILE *fp = NULL; char *p; kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } fp = fdopen ( dup (in_fd), "rb"); if (!fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gpgsm_create_reader (&b64reader, ctrl, fp, 0, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } if (out_fp) { rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } } rc = ksba_cms_new (&cms); if (rc) goto leave; rc = ksba_cms_set_reader_writer (cms, reader, writer); if (rc) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (rc)); goto leave; } rc = gcry_md_open (&data_md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_start_debug (data_md, "vrfy.data"); is_detached = 0; do { rc = ksba_cms_parse (cms, &stopreason); if (rc) { log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } if (stopreason == KSBA_SR_NEED_HASH) { is_detached = 1; if (opt.verbose) log_info ("detached signature\n"); } if (stopreason == KSBA_SR_NEED_HASH || stopreason == KSBA_SR_BEGIN_DATA) { /* We are now able to enable the hash algorithms */ for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) { algo = gcry_md_map_name (algoid); if (!algo) { log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); if (algoid && ( !strcmp (algoid, "1.2.840.113549.1.1.2") ||!strcmp (algoid, "1.2.840.113549.2.2"))) log_info (_("(this is the MD2 algorithm)\n")); } else gcry_md_enable (data_md, algo); } if (is_detached) { if (data_fd == -1) log_info ("detached signature w/o data " "- assuming certs-only\n"); else hash_data (data_fd, data_md); } else { ksba_cms_set_hash_function (cms, HASH_FNC, data_md); } } else if (stopreason == KSBA_SR_END_DATA) { /* The data bas been hashed */ } } while (stopreason != KSBA_SR_READY); if (b64writer) { rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } } if (data_fd != -1 && !is_detached) { log_error ("data given for a non-detached signature\n"); rc = gpg_error (GPG_ERR_CONFLICT); goto leave; } for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) { /* Fixme: it might be better to check the validity of the certificate first before entering it into the DB. This way we would avoid cluttering the DB with invalid certificates. */ keydb_store_cert (cert, 0, NULL); ksba_cert_release (cert); } cert = NULL; for (signer=0; ; signer++) { char *issuer = NULL; ksba_sexp_t sigval = NULL; ksba_isotime_t sigtime, keyexptime; ksba_sexp_t serial; char *msgdigest = NULL; size_t msgdigestlen; char *ctattr; rc = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); if (!signer && gpg_err_code (rc) == GPG_ERR_NO_DATA && data_fd == -1 && is_detached) { log_info ("certs-only message accepted\n"); rc = 0; break; } if (rc) { if (signer && rc == -1) rc = 0; break; } gpgsm_status (ctrl, STATUS_NEWSIG, NULL); if (DBG_X509) { log_debug ("signer %d - issuer: `%s'\n", signer, issuer? issuer:"[NONE]"); log_debug ("signer %d - serial: ", signer); gpgsm_dump_serial (serial); log_printf ("\n"); } rc = ksba_cms_get_signing_time (cms, signer, sigtime); if (gpg_err_code (rc) == GPG_ERR_NO_DATA) *sigtime = 0; else if (rc) { log_error ("error getting signing time: %s\n", gpg_strerror (rc)); *sigtime = 0; /* (we can't encode an error in the time string.) */ } rc = ksba_cms_get_message_digest (cms, signer, &msgdigest, &msgdigestlen); if (!rc) { size_t is_enabled; algoid = ksba_cms_get_digest_algo (cms, signer); algo = gcry_md_map_name (algoid); if (DBG_X509) log_debug ("signer %d - digest algo: %d\n", signer, algo); is_enabled = sizeof algo; if ( gcry_md_info (data_md, GCRYCTL_IS_ALGO_ENABLED, &algo, &is_enabled) || !is_enabled) { log_error ("digest algo %d has not been enabled\n", algo); goto next_signer; } } else if (gpg_err_code (rc) == GPG_ERR_NO_DATA) { assert (!msgdigest); rc = 0; algoid = NULL; algo = 0; } else /* real error */ break; rc = ksba_cms_get_sigattr_oids (cms, signer, "1.2.840.113549.1.9.3", &ctattr); if (!rc) { const char *s; if (DBG_X509) log_debug ("signer %d - content-type attribute: %s", signer, ctattr); s = ksba_cms_get_content_oid (cms, 1); if (!s || strcmp (ctattr, s)) { log_error ("content-type attribute does not match " "actual content-type\n"); ksba_free (ctattr); ctattr = NULL; goto next_signer; } ksba_free (ctattr); ctattr = NULL; } else if (rc != -1) { log_error ("error getting content-type attribute: %s\n", gpg_strerror (rc)); goto next_signer; } rc = 0; sigval = ksba_cms_get_sig_val (cms, signer); if (!sigval) { log_error ("no signature value available\n"); goto next_signer; } if (DBG_X509) log_debug ("signer %d - signature available", signer); /* Find the certificate of the signer */ keydb_search_reset (kh); rc = keydb_search_issuer_sn (kh, issuer, serial); if (rc) { if (rc == -1) { log_error ("certificate not found\n"); rc = gpg_error (GPG_ERR_NO_PUBKEY); } else log_error ("failed to find the certificate: %s\n", gpg_strerror(rc)); { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey", numbuf, NULL); } /* fixme: we might want to append the issuer and serial using our standard notation */ goto next_signer; } rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("failed to get cert: %s\n", gpg_strerror (rc)); goto next_signer; } log_info (_("Signature made ")); if (*sigtime) gpgsm_dump_time (sigtime); else log_printf (_("[date not given]")); log_printf (_(" using certificate ID %08lX\n"), gpgsm_get_short_fingerprint (cert)); if (msgdigest) { /* Signed attributes are available. */ gcry_md_hd_t md; unsigned char *s; /* check that the message digest in the signed attributes matches the one we calculated on the data */ s = gcry_md_read (data_md, algo); if ( !s || !msgdigestlen || gcry_md_get_algo_dlen (algo) != msgdigestlen || !s || memcmp (s, msgdigest, msgdigestlen) ) { char *fpr; log_error ("invalid signature: message digest attribute " "does not match calculated one\n"); fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); gpgsm_status (ctrl, STATUS_BADSIG, fpr); xfree (fpr); goto next_signer; } rc = gcry_md_open (&md, algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto next_signer; } if (DBG_HASHING) gcry_md_start_debug (md, "vrfy.attr"); ksba_cms_set_hash_function (cms, HASH_FNC, md); rc = ksba_cms_hash_signed_attrs (cms, signer); if (rc) { log_error ("hashing signed attrs failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); goto next_signer; } rc = gpgsm_check_cms_signature (cert, sigval, md, algo); gcry_md_close (md); } else { rc = gpgsm_check_cms_signature (cert, sigval, data_md, algo); } if (rc) { char *fpr; log_error ("invalid signature: %s\n", gpg_strerror (rc)); fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); gpgsm_status (ctrl, STATUS_BADSIG, fpr); xfree (fpr); goto next_signer; } rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/ if (rc) { gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage", gpg_err_code (rc)); rc = 0; } if (DBG_X509) log_debug ("signature okay - checking certs\n"); rc = gpgsm_validate_chain (ctrl, cert, keyexptime, 0, NULL, 0); if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) { gpgsm_status (ctrl, STATUS_EXPKEYSIG, NULL); rc = 0; } else gpgsm_status (ctrl, STATUS_GOODSIG, NULL); { char *buf, *fpr, *tstr; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); tstr = strtimestamp_r (sigtime); buf = xmalloc ( strlen(fpr) + strlen (tstr) + 120); sprintf (buf, "%s %s %s %s", fpr, tstr, *sigtime? sigtime : "0", *keyexptime? keyexptime : "0" ); xfree (tstr); xfree (fpr); gpgsm_status (ctrl, STATUS_VALIDSIG, buf); xfree (buf); } if (rc) /* of validate_chain */ { log_error ("invalid certification chain: %s\n", gpg_strerror (rc)); if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN || gpg_err_code (rc) == GPG_ERR_BAD_CERT || gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT || gpg_err_code (rc) == GPG_ERR_CERT_REVOKED) gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL, gpg_err_code (rc)); else gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL, gpg_err_code (rc)); goto next_signer; } for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) { log_info (!i? _("Good signature from") : _(" aka")); log_printf (" \""); gpgsm_print_name (log_get_stream (), p); log_printf ("\"\n"); ksba_free (p); } gpgsm_status (ctrl, STATUS_TRUST_FULLY, NULL); next_signer: rc = 0; xfree (issuer); xfree (serial); xfree (sigval); xfree (msgdigest); ksba_cert_release (cert); cert = NULL; } rc = 0; leave: ksba_cms_release (cms); gpgsm_destroy_reader (b64reader); gpgsm_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); if (fp) fclose (fp); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc ); gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", numbuf, NULL); } return rc; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 5264b8859..38b64c6ea 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,69 +1,70 @@ # Makefile.am -tests makefile for libxtime # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in #if RUN_PKITS_TESTS #pkits = pkits #else #pkits = #endif # #SUBDIRS = . ${pkits} GPGSM = ../sm/gpgsm TESTS_ENVIRONMENT = GNUPGHOME=`pwd` GPG_AGENT_INFO= LC_ALL=C GPGSM=$(GPGSM) \ $(srcdir)/runtest testscripts = sm-sign+verify sm-verify EXTRA_DIST = runtest inittests $(testscripts) \ text-1.txt text-2.txt text-3.txt \ text-1.osig.pem text-1.dsig.pem text-1.osig-bad.pem \ text-2.osig.pem text-2.osig-bad.pem \ samplekeys/32100C27173EF6E9C4E9A25D3D69F86D37A4F939.key \ samplekeys/cert_g10code_pete1.pem \ samplekeys/cert_g10code_test1.pem \ samplekeys/cert_g10code_theo1.pem TESTS = $(testscripts) CLEANFILES = inittests.stamp x y y z out err \ *.lock .\#lk* DISTCLEANFILES = pubring.kbx~ random_seed noinst_PROGRAMS = asschk asschk_SOURCES = asschk.c all-local: inittests.stamp clean-local: srcdir=$(srcdir) $(TESTS_ENVIRONMENT) $(srcdir)/inittests --clean inittests.stamp: inittests srcdir=$(srcdir) $(TESTS_ENVIRONMENT) $(srcdir)/inittests echo timestamp >./inittests.stamp diff --git a/tests/asschk.c b/tests/asschk.c index 6a05fe1a8..40b95ba7d 100644 --- a/tests/asschk.c +++ b/tests/asschk.c @@ -1,1071 +1,1072 @@ /* asschk.c - Assuan Server Checker * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This is a simple stand-alone Assuan server test program. We don't want to use the assuan library because we don't want to hide errors in that library. The script language is line based. Empty lines or lines containing only white spaces are ignored, line with a hash sign as first non white space character are treated as comments. A simple macro mechanism is implemnted. Macros are expanded before a line is processed but after comment processing. Macros are only expanded once and non existing macros expand to the empty string. A macro is dereferenced by prefixing its name with a dollar sign; the end of the name is currently indicated by a white space, a dollar sign or a slash. To use a dollor sign verbatim, double it. A macro is assigned by prefixing a statement with the macro name and an equal sign. The value is assigned verbatim if it does not resemble a command, otherwise the return value of the command will get assigned. The command "let" may be used to assign values unambigiously and it should be used if the value starts with a letter. Conditions are not yes implemented except for a simple evaluation which yields false for an empty string or the string "0". The result may be negated by prefixing with a '!'. The general syntax of a command is: [ =] [] If NAME is not specifed but the statement returns a value it is assigned to the name "?" so that it can be referenced using "$?". The following commands are implemented: let Return VALUE. echo Print VALUE. openfile Open file FILENAME for read access and return the file descriptor. createfile Create file FILENAME, open for write access and return the file descriptor. pipeserver Connect to the Assuan server PROGRAM. send Send LINE to the server. expect-ok Expect an OK response from the server. Status and data out put is ignored. expect-err Expect an ERR response from the server. Status and data out put is ignored. count-status Initialize the assigned variable to 0 and assign it as an counter for status code CODE. This command must be called with an assignment. quit Terminate the process. quit-if Terminate the process if CONDITION evaluates to true. fail-if Terminate the process with an exit code of 1 if CONDITION evaluates to true. cmpfiles Returns true when the content of the files FIRST and SECOND match. getenv Return the value of the environment variable NAME. */ #include #include #include #include #include #include #include #include #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) # define ATTR_PRINTF(f,a) __attribute__ ((format (printf,f,a))) #else # define ATTR_PRINTF(f,a) #endif #if __STDC_VERSION__ < 199901L # if __GNUC__ >= 2 # define __func__ __FUNCTION__ # else /* Let's try our luck here. Some systems may provide __func__ without providing __STDC_VERSION__ 199901L. */ # if 0 # define __func__ "" # endif # endif #endif #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define MAX_LINELEN 2048 typedef enum { LINE_OK = 0, LINE_ERR, LINE_STAT, LINE_DATA, LINE_END, } LINETYPE; typedef enum { VARTYPE_SIMPLE = 0, VARTYPE_FD, VARTYPE_COUNTER } VARTYPE; struct variable_s { struct variable_s *next; VARTYPE type; unsigned int count; char *value; char name[1]; }; typedef struct variable_s *VARIABLE; static void die (const char *format, ...) ATTR_PRINTF(1,2); /* Name of this program to be printed in error messages. */ static const char *invocation_name; /* Talk a bit about what is going on. */ static int opt_verbose; /* Option to ignore the echo command. */ static int opt_no_echo; /* File descriptors used to communicate with the current server. */ static int server_send_fd = -1; static int server_recv_fd = -1; /* The Assuan protocol limits the line length to 1024, so we can safely use a (larger) buffer. The buffer is filled using the read_assuan(). */ static char recv_line[MAX_LINELEN]; /* Tell the status of the current line. */ static LINETYPE recv_type; /* This is our variable storage. */ static VARIABLE variable_list; static void die (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", invocation_name); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); exit (1); } #define die(format, args...) (die) ("%s: " format, __func__ , ##args) static void err (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", invocation_name); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); } static void * xmalloc (size_t n) { void *p = malloc (n); if (!p) die ("out of core"); return p; } static void * xcalloc (size_t n, size_t m) { void *p = calloc (n, m); if (!p) die ("out of core"); return p; } static char * xstrdup (const char *s) { char *p = xmalloc (strlen (s)+1); strcpy (p, s); return p; } /* Write LENGTH bytes from BUFFER to FD. */ static int writen (int fd, const char *buffer, size_t length) { while (length) { int nwritten = write (fd, buffer, length); if (nwritten < 0) { if (errno == EINTR) continue; return -1; /* write error */ } length -= nwritten; buffer += nwritten; } return 0; /* okay */ } /* Assuan specific stuff. */ /* Read a line from FD, store it in the global recv_line, analyze the type and store that in recv_type. The function terminates on a communication error. Returns a pointer into the inputline to the first byte of the arguments. The parsing is very strict to match excalty what we want to send. */ static char * read_assuan (int fd) { static char pending[MAX_LINELEN]; static size_t pending_len; size_t nleft = sizeof recv_line; char *buf = recv_line; char *p; while (nleft > 0) { int n; if (pending_len) { if (pending_len >= nleft) die ("received line too large"); memcpy (buf, pending, pending_len); n = pending_len; pending_len = 0; } else n = read (fd, buf, nleft); if (opt_verbose) { int i; printf ("%s: read \"", __FUNCTION__); for (i = 0; i < n; i ++) putc (buf[i], stdout); printf ("\"\n"); } if (n < 0) { if (errno == EINTR) continue; die ("reading fd %d failed: %s", fd, strerror (errno)); } else if (!n) die ("received incomplete line on fd %d", fd); p = buf; nleft -= n; buf += n; for (; n && *p != '\n'; n--, p++) ; if (n) { if (n>1) { n--; memcpy (pending, p + 1, n); pending_len = n; } *p = '\0'; break; } } if (!nleft) die ("received line too large"); p = recv_line; if (p[0] == 'O' && p[1] == 'K' && (p[2] == ' ' || !p[2])) { recv_type = LINE_OK; p += 3; } else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'R' && (p[3] == ' ' || !p[3])) { recv_type = LINE_ERR; p += 4; } else if (p[0] == 'S' && (p[1] == ' ' || !p[1])) { recv_type = LINE_STAT; p += 2; } else if (p[0] == 'D' && p[1] == ' ') { recv_type = LINE_DATA; p += 2; } else if (p[0] == 'E' && p[1] == 'N' && p[2] == 'D' && !p[3]) { recv_type = LINE_END; p += 3; } else die ("invalid line type (%.5s)", p); return p; } /* Write LINE to the server using FD. It is expected that the line contains the terminating linefeed as last character. */ static void write_assuan (int fd, const char *line) { char buffer[1026]; size_t n = strlen (line); if (n > 1024) die ("line too long for Assuan protocol"); strcpy (buffer, line); if (!n || buffer[n-1] != '\n') buffer[n++] = '\n'; if (writen (fd, buffer, n)) die ("sending line (\"%s\") to %d failed: %s", buffer, fd, strerror (errno)); } /* Start the server with path PGMNAME and connect its stdout and strerr to a newly created pipes; the file descriptors are then store in the gloabl variables SERVER_SEND_FD and SERVER_RECV_FD. The initial handcheck is performed.*/ static void start_server (const char *pgmname) { int rp[2]; int wp[2]; pid_t pid; if (pipe (rp) < 0) die ("pipe creation failed: %s", strerror (errno)); if (pipe (wp) < 0) die ("pipe creation failed: %s", strerror (errno)); fflush (stdout); fflush (stderr); pid = fork (); if (pid < 0) die ("fork failed"); if (!pid) { const char *arg0; arg0 = strrchr (pgmname, '/'); if (arg0) arg0++; else arg0 = pgmname; if (wp[0] != STDIN_FILENO) { if (dup2 (wp[0], STDIN_FILENO) == -1) die ("dup2 failed in child: %s", strerror (errno)); close (wp[0]); } if (rp[1] != STDOUT_FILENO) { if (dup2 (rp[1], STDOUT_FILENO) == -1) die ("dup2 failed in child: %s", strerror (errno)); close (rp[1]); } if (!opt_verbose) { int fd = open ("/dev/null", O_WRONLY); if (fd == -1) die ("can't open `/dev/null': %s", strerror (errno)); if (dup2 (fd, STDERR_FILENO) == -1) die ("dup2 failed in child: %s", strerror (errno)); close (fd); } close (wp[1]); close (rp[0]); execl (pgmname, arg0, "--server", NULL); die ("exec failed for `%s': %s", pgmname, strerror (errno)); } close (wp[0]); close (rp[1]); server_send_fd = wp[1]; server_recv_fd = rp[0]; read_assuan (server_recv_fd); if (recv_type != LINE_OK) die ("no greating message"); } /* Script intepreter. */ static void unset_var (const char *name) { VARIABLE var; for (var=variable_list; var && strcmp (var->name, name); var = var->next) ; if (!var) return; /* fprintf (stderr, "unsetting `%s'\n", name); */ if (var->type == VARTYPE_FD && var->value) { int fd; fd = atoi (var->value); if (fd != -1 && fd != 0 && fd != 1 && fd != 2) close (fd); } free (var->value); var->value = NULL; var->type = 0; var->count = 0; } static void set_type_var (const char *name, const char *value, VARTYPE type) { VARIABLE var; if (!name) name = "?"; for (var=variable_list; var && strcmp (var->name, name); var = var->next) ; if (!var) { var = xcalloc (1, sizeof *var + strlen (name)); strcpy (var->name, name); var->next = variable_list; variable_list = var; } else free (var->value); if (var->type == VARTYPE_FD && var->value) { int fd; fd = atoi (var->value); if (fd != -1 && fd != 0 && fd != 1 && fd != 2) close (fd); } var->type = type; var->count = 0; if (var->type == VARTYPE_COUNTER) { /* We need some extra sapce as scratch area for get_var. */ var->value = xmalloc (strlen (value) + 1 + 20); strcpy (var->value, value); } else var->value = xstrdup (value); } static void set_var (const char *name, const char *value) { set_type_var (name, value, 0); } static const char * get_var (const char *name) { VARIABLE var; for (var=variable_list; var && strcmp (var->name, name); var = var->next) ; if (!var) return NULL; if (var->type == VARTYPE_COUNTER && var->value) { /* Use the scratch space allocated by set_var. */ char *p = var->value + strlen(var->value)+1; sprintf (p, "%u", var->count); return p; } else return var->value; } /* Incremente all counter type variables with NAME in their VALUE. */ static void inc_counter (const char *name) { VARIABLE var; if (!*name) return; for (var=variable_list; var; var = var->next) { if (var->type == VARTYPE_COUNTER && var->value && !strcmp (var->value, name)) var->count++; } } /* Expand variables in LINE and return a new allocated buffer if required. The function might modify LINE if the expanded version fits into it. */ static char * expand_line (char *buffer) { char *line = buffer; char *p, *pend; const char *value; size_t valuelen, n; char *result = NULL; while (*line) { p = strchr (line, '$'); if (!p) return result; /* nothing more to expand */ if (p[1] == '$') /* quoted */ { memmove (p, p+1, strlen (p+1)+1); line = p + 1; continue; } for (pend=p+1; *pend && !spacep (pend) && *pend != '$' && *pend != '/'; pend++) ; if (*pend) { int save = *pend; *pend = 0; value = get_var (p+1); *pend = save; } else value = get_var (p+1); if (!value) value = ""; valuelen = strlen (value); if (valuelen <= pend - p) { memcpy (p, value, valuelen); p += valuelen; n = pend - p; if (n) memmove (p, p+n, strlen (p+n)+1); line = p; } else { char *src = result? result : buffer; char *dst; dst = xmalloc (strlen (src) + valuelen + 1); n = p - src; memcpy (dst, src, n); memcpy (dst + n, value, valuelen); n += valuelen; strcpy (dst + n, pend); line = dst + n; free (result); result = dst; } } return result; } /* Evaluate COND and return the result. */ static int eval_boolean (const char *cond) { int true = 1; for ( ; *cond == '!'; cond++) true = !true; if (!*cond || (*cond == '0' && !cond[1])) return !true; return true; } static void cmd_let (const char *assign_to, char *arg) { set_var (assign_to, arg); } static void cmd_echo (const char *assign_to, char *arg) { if (!opt_no_echo) printf ("%s\n", arg); } static void cmd_send (const char *assign_to, char *arg) { if (opt_verbose) fprintf (stderr, "sending `%s'\n", arg); write_assuan (server_send_fd, arg); } static void handle_status_line (char *arg) { char *p; for (p=arg; *p && !spacep (p); p++) ; if (*p) { int save = *p; *p = 0; inc_counter (arg); *p = save; } else inc_counter (arg); } static void cmd_expect_ok (const char *assign_to, char *arg) { if (opt_verbose) fprintf (stderr, "expecting OK\n"); do { char *p = read_assuan (server_recv_fd); if (opt_verbose > 1) fprintf (stderr, "got line `%s'\n", recv_line); if (recv_type == LINE_STAT) handle_status_line (p); } while (recv_type != LINE_OK && recv_type != LINE_ERR); if (recv_type != LINE_OK) die ("expected OK but got `%s'", recv_line); } static void cmd_expect_err (const char *assign_to, char *arg) { if (opt_verbose) fprintf (stderr, "expecting ERR\n"); do { char *p = read_assuan (server_recv_fd); if (opt_verbose > 1) fprintf (stderr, "got line `%s'\n", recv_line); if (recv_type == LINE_STAT) handle_status_line (p); } while (recv_type != LINE_OK && recv_type != LINE_ERR); if (recv_type != LINE_ERR) die ("expected ERR but got `%s'", recv_line); } static void cmd_count_status (const char *assign_to, char *arg) { char *p; if (!*assign_to || !*arg) die ("syntax error: count-status requires an argument and a variable"); for (p=arg; *p && !spacep (p); p++) ; if (*p) { for (*p++ = 0; spacep (p); p++) ; if (*p) die ("cmpfiles: syntax error"); } set_type_var (assign_to, arg, VARTYPE_COUNTER); } static void cmd_openfile (const char *assign_to, char *arg) { int fd; char numbuf[20]; do fd = open (arg, O_RDONLY); while (fd == -1 && errno == EINTR); if (fd == -1) die ("error opening `%s': %s", arg, strerror (errno)); sprintf (numbuf, "%d", fd); set_type_var (assign_to, numbuf, VARTYPE_FD); } static void cmd_createfile (const char *assign_to, char *arg) { int fd; char numbuf[20]; do fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666); while (fd == -1 && errno == EINTR); if (fd == -1) die ("error creating `%s': %s", arg, strerror (errno)); sprintf (numbuf, "%d", fd); set_type_var (assign_to, numbuf, VARTYPE_FD); } static void cmd_pipeserver (const char *assign_to, char *arg) { if (!*arg) die ("syntax error: servername missing"); start_server (arg); } static void cmd_quit_if(const char *assign_to, char *arg) { if (eval_boolean (arg)) exit (0); } static void cmd_fail_if(const char *assign_to, char *arg) { if (eval_boolean (arg)) exit (1); } static void cmd_cmpfiles (const char *assign_to, char *arg) { char *p = arg; char *second; FILE *fp1, *fp2; char buffer1[2048]; /* note: both must be of equal size. */ char buffer2[2048]; size_t nread1, nread2; int rc = 0; set_var (assign_to, "0"); for (p=arg; *p && !spacep (p); p++) ; if (!*p) die ("cmpfiles: syntax error"); for (*p++ = 0; spacep (p); p++) ; second = p; for (; *p && !spacep (p); p++) ; if (*p) { for (*p++ = 0; spacep (p); p++) ; if (*p) die ("cmpfiles: syntax error"); } fp1 = fopen (arg, "rb"); if (!fp1) { err ("can't open `%s': %s", arg, strerror (errno)); return; } fp2 = fopen (second, "rb"); if (!fp2) { err ("can't open `%s': %s", second, strerror (errno)); fclose (fp1); return; } while ( (nread1 = fread (buffer1, 1, sizeof buffer1, fp1))) { if (ferror (fp1)) break; nread2 = fread (buffer2, 1, sizeof buffer2, fp2); if (ferror (fp2)) break; if (nread1 != nread2 || memcmp (buffer1, buffer2, nread1)) { rc = 1; break; } } if (feof (fp1) && feof (fp2) && !rc) { if (opt_verbose) err ("files match"); set_var (assign_to, "1"); } else if (!rc) err ("cmpfiles: read error: %s", strerror (errno)); else err ("cmpfiles: mismatch"); fclose (fp1); fclose (fp2); } static void cmd_getenv (const char *assign_to, char *arg) { const char *s; s = *arg? getenv (arg):""; set_var (assign_to, s? s:""); } /* Process the current script line LINE. */ static int interpreter (char *line) { static struct { const char *name; void (*fnc)(const char*, char*); } cmdtbl[] = { { "let" , cmd_let }, { "echo" , cmd_echo }, { "send" , cmd_send }, { "expect-ok" , cmd_expect_ok }, { "expect-err", cmd_expect_err }, { "count-status", cmd_count_status }, { "openfile" , cmd_openfile }, { "createfile", cmd_createfile }, { "pipeserver", cmd_pipeserver }, { "quit" , NULL }, { "quit-if" , cmd_quit_if }, { "fail-if" , cmd_fail_if }, { "cmpfiles" , cmd_cmpfiles }, { "getenv" , cmd_getenv }, { NULL } }; char *p, *save_p; int i, save_c; char *stmt = NULL; char *assign_to = NULL; char *must_free = NULL; for ( ;spacep (line); line++) ; if (!*line || *line == '#') return 0; /* empty or comment */ p = expand_line (line); if (p) { must_free = p; line = p; for ( ;spacep (line); line++) ; if (!*line || *line == '#') { free (must_free); return 0; /* empty or comment */ } } for (p=line; *p && !spacep (p) && *p != '='; p++) ; if (*p == '=') { *p = 0; assign_to = line; } else if (*p) { for (*p++ = 0; spacep (p); p++) ; if (*p == '=') assign_to = line; } if (!*line) die ("syntax error"); stmt = line; save_c = 0; save_p = NULL; if (assign_to) { /* this is an assignment */ for (p++; spacep (p); p++) ; if (!*p) { unset_var (assign_to); free (must_free); return 0; } stmt = p; for (; *p && !spacep (p); p++) ; if (*p) { save_p = p; save_c = *p; for (*p++ = 0; spacep (p); p++) ; } } for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++) ; if (!cmdtbl[i].name) { if (!assign_to) die ("invalid statement `%s'\n", stmt); if (save_p) *save_p = save_c; set_var (assign_to, stmt); free (must_free); return 0; } if (cmdtbl[i].fnc) cmdtbl[i].fnc (assign_to, p); free (must_free); return cmdtbl[i].fnc? 0:1; } int main (int argc, char **argv) { char buffer[2048]; char *p, *pend; if (!argc) invocation_name = "asschk"; else { invocation_name = *argv++; argc--; p = strrchr (invocation_name, '/'); if (p) invocation_name = p+1; } set_var ("?","1"); /* defaults to true */ for (; argc; argc--, argv++) { p = *argv; if (*p != '-') break; if (!strcmp (p, "--verbose")) opt_verbose++; else if (!strcmp (p, "--no-echo")) opt_no_echo++; else if (*p == '-' && p[1] == 'D') { p += 2; pend = strchr (p, '='); if (pend) { int tmp = *pend; *pend = 0; set_var (p, pend+1); *pend = tmp; } else set_var (p, "1"); } else if (*p == '-' && p[1] == '-' && !p[2]) { argc--; argv++; break; } else break; } if (argc) die ("usage: asschk [--verbose] {-D[=]}"); while (fgets (buffer, sizeof buffer, stdin)) { p = strchr (buffer,'\n'); if (!p) die ("incomplete script line"); *p = 0; if (interpreter (buffer)) break; fflush (stdout); } return 0; } diff --git a/tests/pkits/Makefile.am b/tests/pkits/Makefile.am index 41fdec497..d53d35a25 100644 --- a/tests/pkits/Makefile.am +++ b/tests/pkits/Makefile.am @@ -1,69 +1,70 @@ # Makefile.am - tests using NIST's PKITS # Copyright (C) 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in GPGSM = ../../sm/gpgsm TESTS_ENVIRONMENT = GNUPGHOME=`pwd` GPG_AGENT_INFO= LC_ALL=C GPGSM=$(GPGSM) \ LD_LIBRARY_PATH=$$(seen=0; \ for i in $(LDFLAGS) $(LIBGCRYPT_LIBS) $(PTH_LIBS); \ do \ if echo "$$i" | egrep '^-L' >/dev/null 2>&1; \ then \ if test $$seen = 0; \ then \ seen=1; \ else \ printf ":"; \ fi; \ printf "%s" "$${i}" | sed 's/^-L//'; \ fi; \ done; \ if test $$seen != 0 \ && test x$${LD_LIBRARY_PATH} != x; \ then \ printf ":"; \ fi; \ printf "%s" "$${LD_LIBRARY_PATH}") $(srcdir)/runtest testscripts = import-all-certs validate-all-certs EXTRA_DIST = PKITS_data.tar.bz2 inittests runtest $(testscripts) import-all-certs.data TESTS = $(testscripts) CLEANFILES = inittests.stamp x y y z out err *.lock .\#lk* *.log DISTCLEANFILES = pubring.kbx~ random_seed all-local: inittests.stamp clean-local: srcdir=$(srcdir) $(TESTS_ENVIRONMENT) $(srcdir)/inittests --clean inittests.stamp: inittests srcdir=$(srcdir) $(TESTS_ENVIRONMENT) $(srcdir)/inittests echo timestamp >./inittests.stamp diff --git a/tests/pkits/common.sh b/tests/pkits/common.sh index 5e773ea5d..09fb62bc8 100644 --- a/tests/pkits/common.sh +++ b/tests/pkits/common.sh @@ -1,135 +1,136 @@ #!/bin/sh # common.sh - common defs for all tests -*- sh -*- # Copyright (C) 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. # reset some environment variables because we do not want to test locals export LANG=C export LANGUAGE=C export LC_ALL=C [ "$VERBOSE" = yes ] && set -x [ -z "$srcdir" ] && srcdir="." [ -z "$top_srcdir" ] && top_srcdir=".." [ -z "$GPGSM" ] && GPGSM="../../sm/gpgsm" if [ "$GNUPGHOME" != "`pwd`" ]; then echo "inittests: please set GNUPGHOME to the tests/pkits directory" >&2 exit 1 fi if [ -n "$GPG_AGENT_INFO" ]; then echo "inittests: please unset GPG_AGENT_INFO" >&2 exit 1 fi #-------------------------------- #------ utility functions ------- #-------------------------------- echo_n_init=no echo_n () { if test "$echo_n_init" = "no"; then if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then echo_n_n= echo_n_c=' ' else echo_n_n='-n' echo_n_c= fi else echo_n_n= echo_n_c='\c' fi echo_n_init=yes fi echo $echo_n_n "${1}$echo_n_c" } fatal () { echo "$pgmname: fatal:" $* >&2 exit 1; } error () { echo "$pgmname:" $* >&2 exit 1 } info () { echo "$pgmname:" $* >&2 } info_n () { $echo_n "$pgmname:" $* >&2 } pass () { echo "PASS: " $* >&2 pass_count=`expr ${pass_count} + 1` } fail () { echo "FAIL: " $* >&2 fail_count=`expr ${fail_count} + 1` } unresolved () { echo "UNRESOLVED: " $* >&2 unresolved_count=`expr ${unresolved_count} + 1` } unsupported () { echo "UNSUPPORTED: " $* >&2 unsupported_count=`expr ${unsupported_count} + 1` } final_result () { [ $pass_count = 0 ] || info "$pass_count tests passed" [ $fail_count = 0 ] || info "$fail_count tests failed" [ $unresolved_count = 0 ] || info "$unresolved_count tests unresolved" [ $unsupported_count = 0 ] || info "$unsupported_count tests unsupported" if [ $fail_count = 0 ]; then info "all tests passed" else exit 1 fi } set -e pgmname=`basename $0` pass_count=0 fail_count=0 unresolved_count=0 unsupported_count=0 #trap cleanup SIGHUP SIGINT SIGQUIT exec 2> ${pgmname}.log : # end diff --git a/tests/pkits/import-all-certs b/tests/pkits/import-all-certs index d1af5fb03..2d70d06df 100755 --- a/tests/pkits/import-all-certs +++ b/tests/pkits/import-all-certs @@ -1,53 +1,54 @@ #!/bin/sh # Copyright (C) 2004 Free Software Foundation, Inc. -*- sh -*- # # 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. . ${srcdir:-.}/common.sh || exit 2 while read flag dummy name; do case $flag in \#*) continue;; esac [ -z "$flag" ] && continue; if ${GPGSM} -q --import certs/$name ; then if [ "$flag" = 'p' ]; then pass "importing certificate \`$name' succeeded" elif [ "$flag" = 'f' ]; then fail "importing certificate \`$name' succeeded" elif [ "$flag" = '?' ]; then unresolved "importing certificate \`$name' succeeded" elif [ "$flag" = 'u' ]; then unsupported "importing certificate \`$name' succeeded" else info "importing certificate \`$name' succeeded - (flag=$flag)" fi else if [ "$flag" = 'p' ]; then fail "importing certificate \`$name' failed" elif [ "$flag" = 'f' ]; then pass "importing certificate \`$name' failed" elif [ "$flag" = '?' ]; then unresolved "importing certificate \`$name' failed" elif [ "$flag" = 'u' ]; then unsupported "importing certificate \`$name' failed" else info "importing certificate \`$name' failed - (flag=$flag)" fi fi done < $srcdir/import-all-certs.data final_result diff --git a/tests/pkits/validate-all-certs b/tests/pkits/validate-all-certs index f482fdb51..08f72af71 100755 --- a/tests/pkits/validate-all-certs +++ b/tests/pkits/validate-all-certs @@ -1,55 +1,56 @@ #!/bin/sh # validate-all-certs -*- sh -*- # Copyright (C) 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. . ${srcdir:-.}/common.sh || exit 2 while read dummy flag name; do case $dummy in \#*) continue;; esac [ -z "$dummy" ] && continue; if ${GPGSM} -q --import --with-validation --disable-crl-checks \ certs/$name ; then if [ "$flag" = 'p' ]; then pass "validating certificate \`$name' succeeded" elif [ "$flag" = 'f' ]; then fail "validating certificate \`$name' succeeded" elif [ "$flag" = '?' ]; then unresolved "validating certificate \`$name' succeeded" elif [ "$flag" = 'u' ]; then unsupported "validating certificate \`$name' succeeded" else info "validating certificate \`$name' succeeded - (flag=$flag)" fi else if [ "$flag" = 'p' ]; then fail "validating certificate \`$name' failed" elif [ "$flag" = 'f' ]; then pass "validating certificate \`$name' failed" elif [ "$flag" = '?' ]; then unresolved "validating certificate \`$name' failed" elif [ "$flag" = 'u' ]; then unsupported "validating certificate \`$name' failed" else info "validating certificate \`$name' failed - (flag=$flag)" fi fi done < $srcdir/import-all-certs.data final_result diff --git a/tools/Makefile.am b/tools/Makefile.am index d9ef8812a..6b4767a79 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,72 +1,73 @@ # Makefile.am - Tools directory # Copyright (C) 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 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. EXTRA_DIST = Manifest watchgnupg.c \ addgnupghome gpgsm-gencert.sh AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common include $(top_srcdir)/am/cmacros.am AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) sbin_SCRIPTS = addgnupghome bin_SCRIPTS = gpgsm-gencert.sh if BUILD_SYMCRYPTRUN symcryptrun = symcryptrun else symcryptrun = endif bin_PROGRAMS = gpgconf gpg-connect-agent gpgkey2ssh ${symcryptrun} gpgparsemail if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg endif gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c no-libgcrypt.c # jnlib/common sucks in gpg-error, will they, nil they (some compilers # do not eliminate the supposed-to-be-unused-inline-functions). gpgconf_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \ ../gl/libgnu.a @LIBINTL@ $(GPG_ERROR_LIBS) gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h gpgparsemail_LDADD = symcryptrun_SOURCES = symcryptrun.c symcryptrun_LDADD = $(LIBUTIL_LIBS) ../jnlib/libjnlib.a \ ../common/libcommon.a ../gl/libgnu.a \ ../common/libsimple-pwquery.a $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) $(LIBINTL) watchgnupg_SOURCES = watchgnupg.c watchgnupg_LDADD = $(NETLIBS) gpg_connect_agent_SOURCES = gpg-connect-agent.c no-libgcrypt.c gpg_connect_agent_LDADD = ../jnlib/libjnlib.a \ ../common/libcommon.a ../gl/libgnu.a \ $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) gpgkey2ssh_SOURCES = gpgkey2ssh.c gpgkey2ssh_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) # common sucks in jnlib, via use of BUG() in an inline function, which # some compilers do not eliminate. gpgkey2ssh_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index c9a324fa8..90e321000 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -1,638 +1,639 @@ /* gpg-connect-agent.c - Tool to connect to the agent. * Copyright (C) 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include #include #include "i18n.h" #include "../common/util.h" #include "../common/asshelp.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oRawSocket = 'S', oNoVerbose = 500, oHomedir, oHex }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@\nOptions:\n ") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oHex, "hex", 0, N_("print data out hex encoded") }, { oRawSocket, "raw-socket", 2, N_("|NAME|connect to Assuan socket NAME")}, /* hidden options */ { oNoVerbose, "no-verbose", 0, "@"}, { oHomedir, "homedir", 2, "@" }, {0} }; /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ int quiet; /* Be extra quiet. */ const char *homedir; /* Configuration directory name */ int hex; /* Print data lines in hex format. */ const char *raw_socket; /* Name of socket to connect in raw mode. */ } opt; /* Definitions for /definq commands and a global linked list with all the definitions. */ struct definq_s { struct definq_s *next; char *name; /* Name of inquiry or NULL for any name. */ int is_prog; /* True if this is a program to run. */ char file[1]; /* Name of file or program. */ }; typedef struct definq_s *definq_t; static definq_t definq_list; static definq_t *definq_list_tail = &definq_list; /*-- local prototypes --*/ static int read_and_print_response (assuan_context_t ctx); static assuan_context_t start_agent (void); /* Print usage information and and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpg-connect-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-connect-agent [options] (-h for help)"); break; case 41: p = _("Syntax: gpg-connect-agent [options]\n" "Connect to a running agent and send commands\n"); break; case 31: p = "\nHome: "; break; case 32: p = opt.homedir; break; case 33: p = "\n"; break; default: p = NULL; break; } return p; } /* Initialize the gettext system. */ static void i18n_init(void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file (PACKAGE_GT); #else # ifdef ENABLE_NLS setlocale (LC_ALL, "" ); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); # endif #endif } /* Store an inquire response pattern. Note, that this function may change the content of LINE. We assume that leading white spaces are already removed. */ static void add_definq (char *line, int is_prog) { definq_t d; char *name, *p; /* Get name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; d = xmalloc (sizeof *d + strlen (p) ); strcpy (d->file, p); d->is_prog = is_prog; if ( !strcmp (name, "*")) d->name = NULL; else d->name = xstrdup (name); d->next = NULL; *definq_list_tail = d; definq_list_tail = &d->next; } /* Show all inquiry defintions. */ static void show_definq (void) { definq_t d; for (d=definq_list; d; d = d->next) if (d->name) printf ("%-20s %c %s\n", d->name, d->is_prog? 'p':'f', d->file); for (d=definq_list; d; d = d->next) if (!d->name) printf ("%-20s %c %s\n", "*", d->is_prog? 'p':'f', d->file); } /* Clear all inquiry definitions. */ static void clear_definq (void) { while (definq_list) { definq_t tmp = definq_list->next; xfree (definq_list->name); xfree (definq_list); definq_list = tmp; } definq_list_tail = &definq_list; } /* gpg-connect-agent's entry point. */ int main (int argc, char **argv) { ARGPARSE_ARGS pargs; const char *fname; int no_more_options = 0; assuan_context_t ctx; char *line, *p; size_t linesize; int rc; set_strusage (my_strusage); log_set_prefix ("gpg-connect-agent", 1); i18n_init(); opt.homedir = default_homedir (); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = 1; /* Do not remove the args. */ while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts)) { switch (pargs.r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: opt.homedir = pargs.r.ret_str; break; case oHex: opt.hex = 1; break; case oRawSocket: opt.raw_socket = pargs.r.ret_str; break; default: pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); fname = argc ? *argv : NULL; if (opt.raw_socket) { rc = assuan_socket_connect (&ctx, opt.raw_socket, 0); if (rc) { log_error ("can't connect to socket `%s': %s\n", opt.raw_socket, assuan_strerror (rc)); exit (1); } if (opt.verbose) log_info ("connection to socket `%s' established\n", opt.raw_socket); } else ctx = start_agent (); line = NULL; linesize = 0; for (;;) { int n; size_t maxlength; maxlength = 2048; n = read_line (stdin, &line, &linesize, &maxlength); if (n < 0) { log_error (_("error reading input: %s\n"), strerror (errno)); exit (1); } if (!n) break; /* EOF */ if (!maxlength) { log_error (_("line too long - skipped\n")); continue; } if (memchr (line, 0, n)) log_info (_("line shortened due to embedded Nul character\n")); if (line[n-1] == '\n') line[n-1] = 0; if (*line == '/') { /* Handle control commands. */ char *cmd = line+1; for (p=cmd; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; if (!strcmp (cmd, "definqfile")) { add_definq (p, 0); } else if (!strcmp (cmd, "definqprog")) { add_definq (p, 1); } else if (!strcmp (cmd, "showdef")) { show_definq (); } else if (!strcmp (cmd, "cleardef")) { clear_definq (); } else if (!strcmp (cmd, "echo")) { puts (p); } else if (!strcmp (cmd, "help")) { puts ("Available commands:\n" "/echo ARGS Echo ARGS.\n" "/definqfile NAME FILE\n" " Use content of FILE for inquiries with NAME.\n" " NAME may be \"*\" to match any inquiry.\n" "/definqprog NAME PGM\n" " Run PGM for inquiries matching NAME and pass the\n" " entire line to it as arguments.\n" "/showdef Print all definitions.\n" "/cleardef Delete all definitions.\n" "/help Print this help."); } else log_error (_("unknown command `%s'\n"), cmd ); continue; } rc = assuan_write_line (ctx, line); if (rc) { log_info (_("sending line failed: %s\n"), assuan_strerror (rc) ); continue; } if (*line == '#' || !*line) continue; /* Don't expect a response for a coment line. */ rc = read_and_print_response (ctx); if (rc) log_info (_("receiving line failed: %s\n"), assuan_strerror (rc) ); } if (opt.verbose) log_info ("closing connection to agent\n"); return 0; } /* Handle an Inquire from the server. Return False if it could not be handled; in this case the caller shll complete the operation. LINE is the complete line as received from the server. This function may change the content of LINE. */ static int handle_inquire (assuan_context_t ctx, char *line) { const char *name; definq_t d; FILE *fp; char buffer[1024]; int rc, n; /* Skip the command and trailing spaces. */ for (; *line && !spacep (line); line++) ; while (spacep (line)) line++; /* Get the name. */ name = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* Now match it against our list. he second loop is todetect the match all entry. **/ for (d=definq_list; d; d = d->next) if (d->name && !strcmp (d->name, name)) break; if (!d) for (d=definq_list; d; d = d->next) if (!d->name) break; if (!d) { if (opt.verbose) log_info ("no handler for inquiry `%s' found\n", name); return 0; } if (d->is_prog) { fp = popen (d->file, "r"); if (!fp) log_error ("error executing `%s': %s\n", d->file, strerror (errno)); else if (opt.verbose) log_error ("handling inquiry `%s' by running `%s'\n", name, d->file); } else { fp = fopen (d->file, "rb"); if (!fp) log_error ("error opening `%s': %s\n", d->file, strerror (errno)); else if (opt.verbose) log_error ("handling inquiry `%s' by returning content of `%s'\n", name, d->file); } if (!fp) return 0; while ( (n = fread (buffer, 1, sizeof buffer, fp)) ) { rc = assuan_send_data (ctx, buffer, n); if (rc) { log_error ("sending data back failed: %s\n", assuan_strerror (rc) ); break; } } if (ferror (fp)) log_error ("error reading from `%s': %s\n", d->file, strerror (errno)); rc = assuan_send_data (ctx, NULL, 0); if (rc) log_error ("sending data back failed: %s\n", assuan_strerror (rc) ); if (d->is_prog) { if (pclose (fp)) log_error ("error running `%s': %s\n", d->file, strerror (errno)); } else fclose (fp); return 1; } /* Read all response lines from server and print them. Returns 0 on success or an assuan error code. */ static int read_and_print_response (assuan_context_t ctx) { char *line; size_t linelen; assuan_error_t rc; int i, j; for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) return rc; } while (*line == '#' || !linelen); if (linelen >= 1 && line[0] == 'D' && line[1] == ' ') { if (opt.hex) { for (i=2; i < linelen; ) { int save_i = i; printf ("D[%04X] ", i-2); for (j=0; j < 16 ; j++, i++) { if (j == 8) putchar (' '); if (i < linelen) printf (" %02X", ((unsigned char*)line)[i]); else fputs (" ", stdout); } fputs (" ", stdout); i= save_i; for (j=0; j < 16; j++, i++) { unsigned int c = ((unsigned char*)line)[i]; if ( i >= linelen ) putchar (' '); else if (isascii (c) && isprint (c) && !iscntrl (c)) putchar (c); else putchar ('.'); } putchar ('\n'); } } else { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } else if (linelen >= 1 && line[0] == 'S' && (line[1] == '\0' || line[1] == ' ')) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { fwrite (line, linelen, 1, stdout); putchar ('\n'); return 0; } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { fwrite (line, linelen, 1, stdout); putchar ('\n'); return 0; } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { fwrite (line, linelen, 1, stdout); putchar ('\n'); if (!handle_inquire (ctx, line)) assuan_write_line (ctx, "CANCEL"); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (line[3] == '\0' || line[3] == ' ')) { fwrite (line, linelen, 1, stdout); putchar ('\n'); /* Received from server, thus more responses are expected. */ } else return ASSUAN_Invalid_Response; } } /* Connect to the agent and send the standard options. */ static assuan_context_t start_agent (void) { int rc = 0; char *infostr, *p; assuan_context_t ctx; infostr = getenv ("GPG_AGENT_INFO"); if (!infostr || !*infostr) { char *sockname; /* Check whether we can connect at the standard socket. */ sockname = make_filename (opt.homedir, "S.gpg-agent", NULL); rc = assuan_socket_connect (&ctx, sockname, 0); xfree (sockname); } else { int prot; int pid; infostr = xstrdup (infostr); if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) { log_error (_("malformed GPG_AGENT_INFO environment variable\n")); xfree (infostr); exit (1); } *p++ = 0; pid = atoi (p); while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("gpg-agent protocol version %d is not supported\n"), prot); xfree (infostr); exit (1); } rc = assuan_socket_connect (&ctx, infostr, pid); xfree (infostr); } if (rc) { log_error ("can't connect to the agent: %s\n", assuan_strerror (rc)); exit (1); } if (opt.verbose) log_info ("connection to agent established\n"); rc = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) { log_error (_("error sending %s command: %s\n"), "RESET", assuan_strerror (rc)); exit (1); } rc = send_pinentry_environment (ctx, GPG_ERR_SOURCE_DEFAULT, NULL, NULL, NULL, NULL, NULL); if (rc) { log_error (_("error sending standard options: %s\n"), gpg_strerror (rc)); exit (1); } return ctx; } diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 2da88bc49..04a61a193 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,2497 +1,2499 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. - Copyright (C) 2004 Free Software Foundation, Inc. - - This file is part of GnuPG. - - GnuPG is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 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 GnuPG; if not, write to the Free Software Foundation, - Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 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 GnuPG; if not, write to the Free Software Foundation, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include /* For log_logv(), asctimestamp(), gnupg_get_time (). */ #define JNLIB_NEED_LOG_LOGV #include "util.h" #include "i18n.h" #include "gpgconf.h" /* TODO: Components: Add more components and their options. Robustness: Do more validation. Call programs to do validation for us. Don't use popen, as this will not tell us if the program had a non-zero exit code. Add options to change backend binary path. Extract binary path for some backends from gpgsm/gpg config. */ #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )) void gc_error (int status, int errnum, const char *fmt, ...) \ __attribute__ ((format (printf, 3, 4))); #endif /* Output a diagnostic message. If ERRNUM is not 0, then the output is followed by a colon, a white space, and the error string for the error number ERRNUM. In any case the output is finished by a newline. The message is prepended by the program name, a colon, and a whitespace. The output may be further formatted or redirected by the jnlib logging facility. */ void gc_error (int status, int errnum, const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); log_logv (JNLIB_LOG_ERROR, fmt, arg_ptr); va_end (arg_ptr); if (errnum) log_printf (": %s\n", strerror (errnum)); else log_printf ("\n"); if (status) { log_printf (NULL); log_printf ("fatal error (exit status %i)\n", status); exit (status); } } /* Forward declaration. */ void gpg_agent_runtime_change (void); /* Backend configuration. Backends are used to decide how the default and current value of an option can be determined, and how the option can be changed. To every option in every component belongs exactly one backend that controls and determines the option. Some backends are programs from the GPG system. Others might be implemented by GPGConf itself. If you change this enum, don't forget to update GC_BACKEND below. */ typedef enum { /* Any backend, used for find_option (). */ GC_BACKEND_ANY, /* The Gnu Privacy Guard. */ GC_BACKEND_GPG, /* The Gnu Privacy Guard for S/MIME. */ GC_BACKEND_GPGSM, /* The GPG Agent. */ GC_BACKEND_GPG_AGENT, /* The GnuPG SCDaemon. */ GC_BACKEND_SCDAEMON, /* The Aegypten directory manager. */ GC_BACKEND_DIRMNGR, /* The LDAP server list file for the Aegypten director manager. */ GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST, /* The number of the above entries. */ GC_BACKEND_NR } gc_backend_t; /* To be able to implement generic algorithms for the various backends, we collect all information about them in this struct. */ static struct { /* The name of the backend. */ const char *name; /* The name of the program that acts as the backend. Some backends don't have an associated program, but are implemented directly by GPGConf. In this case, PROGRAM is NULL. */ char *program; /* The runtime change callback. */ void (*runtime_change) (void); /* The option name for the configuration filename of this backend. This must be an absolute pathname. It can be an option from a different backend (but then ordering of the options might matter). */ const char *option_config_filename; /* If this is a file backend rather than a program backend, then this is the name of the option associated with the file. */ const char *option_name; } gc_backend[GC_BACKEND_NR] = { { NULL }, /* GC_BACKEND_ANY dummy entry. */ { "GnuPG", "gpg", NULL, "gpgconf-gpg.conf" }, { "GPGSM", "gpgsm", NULL, "gpgconf-gpgsm.conf" }, { "GPG Agent", "gpg-agent", gpg_agent_runtime_change, "gpgconf-gpg-agent.conf" }, { "SCDaemon", "scdaemon", NULL, "gpgconf-scdaemon.conf" }, { "DirMngr", "dirmngr", NULL, "gpgconf-dirmngr.conf" }, { "DirMngr LDAP Server List", NULL, NULL, "ldapserverlist-file", "LDAP Server" }, }; /* Option configuration. */ /* An option might take an argument, or not. Argument types can be basic or complex. Basic types are generic and easy to validate. Complex types provide more specific information about the intended use, but can be difficult to validate. If you add to this enum, don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* Basic argument types. */ /* No argument. */ GC_ARG_TYPE_NONE = 0, /* A String argument. */ GC_ARG_TYPE_STRING = 1, /* A signed integer argument. */ GC_ARG_TYPE_INT32 = 2, /* An unsigned integer argument. */ GC_ARG_TYPE_UINT32 = 3, /* ADD NEW BASIC TYPE ENTRIES HERE. */ /* Complex argument types. */ /* A complete pathname. */ GC_ARG_TYPE_PATHNAME = 32, /* An LDAP server in the format HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */ GC_ARG_TYPE_LDAP_SERVER = 33, /* A 40 character fingerprint. */ GC_ARG_TYPE_KEY_FPR = 34, /* ADD NEW COMPLEX TYPE ENTRIES HERE. */ /* The number of the above entries. */ GC_ARG_TYPE_NR } gc_arg_type_t; /* For every argument, we record some information about it in the following struct. */ static struct { /* For every argument type exists a basic argument type that can be used as a fallback for input and validation purposes. */ gc_arg_type_t fallback; /* Human-readable name of the type. */ const char *name; } gc_arg_type[GC_ARG_TYPE_NR] = { /* The basic argument types have their own types as fallback. */ { GC_ARG_TYPE_NONE, "none" }, { GC_ARG_TYPE_STRING, "string" }, { GC_ARG_TYPE_INT32, "int32" }, { GC_ARG_TYPE_UINT32, "uint32" }, /* Reserved basic type entries for future extension. */ { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, /* The complex argument types have a basic type as fallback. */ { GC_ARG_TYPE_STRING, "pathname" }, { GC_ARG_TYPE_STRING, "ldap server" }, { GC_ARG_TYPE_STRING, "key fpr" }, }; /* Every option has an associated expert level, than can be used to hide advanced and expert options from beginners. If you add to this list, don't forget to update GC_LEVEL below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* The basic options should always be displayed. */ GC_LEVEL_BASIC, /* The advanced options may be hidden from beginners. */ GC_LEVEL_ADVANCED, /* The expert options should only be displayed to experts. */ GC_LEVEL_EXPERT, /* The invisible options should normally never be displayed. */ GC_LEVEL_INVISIBLE, /* The internal options are never exported, they mark options that are recorded for internal use only. */ GC_LEVEL_INTERNAL, /* ADD NEW ENTRIES HERE. */ /* The number of the above entries. */ GC_LEVEL_NR } gc_expert_level_t; /* A description for each expert level. */ static struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ #define GC_OPT_FLAG_NONE 0UL /* Some entries in the option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. */ #define GC_OPT_FLAG_GROUP (1UL << 0) /* The ARG_OPT flag for an option indicates that the argument is optional. This is never set for GC_ARG_TYPE_NONE options. */ #define GC_OPT_FLAG_ARG_OPT (1UL << 1) /* The LIST flag for an option indicates that the option can occur several times. A comma separated list of arguments is used as the argument value. */ #define GC_OPT_FLAG_LIST (1UL << 2) /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* The following flags are incorporated from the backend. */ /* The DEFAULT flag for an option indicates that the option has a default value. */ #define GC_OPT_FLAG_DEFAULT (1UL << 4) /* The DEF_DESC flag for an option indicates that the option has a default, which is described by the value of the default field. */ #define GC_OPT_FLAG_DEF_DESC (1UL << 5) /* The NO_ARG_DESC flag for an option indicates that the argument has a default, which is described by the value of the ARGDEF field. */ #define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) /* A human-readable description for each flag. */ static struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" } }; /* To each option, or group marker, the information in the GC_OPTION struct is provided. If you change this, don't forget to update the option list of each component. */ struct gc_option { /* If this is NULL, then this is a terminator in an array of unknown length. Otherwise, if this entry is a group marker (see FLAGS), then this is the name of the group described by this entry. Otherwise it is the name of the option described by this entry. The name must not contain a colon. */ const char *name; /* The option flags. If the GROUP flag is set, then this entry is a group marker, not an option, and only the fields LEVEL, DESC_DOMAIN and DESC are valid. In all other cases, this entry describes a new option and all fields are valid. */ unsigned long flags; /* The expert level. This field is valid for options and groups. A group has the expert level of the lowest-level option in the group. */ gc_expert_level_t level; /* A gettext domain in which the following description can be found. If this is NULL, then DESC is not translated. Valid for groups and options. Note that we try to keep the description of groups within the gnupg domain. IMPORTANT: If you add a new domain please make sure to add a code set switching call to the function my_dgettext further below. */ const char *desc_domain; /* A gettext description for this group or option. If it starts with a '|', then the string up to the next '|' describes the argument, and the description follows the second '|'. In general enclosing these description in N_() is not required because the description should be identical to the one in the help menu of the respective program. */ const char *desc; /* The following fields are only valid for options. */ /* The type of the option argument. */ gc_arg_type_t arg_type; /* The backend that implements this option. */ gc_backend_t backend; /* The following fields are set to NULL at startup (because all option's are declared as static variables). They are at the end of the list so that they can be omitted from the option declarations. */ /* This is true if the option is supported by this version of the backend. */ int active; /* The default value for this option. This is NULL if the option is not present in the backend, the empty string if no default is available, and otherwise a quoted string. */ char *default_value; /* The default argument is only valid if the "optional arg" flag is set, and specifies the default argument (value) that is used if the argument is omitted. */ char *default_arg; /* The current value of this option. */ char *value; /* The new flags for this option. The only defined flag is actually GC_OPT_FLAG_DEFAULT, and it means that the option should be deleted. In this case, NEW_VALUE is NULL. */ unsigned long new_flags; /* The new value of this option. */ char *new_value; }; typedef struct gc_option gc_option_t; /* Use this macro to terminate an option list. */ #define GC_OPTION_NULL { NULL } /* The options of the GC_COMPONENT_GPG_AGENT component. */ static gc_option_t gc_options_gpg_agent[] = { /* The configuration file to which we write the changes. */ { "gpgconf-gpg-agent.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not use the SCdaemon", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT }, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|expire cached PINs after N seconds", GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "allow clients to mark keys as \"trusted\"", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "do not grab keyboard and mouse", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, GC_OPTION_NULL }; /* The options of the GC_COMPONENT_SCDAEMON component. */ static gc_option_t gc_options_scdaemon[] = { /* The configuration file to which we write the changes. */ { "gpgconf-scdaemon.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON }, { "reader-port", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "|N|connect to reader at port N", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "ctapi-driver", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use NAME as ct-API driver", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "pcsc-driver", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use NAME as PC/SC driver", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "disable-opensc", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "do not use the OpenSC layer", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "disable-ccid", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "do not use the internal CCID driver", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "disable-keypad", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not use a reader's keypad", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "allow-admin", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "allow the use of admin card commands", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, GC_OPTION_NULL }; /* The options of the GC_COMPONENT_GPG component. */ static gc_option_t gc_options_gpg[] = { /* The configuration file to which we write the changes. */ { "gpgconf-gpg.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG }, /* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */ /* NULL, NULL, */ /* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */ { "Keyserver", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration for Keyservers") }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "|URL|use keyserver at URL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("allow PKA lookups (DNS requests)"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, GC_OPTION_NULL }; /* The options of the GC_COMPONENT_GPGSM component. */ static gc_option_t gc_options_gpgsm[] = { /* The configuration file to which we write the changes. */ { "gpgconf-gpgsm.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM }, { "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "use system's dirmngr if available", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "never consult a CRL", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("do not check CRLs for root certificates"), GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "check validity using OCSP", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|N|number of certificates to include", GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not check certificate policies", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "fetch missing issuer certificates", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, GC_OPTION_NULL }; /* The options of the GC_COMPONENT_DIRMNGR component. */ static gc_option_t gc_options_dirmngr[] = { /* The configuration file to which we write the changes. */ { "gpgconf-dirmngr.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "dirmngr", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Format", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the format of the output") }, { "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "sh-style command output", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "csh-style command output", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "dirmngr", "|FILE|read options from FILE", GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "dirmngr", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "do not detach from the console", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, { "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "Enforcement", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the interactivity and enforcement") }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "run without asking a user", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "force loading of outdated CRLs", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "HTTP", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Configuration for HTTP servers") }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "inhibit the use of HTTP", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore HTTP CRL distribution points", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|URL|redirect all HTTP requests to URL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", N_("use system's HTTP proxy setting"), GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "LDAP", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration of LDAP servers to use") }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "inhibit the use of LDAP", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore LDAP CRL distribution points", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|HOST|use HOST for LDAP queries", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "do not use fallback hosts with --ldap-proxy", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "add new servers discovered in CRL distribution points" " to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|N|set LDAP timeout to N seconds", GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, /* The following entry must not be removed, as it is required for the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */ { "ldapserverlist-file", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, "dirmngr", "|FILE|read LDAP server list from FILE", GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, /* This entry must come after at least one entry for GC_BACKEND_DIRMNGR in this component, so that the entry for "ldapserverlist-file will be initialized before this one. */ { "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, NULL, "LDAP server list", GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|N|do not return more than N items in one query", GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "OCSP", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Configuration for OCSP") }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "allow sending OCSP requests", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore certificate contained OCSP service URLs", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|URL|use OCSP responder at URL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|FPR|OCSP response signed by FPR", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, GC_OPTION_NULL }; /* Component system. Each component is a set of options that can be configured at the same time. If you change this, don't forget to update GC_COMPONENT below. */ typedef enum { /* The classic GPG for OpenPGP. */ GC_COMPONENT_GPG, /* The GPG Agent. */ GC_COMPONENT_GPG_AGENT, /* The Smardcard Daemon. */ GC_COMPONENT_SCDAEMON, /* GPG for S/MIME. */ GC_COMPONENT_GPGSM, /* The LDAP Directory Manager for CRLs. */ GC_COMPONENT_DIRMNGR, /* The number of components. */ GC_COMPONENT_NR } gc_component_t; /* The information associated with each component. */ static struct { /* The name of this component. Must not contain a colon (':') character. */ const char *name; /* The gettext domain for the description DESC. If this is NULL, then the description is not translated. */ const char *desc_domain; /* The description for this domain. */ const char *desc; /* The list of options for this component, terminated by GC_OPTION_NULL. */ gc_option_t *options; } gc_component[] = { { "gpg", NULL, "GPG for OpenPGP", gc_options_gpg }, { "gpg-agent", NULL, "GPG Agent", gc_options_gpg_agent }, { "scdaemon", NULL, "Smartcard Daemon", gc_options_scdaemon }, { "gpgsm", NULL, "GPG for S/MIME", gc_options_gpgsm }, { "dirmngr", NULL, "Directory Manager", gc_options_dirmngr } }; /* Engine specific support. */ void gpg_agent_runtime_change (void) { #ifndef HAVE_W32_SYSTEM char *agent = getenv ("GPG_AGENT_INFO"); char *pid_str; unsigned long pid_long; char *tail; pid_t pid; if (!agent) return; pid_str = strchr (agent, ':'); if (!pid_str) return; pid_str++; errno = 0; pid_long = strtoul (pid_str, &tail, 0); if (errno || (*tail != ':' && *tail != '\0')) return; pid = (pid_t) pid_long; /* Check for overflow. */ if (pid_long != (unsigned long) pid) return; /* Ignore any errors here. */ kill (pid, SIGHUP); #endif /*!HAVE_W32_SYSTEM*/ } /* More or less Robust version of dgettext. It has the side effect of switching the codeset to utf-8 because this is what we want to output. In theory it is posible to keep the orginal code set and switch back for regular disgnostic output (redefine "_(" for that) but given the natur of this tool, being something invoked from other pograms, it does not make much sense. */ static const char * my_dgettext (const char *domain, const char *msgid) { #ifdef ENABLE_NLS if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; bind_textdomain_codeset (PACKAGE_GT, "utf-8"); bindtextdomain ("dirmngr", LOCALEDIR); bind_textdomain_codeset ("dirmngr", "utf-8"); } /* Note: This is a hack to actually use the gnupg2 domain as long we are in a transition phase where gnupg 1.x and 1.9 may coexist. */ if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; text = dgettext (domain, msgid); return text ? text : msgid; } else #endif return msgid; } /* Percent-Escape special characters. The string is valid until the next invocation of the function. */ static char * percent_escape (const char *src) { static char *esc_str; static int esc_str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (esc_str_len < new_len) { char *new_esc_str = realloc (esc_str, new_len); if (!new_esc_str) gc_error (1, errno, "can not escape string"); esc_str = new_esc_str; esc_str_len = new_len; } dst = esc_str; while (*src) { if (*src == '%') { *(dst++) = '%'; *(dst++) = '2'; *(dst++) = '5'; } else if (*src == ':') { /* The colon is used as field separator. */ *(dst++) = '%'; *(dst++) = '3'; *(dst++) = 'a'; } else if (*src == ',') { /* The comma is used as list separator. */ *(dst++) = '%'; *(dst++) = '2'; *(dst++) = 'c'; } else *(dst++) = *(src); src++; } *dst = '\0'; return esc_str; } /* Percent-Deescape special characters. The string is valid until the next invocation of the function. */ static char * percent_deescape (const char *src) { static char *str; static int str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (str_len < new_len) { char *new_str = realloc (str, new_len); if (!new_str) gc_error (1, errno, "can not deescape string"); str = new_str; str_len = new_len; } dst = str; while (*src) { if (*src == '%') { int val = hextobyte (src + 1); if (val < 0) gc_error (1, 0, "malformed end of string %s", src); *(dst++) = (char) val; src += 3; } else *(dst++) = *(src++); } *dst = '\0'; return str; } /* List all components that are available. */ void gc_component_list_components (FILE *out) { gc_component_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { const char *desc = gc_component[idx].desc; desc = my_dgettext (gc_component[idx].desc_domain, desc); fprintf (out, "%s:%s\n", gc_component[idx].name, percent_escape (desc)); } } /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name) { gc_component_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (!strcmp (name, gc_component[idx].name)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (const gc_option_t *option, FILE *out) { const char *desc = NULL; char *arg_name = NULL; if (option->desc) { desc = my_dgettext (option->desc_domain, option->desc); if (*desc == '|') { const char *arg_tail = strchr (&desc[1], '|'); if (arg_tail) { int arg_len = arg_tail - &desc[1]; arg_name = xmalloc (arg_len + 1); memcpy (arg_name, &desc[1], arg_len); arg_name[arg_len] = '\0'; desc = arg_tail + 1; } } } /* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY FIELDS. */ /* The name field. */ fprintf (out, "%s", option->name); /* The flags field. */ fprintf (out, ":%lu", option->flags); if (opt.verbose) { putc (' ', out); if (!option->flags) fprintf (out, "none"); else { unsigned long flags = option->flags; unsigned long flag = 0; unsigned long first = 1; while (flags) { if (flags & 1) { if (first) first = 0; else putc (',', out); fprintf (out, "%s", gc_flag[flag].name); } flags >>= 1; flag++; } } } /* The level field. */ fprintf (out, ":%u", option->level); if (opt.verbose) fprintf (out, " %s", gc_level[option->level].name); /* The description field. */ fprintf (out, ":%s", desc ? percent_escape (desc) : ""); /* The type field. */ fprintf (out, ":%u", option->arg_type); if (opt.verbose) fprintf (out, " %s", gc_arg_type[option->arg_type].name); /* The alternate type field. */ fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback); if (opt.verbose) fprintf (out, " %s", gc_arg_type[gc_arg_type[option->arg_type].fallback].name); /* The argument name field. */ fprintf (out, ":%s", arg_name ? percent_escape (arg_name) : ""); if (arg_name) xfree (arg_name); /* The default value field. */ fprintf (out, ":%s", option->default_value ? option->default_value : ""); /* The default argument field. */ fprintf (out, ":%s", option->default_arg ? option->default_arg : ""); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && (option->flags & GC_OPT_FLAG_LIST) && option->value) /* The special format "1,1,1,1,...,1" is converted to a number here. */ fprintf (out, ":%u", (strlen (option->value) + 1) / 2); else fprintf (out, ":%s", option->value ? option->value : ""); /* ADD NEW FIELDS HERE. */ putc ('\n', out); } /* List all options of the component COMPONENT. */ void gc_component_list_options (int component, FILE *out) { const gc_option_t *option = gc_component[component].options; const gc_option_t *group_option = NULL; while (option->name) { /* Do not output unknown or internal options. */ if (!(option->flags & GC_OPT_FLAG_GROUP) && (!option->active || option->level == GC_LEVEL_INTERNAL)) { option++; continue; } if (option->flags & GC_OPT_FLAG_GROUP) group_option = option; else { if (group_option) { list_one_option (group_option, out); group_option = NULL; } list_one_option (option, out); } option++; } } /* Find the option NAME in component COMPONENT, for the backend BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */ static gc_option_t * find_option (gc_component_t component, const char *name, gc_backend_t backend) { gc_option_t *option = gc_component[component].options; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP) && !strcmp (option->name, name) && (backend == GC_BACKEND_ANY || option->backend == backend)) break; option++; } return option->name ? option : NULL; } /* Determine the configuration pathname for the component COMPONENT and backend BACKEND. */ static char * get_config_pathname (gc_component_t component, gc_backend_t backend) { char *pathname = NULL; gc_option_t *option = find_option (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); assert (option); assert (option->arg_type == GC_ARG_TYPE_PATHNAME); assert (!(option->flags & GC_OPT_FLAG_LIST)); if (!option->active || !option->default_value) gc_error (1, 0, "Option %s, needed by backend %s, was not initialized", gc_backend[backend].option_config_filename, gc_backend[backend].name); if (option->value && *option->value) pathname = percent_deescape (&option->value[1]); else if (option->default_value && *option->default_value) pathname = percent_deescape (&option->default_value[1]); else pathname = ""; #ifdef HAVE_DOSISH_SYSTEM if (!(pathname[0] && pathname[1] == ':' && (pathname[2] == '/' || pathname[2] == '\\'))) #else if (pathname[0] != '/') #endif gc_error (1, 0, "Option %s, needed by backend %s, is not absolute", gc_backend[backend].option_config_filename, gc_backend[backend].name); return pathname; } /* Retrieve the options for the component COMPONENT from backend BACKEND, which we already know is a program-type backend. */ static void retrieve_options_from_program (gc_component_t component, gc_backend_t backend) { char *cmd_line; char *line = NULL; size_t line_len = 0; ssize_t length; FILE *config; char *config_pathname; cmd_line = xasprintf ("%s --gpgconf-list", gc_backend[backend].program); config = popen (cmd_line, "r"); if (!config) gc_error (1, errno, "could not gather active options from %s", cmd_line); while ((length = read_line (config, &line, &line_len, NULL)) > 0) { gc_option_t *option; char *linep; unsigned long flags = 0; char *default_value = NULL; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; errno = 0; flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s from %s", line, cmd_line); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s from %s", line, cmd_line); linep = end; } /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; if (flags & GC_OPT_FLAG_DEFAULT) default_value = linep; linep = end; } /* Look up the option in the component and install the configuration data. */ option = find_option (component, line, backend); if (option) { if (option->active) gc_error (1, errno, "option %s returned twice from %s", line, cmd_line); option->active = 1; option->flags |= flags; if (default_value && *default_value) option->default_value = xstrdup (default_value); } } if (length < 0 || ferror (config)) gc_error (1, errno, "error reading from %s", cmd_line); if (fclose (config) && ferror (config)) gc_error (1, errno, "error closing %s", cmd_line); xfree (cmd_line); /* At this point, we can parse the configuration file. */ config_pathname = get_config_pathname (component, backend); config = fopen (config_pathname, "r"); if (!config) gc_error (0, errno, "warning: can not open config file %s", config_pathname); else { while ((length = read_line (config, &line, &line_len, NULL)) > 0) { char *name; char *value; gc_option_t *option; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; value = name; while (*value && *value != ' ' && *value != '\t' && *value != '#' && *value != '\r' && *value != '\n') value++; if (*value == ' ' || *value == '\t') { char *end; *(value++) = '\0'; while (*value == ' ' || *value == '\t') value++; end = value; while (*end && *end != '#' && *end != '\r' && *end != '\n') end++; while (end > value && (end[-1] == ' ' || end[-1] == '\t')) end--; *end = '\0'; } else *value = '\0'; /* Look up the option in the component and install the configuration data. */ option = find_option (component, line, backend); if (option) { char *opt_value; if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { if (*value) gc_error (0, 0, "warning: ignoring argument %s for option %s", value, name); opt_value = xstrdup ("1"); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) opt_value = xasprintf ("\"%s", percent_escape (value)); else { /* FIXME: Verify that the number is sane. */ opt_value = xstrdup (value); } /* Now enter the option into the table. */ if (!(option->flags & GC_OPT_FLAG_LIST)) { if (option->value) free (option->value); option->value = opt_value; } else { if (!option->value) option->value = opt_value; else { char *opt_val = opt_value; option->value = xasprintf ("%s,%s", option->value, opt_val); xfree (opt_value); } } } } if (length < 0 || ferror (config)) gc_error (1, errno, "error reading from %s", config_pathname); if (fclose (config) && ferror (config)) gc_error (1, errno, "error closing %s", config_pathname); } xfree (line); } /* Retrieve the options for the component COMPONENT from backend BACKEND, which we already know is of type file list. */ static void retrieve_options_from_file (gc_component_t component, gc_backend_t backend) { gc_option_t *list_option; char *list_pathname; FILE *list_file; char *line = NULL; size_t line_len = 0; ssize_t length; char *list = NULL; list_option = find_option (component, gc_backend[backend].option_name, GC_BACKEND_ANY); assert (list_option); assert (!list_option->active); list_pathname = get_config_pathname (component, backend); list_file = fopen (list_pathname, "r"); if (!list_file) gc_error (0, errno, "warning: can not open list file %s", list_pathname); else { while ((length = read_line (list_file, &line, &line_len, NULL)) > 0) { char *start; char *end; char *new_list; start = line; while (*start == ' ' || *start == '\t') start++; if (!*start || *start == '#' || *start == '\r' || *start == '\n') continue; end = start; while (*end && *end != '#' && *end != '\r' && *end != '\n') end++; /* Walk back to skip trailing white spaces. Looks evil, but works because of the conditions on START and END imposed at this point (END is at least START + 1, and START is not a whitespace character). */ while (*(end - 1) == ' ' || *(end - 1) == '\t') end--; *end = '\0'; /* FIXME: Oh, no! This is so lame! Should use realloc and really append. */ if (list) { new_list = xasprintf ("%s,\"%s", list, percent_escape (start)); xfree (list); list = new_list; } else list = xasprintf ("\"%s", percent_escape (start)); } if (length < 0 || ferror (list_file)) gc_error (1, errno, "can not read list file %s", list_pathname); } list_option->active = 1; list_option->value = list; xfree (line); } /* Retrieve the currently active options and their defaults from all involved backends for this component. */ void gc_component_retrieve_options (int component) { int backend_seen[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option = gc_component[component].options; for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP)) { backend = option->backend; if (backend_seen[backend]) { option++; continue; } backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (gc_backend[backend].program) retrieve_options_from_program (component, backend); else retrieve_options_from_file (component, backend); } option++; } } /* Perform a simple validity check based on the type. Return in NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of type GC_ARG_TYPE_NONE. */ static void option_check_validity (gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr) { char *arg; if (!option->active) gc_error (1, 0, "option %s not supported by backend", option->name); if (option->new_flags || option->new_value) gc_error (1, 0, "option %s already changed", option->name); if (flags & GC_OPT_FLAG_DEFAULT) { if (*new_value) gc_error (1, 0, "argument %s provided for deleted option %s", new_value, option->name); return; } /* GC_ARG_TYPE_NONE options have special list treatment. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { char *tail; errno = 0; *new_value_nr = strtoul (new_value, &tail, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*tail) gc_error (1, 0, "garbage after argument for option %s", option->name); if (!(option->flags & GC_OPT_FLAG_LIST)) { if (*new_value_nr != 1) gc_error (1, 0, "argument for non-list option %s of type 0 " "(none) must be 1", option->name); } else { if (*new_value_nr == 0) gc_error (1, 0, "argument for option %s of type 0 (none) " "must be positive", option->name); } return; } arg = new_value; do { if (*arg == '\0' || *arg == ',') { if (!(option->flags & GC_OPT_FLAG_ARG_OPT)) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !(option->flags & GC_OPT_FLAG_LIST)) gc_error (1, 0, "list found for non-list option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { if (*arg != '"') gc_error (1, 0, "string argument for option %s must begin " "with a quote (\") character", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) { errno = 0; (void) strtol (arg, &arg, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && *arg != ',') gc_error (1, 0, "garbage after argument for option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) { errno = 0; (void) strtoul (arg, &arg, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && *arg != ',') gc_error (1, 0, "garbage after argument for option %s", option->name); } arg = strchr (arg, ','); if (arg) arg++; } while (arg && *arg); } /* Create and verify the new configuration file for the specified backend and component. Returns 0 on success and -1 on error. */ static int change_options_file (gc_component_t component, gc_backend_t backend, char **src_filenamep, char **dest_filenamep, char **orig_filenamep) { static const char marker[] = "###+++--- GPGConf ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; FILE *src_file = NULL; FILE *dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; char *arg; char *cur_arg = NULL; option = find_option (component, gc_backend[backend].option_name, GC_BACKEND_ANY); assert (option); assert (option->active); assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE); /* FIXME. Throughout the function, do better error reporting. */ /* Note that get_config_pathname() calls percent_deescape(), so we call this before processing the arguments. */ dest_filename = xstrdup (get_config_pathname (component, backend)); src_filename = xasprintf ("%s.gpgconf.%i.new", dest_filename, getpid ()); orig_filename = xasprintf ("%s.gpgconf.%i.bak", dest_filename, getpid ()); arg = option->new_value; if (arg && arg[0] == '\0') arg = NULL; else if (arg) { char *end; arg++; end = strchr (arg, ','); if (end) *end = '\0'; cur_arg = percent_deescape (arg); if (end) { *end = ','; arg = end + 1; } else arg = NULL; } #if HAVE_W32_SYSTEM res = 0; #warning no backups for W32 yet - need to write a copy function #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) return -1; if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. */ fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = fdopen (fd, "w"); res = errno; if (!src_file) { errno = res; return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = fopen (dest_filename, "r"); if (!dest_file) goto change_file_one_err; while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char *endp; char saved_end; endp = start; end = endp; /* Search for the end of the line. */ while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n') { endp++; if (*endp && *endp != ' ' && *endp != '\t' && *endp != '\r' && *endp != '\n' && *endp != '#') end = endp + 1; } saved_end = *end; *end = '\0'; if ((option->new_flags & GC_OPT_FLAG_DEFAULT) || !cur_arg || strcmp (start, cur_arg)) disable = 1; else { /* Find next argument. */ if (arg) { char *arg_end; arg++; arg_end = strchr (arg, ','); if (arg_end) *arg_end = '\0'; cur_arg = percent_deescape (arg); if (arg_end) { *arg_end = ','; arg = arg_end + 1; } else arg = NULL; } else cur_arg = NULL; } *end = saved_end; } if (disable) { if (!in_marker) { fprintf (src_file, "# GPGConf disabled this option here at %s\n", asctimestamp (gnupg_get_time ())); if (ferror (src_file)) goto change_file_one_err; fprintf (src_file, "# %s", line); if (ferror (src_file)) goto change_file_one_err; } } else { fprintf (src_file, "%s", line); if (ferror (src_file)) goto change_file_one_err; } } if (length < 0 || ferror (dest_file)) goto change_file_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ fprintf (src_file, "\n%s\n", marker); if (ferror (src_file)) goto change_file_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the arguments we are going to add. Now, dump the new arguments and write the end marker, possibly followed by the rest of the original file. */ while (cur_arg) { fprintf (src_file, "%s\n", cur_arg); /* Find next argument. */ if (arg) { char *end; arg++; end = strchr (arg, ','); if (end) *end = '\0'; cur_arg = percent_deescape (arg); if (end) { *end = ','; arg = end + 1; } else arg = NULL; } else cur_arg = NULL; } fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (ferror (src_file)) goto change_file_one_err; if (!in_marker) { fprintf (src_file, "# GPGConf edited this configuration file.\n"); if (ferror (src_file)) goto change_file_one_err; fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (ferror (src_file)) goto change_file_one_err; fprintf (src_file, "# never change anything below these lines.\n"); if (ferror (src_file)) goto change_file_one_err; } if (dest_file) { while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0) { fprintf (src_file, "%s", line); if (ferror (src_file)) goto change_file_one_err; } if (length < 0 || ferror (dest_file)) goto change_file_one_err; } xfree (line); line = NULL; res = fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) fclose (dest_file); errno = res; return -1; } close (fd); if (dest_file) { res = fclose (dest_file); if (res) return -1; } return 0; change_file_one_err: xfree (line); res = errno; if (src_file) { fclose (src_file); close (fd); } if (dest_file) fclose (dest_file); errno = res; return -1; } /* Create and verify the new configuration file for the specified backend and component. Returns 0 on success and -1 on error. */ static int change_options_program (gc_component_t component, gc_backend_t backend, char **src_filenamep, char **dest_filenamep, char **orig_filenamep) { static const char marker[] = "###+++--- GPGConf ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; FILE *src_file = NULL; FILE *dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; /* FIXME. Throughout the function, do better error reporting. */ dest_filename = xstrdup (get_config_pathname (component, backend)); src_filename = xasprintf ("%s.gpgconf.%i.new", dest_filename, getpid ()); orig_filename = xasprintf ("%s.gpgconf.%i.bak", dest_filename, getpid ()); #if HAVE_W32_SYSTEM res = 0; #warning no backups for W32 yet - need to write a copy function #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) return -1; if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. */ fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = fdopen (fd, "w"); res = errno; if (!src_file) { errno = res; return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = fopen (dest_filename, "r"); if (!dest_file) goto change_one_err; while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char saved_end; end = start; while (*end && *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n' && *end != '#') end++; saved_end = *end; *end = '\0'; option = find_option (component, start, backend); *end = saved_end; if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) || option->new_value)) disable = 1; } if (disable) { if (!in_marker) { fprintf (src_file, "# GPGConf disabled this option here at %s\n", asctimestamp (gnupg_get_time ())); if (ferror (src_file)) goto change_one_err; fprintf (src_file, "# %s", line); if (ferror (src_file)) goto change_one_err; } } else { fprintf (src_file, "%s", line); if (ferror (src_file)) goto change_one_err; } } if (length < 0 || ferror (dest_file)) goto change_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ fprintf (src_file, "\n%s\n", marker); if (ferror (src_file)) goto change_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the options we are going to change. Now, dump the changed options (except for those we are going to revert to their default), and write the end marker, possibly followed by the rest of the original file. */ /* We have to turn on UTF8 strings for GnuPG. */ if (backend == GC_BACKEND_GPG) fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP) && option->backend == backend && option->new_value) { char *arg = option->new_value; do { if (*arg == '\0' || *arg == ',') { fprintf (src_file, "%s\n", option->name); if (ferror (src_file)) goto change_one_err; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { assert (*arg == '1'); fprintf (src_file, "%s\n", option->name); if (ferror (src_file)) goto change_one_err; arg++; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { char *end; assert (*arg == '"'); arg++; end = strchr (arg, ','); if (end) *end = '\0'; fprintf (src_file, "%s %s\n", option->name, percent_deescape (arg)); if (ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } else { char *end; end = strchr (arg, ','); if (end) *end = '\0'; fprintf (src_file, "%s %s\n", option->name, arg); if (ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } option++; } fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (ferror (src_file)) goto change_one_err; if (!in_marker) { fprintf (src_file, "# GPGConf edited this configuration file.\n"); if (ferror (src_file)) goto change_one_err; fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (ferror (src_file)) goto change_one_err; fprintf (src_file, "# never change anything below these lines.\n"); if (ferror (src_file)) goto change_one_err; } if (dest_file) { while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0) { fprintf (src_file, "%s", line); if (ferror (src_file)) goto change_one_err; } if (length < 0 || ferror (dest_file)) goto change_one_err; } xfree (line); line = NULL; res = fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) fclose (dest_file); errno = res; return -1; } close (fd); if (dest_file) { res = fclose (dest_file); if (res) return -1; } return 0; change_one_err: xfree (line); res = errno; if (src_file) { fclose (src_file); close (fd); } if (dest_file) fclose (dest_file); errno = res; return -1; } /* Read the modifications from IN and apply them. */ void gc_component_change_options (int component, FILE *in) { int err = 0; int runtime[GC_BACKEND_NR]; char *src_pathname[GC_BACKEND_NR]; char *dest_pathname[GC_BACKEND_NR]; char *orig_pathname[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; for (backend = 0; backend < GC_BACKEND_NR; backend++) { runtime[backend] = 0; src_pathname[backend] = NULL; dest_pathname[backend] = NULL; orig_pathname[backend] = NULL; } while ((length = read_line (in, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *new_value = ""; unsigned long new_value_nr = 0; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; errno = 0; flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s", line); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s", line); linep = end; } /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; new_value = linep; linep = end; } option = find_option (component, line, GC_BACKEND_ANY); if (!option) gc_error (1, 0, "unknown option %s", line); option_check_validity (option, flags, new_value, &new_value_nr); if (option->flags & GC_OPT_FLAG_RUNTIME) runtime[option->backend] = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && (option->flags & GC_OPT_FLAG_LIST)) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ assert (new_value_nr > 0); option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1); str = option->new_value; *(str++) = '1'; while (--new_value_nr > 0) { *(str++) = ','; *(str++) = '1'; } *(str++) = '\0'; } else option->new_value = xstrdup (new_value); } } /* Now that we have collected and locally verified the changes, write them out to new configuration files, verify them externally, and then commit them. */ option = gc_component[component].options; while (option->name) { /* Go on if we have already seen this backend, or if there is nothing to do. */ if (src_pathname[option->backend] || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_backend[option->backend].program) err = change_options_program (component, option->backend, &src_pathname[option->backend], &dest_pathname[option->backend], &orig_pathname[option->backend]); else err = change_options_file (component, option->backend, &src_pathname[option->backend], &dest_pathname[option->backend], &orig_pathname[option->backend]); if (err) break; option++; } if (!err) { int i; for (i = 0; i < GC_BACKEND_NR; i++) { if (src_pathname[i]) { /* FIXME: Make a verification here. */ assert (dest_pathname[i]); if (orig_pathname[i]) err = rename (src_pathname[i], dest_pathname[i]); else { #ifdef HAVE_W32_SYSTEM /* FIXME: Won't work becuase W32 doesn't silently overwrite. Fix it by creating a backup copy and deliting the orginal file first. */ err = rename (src_pathname[i], dest_pathname[i]); #else /*!HAVE_W32_SYSTEM*/ /* This is a bit safer than rename() because we expect DEST_PATHNAME not to be there. If it happens to be there, this will fail. */ err = link (src_pathname[i], dest_pathname[i]); if (!err) unlink (src_pathname[i]); #endif /*!HAVE_W32_SYSTEM*/ } if (err) break; src_pathname[i] = NULL; } } } if (err) { int i; int saved_errno = errno; /* An error occured. */ for (i = 0; i < GC_BACKEND_NR; i++) { if (src_pathname[i]) { /* The change was not yet committed. */ unlink (src_pathname[i]); if (orig_pathname[i]) unlink (orig_pathname[i]); } else { /* The changes were already committed. FIXME: This is a tad dangerous, as we don't know if we don't overwrite a version of the file that is even newer than the one we just installed. */ if (orig_pathname[i]) rename (orig_pathname[i], dest_pathname[i]); else unlink (dest_pathname[i]); } } gc_error (1, saved_errno, "could not commit changes"); } /* If it all worked, notify the daemons of the changes. */ if (opt.runtime) for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (); } /* Move the per-process backup file into its place. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) if (orig_pathname[backend]) { char *backup_pathname; assert (dest_pathname[backend]); backup_pathname = xasprintf ("%s.gpgconf.bak", dest_pathname[backend]); rename (orig_pathname[backend], backup_pathname); } xfree (line); } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index dd505e99d..87ba45ae1 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,202 +1,203 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "gpgconf.h" #include "i18n.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNoVerbose = 500, oHomedir, aListComponents, aListOptions, aChangeOptions, }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { 301, NULL, 0, N_("@\nOptions:\n ") }, { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, /* hidden options */ { oNoVerbose, "no-verbose", 0, "@"}, {0} }; /* Print usage information and and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpgconf (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: gpgconf [options] (-h for help)"); break; case 41: p = _("Syntax: gpgconf [options]\n" "Manage configuration options for tools of the GnuPG system\n"); break; default: p = NULL; break; } return p; } /* Initialize the gettext system. */ static void i18n_init(void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file (PACKAGE_GT); #else # ifdef ENABLE_NLS setlocale (LC_ALL, "" ); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); # endif #endif } /* gpgconf main. */ int main (int argc, char **argv) { ARGPARSE_ARGS pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; set_strusage (my_strusage); log_set_prefix ("gpgconf", 1); i18n_init(); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = 1; /* Do not remove the args. */ while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case aListComponents: case aListOptions: case aChangeOptions: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); fname = argc ? *argv : NULL; switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (stdout); break; case aListOptions: case aChangeOptions: if (!fname) { fputs (_("usage: gpgconf [options] "), stderr); putc ('\n',stderr); fputs (_("Need one component argument"), stderr); putc ('\n',stderr); exit (2); } else { int idx = gc_component_find (fname); if (idx < 0) { fputs (_("Component not found"), stderr); putc ('\n', stderr); exit (1); } gc_component_retrieve_options (idx); if (cmd == aListOptions) gc_component_list_options (idx, stdout); else gc_component_change_options (idx, stdin); } } return 0; } diff --git a/tools/gpgconf.h b/tools/gpgconf.h index 138380b6d..c083c26aa 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -1,58 +1,59 @@ /* gpgconf.h - Global definitions for gpgconf * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GPGCONF_H #define GPGCONF_H #include "../common/util.h" /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ int quiet; /* Be extra quiet. */ int dry_run; /* Don't change any persistent data. */ int runtime; /* Make changes active at runtime. */ char *outfile; /* Name of output file. */ int component; /* The active component. */ } opt; /*-- gpgconf-comp.c --*/ /* List all components that are available. */ void gc_component_list_components (FILE *out); /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name); /* Retrieve the currently active options and their defaults from all involved backends for this component. */ void gc_component_retrieve_options (int component); /* List all options of the component COMPONENT. */ void gc_component_list_options (int component, FILE *out); /* Read the modifications from IN and apply them. */ void gc_component_change_options (int component, FILE *in); #endif /*GPGCONF_H*/ diff --git a/tools/gpgkey2ssh.c b/tools/gpgkey2ssh.c index e874ab22e..3dcb6516e 100644 --- a/tools/gpgkey2ssh.c +++ b/tools/gpgkey2ssh.c @@ -1,297 +1,298 @@ /* gpgkey2ssh.c - Converter ... - Copyright (C) 2005 Free Software Foundation, Inc. - - This file is part of GnuPG. - - GnuPG is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 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. */ + * Copyright (C) 2005 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #include #include #include #include #include #include #include #include "util.h" typedef struct pkdbuf { unsigned char *buffer; size_t buffer_n; } pkdbuf_t; /* Retrieve the public key material for the RSA key, whose fingerprint is FPR, from gpg output, which can be read through the stream FP. The RSA modulus will be stored at the address of M and MLEN, the public exponent at E and ELEN. Returns zero on success, an error code on failure. Caller must release the allocated buffers at M and E if the function returns success. */ static gpg_error_t retrieve_key_material (FILE *fp, const char *hexkeyid, int *algorithm_id, pkdbuf_t **pkdbuf, size_t *pkdbuf_n) { pkdbuf_t *pkdbuf_new; pkdbuf_t *pkdbuf_tmp; size_t pkdbuf_new_n; gcry_error_t err = 0; char *line = NULL; /* read_line() buffer. */ size_t line_size = 0; /* Helper for for read_line. */ int found_key = 0; /* Helper to find a matching key. */ int id; unsigned char *buffer; size_t buffer_n; int i; pkdbuf_new = NULL; pkdbuf_new_n = 0; id = 0; /* Loop over all records until we have found the subkey corresponsing to the fingerprint. Inm general the first record should be the pub record, but we don't rely on that. Given that we only need to look at one key, it is sufficient to compare the keyid so that we don't need to look at "fpr" records. */ for (;;) { char *p; char *fields[6]; int nfields; size_t max_length; gcry_mpi_t mpi; max_length = 4096; i = read_line (fp, &line, &line_size, &max_length); if (!i) break; /* EOF. */ if (i < 0) { err = gpg_error_from_errno (errno); goto leave; /* Error. */ } if (!max_length) { err = gpg_error (GPG_ERR_TRUNCATED); goto leave; /* Line truncated - we better stop processing. */ } /* Parse the line into fields. */ for (nfields=0, p=line; p && nfields < DIM (fields); nfields++) { fields[nfields] = p; p = strchr (p, ':'); if (p) *(p++) = 0; } if (!nfields) continue; /* No fields at all - skip line. */ if (!found_key) { if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) && nfields > 4 && (((strlen (hexkeyid) == 8) && (strlen (fields[4]) == 16) && (! strcmp (fields[4] + 8, hexkeyid))) || ((strlen (hexkeyid) == 16) && (! strcmp (fields[4], hexkeyid))))) { found_key = 1; /* Save algorithm ID. */ id = atoi (fields[3]); } continue; } if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) break; /* Next key - stop. */ if ( strcmp (fields[0], "pkd") ) continue; /* Not a key data record. */ /* FIXME, necessary? */ i = atoi (fields[1]); if ((nfields < 4) || (i < 0)) { err = gpg_error (GPG_ERR_GENERAL); goto leave; } err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL); if (err) mpi = NULL; err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &buffer, &buffer_n, mpi); gcry_mpi_release (mpi); if (err) goto leave; pkdbuf_tmp = xrealloc (pkdbuf_new, sizeof (*pkdbuf_new) * (pkdbuf_new_n + 1)); if (pkdbuf_new != pkdbuf_tmp) pkdbuf_new = pkdbuf_tmp; pkdbuf_new[pkdbuf_new_n].buffer = buffer; pkdbuf_new[pkdbuf_new_n].buffer_n = buffer_n; pkdbuf_new_n++; } *algorithm_id = id; *pkdbuf = pkdbuf_new; *pkdbuf_n = pkdbuf_new_n; leave: if (err) if (pkdbuf_new) { for (i = 0; i < pkdbuf_new_n; i++) xfree (pkdbuf_new[i].buffer); xfree (pkdbuf_new); } xfree (line); return err; } int key_to_blob (unsigned char **blob, size_t *blob_n, const char *identifier, ...) { unsigned char *blob_new; size_t blob_new_n; unsigned char uint32_buffer[4]; u32 identifier_n; FILE *stream; va_list ap; int ret; pkdbuf_t *pkd; stream = tmpfile (); assert (stream); identifier_n = strlen (identifier); uint32_buffer[0] = identifier_n >> 24; uint32_buffer[1] = identifier_n >> 16; uint32_buffer[2] = identifier_n >> 8; uint32_buffer[3] = identifier_n >> 0; ret = fwrite (uint32_buffer, sizeof (uint32_buffer), 1, stream); assert (ret == 1); ret = fwrite (identifier, identifier_n, 1, stream); assert (ret == 1); va_start (ap, identifier); while (1) { pkd = va_arg (ap, pkdbuf_t *); if (! pkd) break; uint32_buffer[0] = pkd->buffer_n >> 24; uint32_buffer[1] = pkd->buffer_n >> 16; uint32_buffer[2] = pkd->buffer_n >> 8; uint32_buffer[3] = pkd->buffer_n >> 0; ret = fwrite (uint32_buffer, sizeof (uint32_buffer), 1, stream); assert (ret == 1); ret = fwrite (pkd->buffer, pkd->buffer_n, 1, stream); assert (ret == 1); } blob_new_n = ftell (stream); rewind (stream); blob_new = xmalloc (blob_new_n); ret = fread (blob_new, blob_new_n, 1, stream); assert (ret == 1); *blob = blob_new; *blob_n = blob_new_n; fclose (stream); return 0; } int main (int argc, char **argv) { const char *keyid; int algorithm_id; pkdbuf_t *pkdbuf; size_t pkdbuf_n; char *command; FILE *fp; int ret; gcry_error_t err; unsigned char *blob; size_t blob_n; struct b64state b64_state; const char *identifier; pkdbuf = NULL; pkdbuf_n = 0; algorithm_id = 0; /* (avoid cc warning) */ identifier = NULL; /* (avoid cc warning) */ assert (argc == 2); keyid = argv[1]; ret = asprintf (&command, "gpg --list-keys --with-colons --with-key-data '%s'", keyid); assert (ret > 0); fp = popen (command, "r"); assert (fp); err = retrieve_key_material (fp, keyid, &algorithm_id, &pkdbuf, &pkdbuf_n); assert (! err); assert ((algorithm_id == 1) || (algorithm_id == 17)); if (algorithm_id == 1) { identifier = "ssh-rsa"; ret = key_to_blob (&blob, &blob_n, identifier, &pkdbuf[0], &pkdbuf[1], NULL); } else if (algorithm_id == 17) { identifier = "ssh-dsa"; ret = key_to_blob (&blob, &blob_n, identifier, &pkdbuf[0], &pkdbuf[1], &pkdbuf[2], &pkdbuf[3], NULL); } assert (! ret); printf ("%s ", identifier); err = b64enc_start (&b64_state, stdout, ""); assert (! err); err = b64enc_write (&b64_state, blob, blob_n); assert (! err); err = b64enc_finish (&b64_state); assert (! err); printf (" COMMENT\n"); return 0; } diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c index 566f5747f..30759f9a4 100644 --- a/tools/gpgparsemail.c +++ b/tools/gpgparsemail.c @@ -1,766 +1,767 @@ /* gpgparsemail.c - Standalone crypto mail parser * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This utility prints an RFC8222, possible MIME structured, message in an annotated format with the first column having an indicator for the content of the line. Several options are available to scrutinize the message. S/MIME and OpenPGP support is included. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "rfc822parse.h" #define PGM "gpgparsemail" /* Option flags. */ static int verbose; static int debug; static int opt_crypto; /* Decrypt or verify messages. */ static int opt_no_header; /* Don't output the header lines. */ /* Structure used to communicate with the parser callback. */ struct parse_info_s { int show_header; /* Show the header lines. */ int show_data; /* Show the data lines. */ unsigned int skip_show; /* Temporary disable above for these number of lines. */ int show_data_as_note; /* The next data line should be shown as a note. */ int show_boundary; int nesting_level; int is_pkcs7; /* Old style S/MIME message. */ int gpgsm_mime; /* gpgsm shall be used from S/MIME. */ char *signing_protocol; int hashing_level; /* The nesting level we are hashing. */ int hashing; FILE *hash_file; FILE *sig_file; /* Signature part with MIME or full pkcs7 data if IS_PCKS7 is set. */ int verify_now; /* Flag set when all signature data is available. */ }; /* Print diagnostic message and exit with failure. */ static void die (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", PGM); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); exit (1); } /* Print diagnostic message. */ static void err (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", PGM); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); } static void * xmalloc (size_t n) { void *p = malloc (n); if (!p) die ("out of core: %s", strerror (errno)); return p; } /* static void * */ /* xcalloc (size_t n, size_t m) */ /* { */ /* void *p = calloc (n, m); */ /* if (!p) */ /* die ("out of core: %s", strerror (errno)); */ /* return p; */ /* } */ /* static void * */ /* xrealloc (void *old, size_t n) */ /* { */ /* void *p = realloc (old, n); */ /* if (!p) */ /* die ("out of core: %s", strerror (errno)); */ /* return p; */ /* } */ static char * xstrdup (const char *string) { void *p = malloc (strlen (string)+1); if (!p) die ("out of core: %s", strerror (errno)); strcpy (p, string); return p; } #ifndef HAVE_STPCPY static char * stpcpy (char *a,const char *b) { while (*b) *a++ = *b++; *a = 0; return (char*)a; } #endif static int run_gnupg (int smime, int sig_fd, int data_fd, int *close_list) { int rp[2]; pid_t pid; int i, c, is_status; unsigned int pos; char status_buf[10]; const char *cmd = smime? "gpgsm":"gpg"; FILE *fp; if (pipe (rp) == -1) die ("error creating a pipe: %s", strerror (errno)); pid = fork (); if (pid == -1) die ("error forking process: %s", strerror (errno)); if (!pid) { /* Child. */ char data_fd_buf[50]; int fd; /* Connect our signature fd to stdin. */ if (sig_fd != 0) { if (dup2 (sig_fd, 0) == -1) die ("dup2 stdin failed: %s", strerror (errno)); } /* Keep our data fd and format it for gpg/gpgsm use. */ if (data_fd == -1) *data_fd_buf = 0; else sprintf (data_fd_buf, "-&%d", data_fd); /* Send stdout to the bit bucket. */ fd = open ("/dev/null", O_WRONLY); if (fd == -1) die ("can't open `/dev/null': %s", strerror (errno)); if (fd != 1) { if (dup2 (fd, 1) == -1) die ("dup2 stderr failed: %s", strerror (errno)); } /* Connect stderr to our pipe. */ if (rp[1] != 2) { if (dup2 (rp[1], 2) == -1) die ("dup2 stderr failed: %s", strerror (errno)); } /* Close other files. */ for (i=0; (fd=close_list[i]) != -1; i++) if (fd > 2 && fd != data_fd) close (fd); errno = 0; execlp (cmd, cmd, "--enable-special-filenames", "--status-fd", "2", "--assume-base64", "--verify", "--", "-", data_fd == -1? NULL : data_fd_buf, NULL); die ("failed to exec the crypto command: %s", strerror (errno)); } /* Parent. */ close (rp[1]); fp = fdopen (rp[0], "r"); if (!fp) die ("can't fdopen pipe for reading: %s", strerror (errno)); pos = 0; is_status = 0; assert (sizeof status_buf > 9); while ((c=getc (fp)) != EOF) { if (pos < 9) status_buf[pos] = c; else { if (pos == 9) { is_status = !memcmp (status_buf, "[GNUPG:] ", 9); if (is_status) fputs ( "c ", stdout); else if (verbose) fputs ( "# ", stdout); fwrite (status_buf, 9, 1, stdout); } putchar (c); } if (c == '\n') { if (verbose && pos < 9) { fputs ( "# ", stdout); fwrite (status_buf, pos+1, 1, stdout); } pos = 0; } else pos++; } if (pos) { if (verbose && pos < 9) { fputs ( "# ", stdout); fwrite (status_buf, pos+1, 1, stdout); } putchar ('\n'); } fclose (fp); while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR) ; if (i == -1) die ("waiting for child failed: %s", strerror (errno)); return 0; } /* Verify the signature in the current temp files. */ static void verify_signature (struct parse_info_s *info) { int close_list[10]; if (info->is_pkcs7) { assert (!info->hash_file); assert (info->sig_file); rewind (info->sig_file); } else { assert (info->hash_file); assert (info->sig_file); rewind (info->hash_file); rewind (info->sig_file); } /* printf ("# Begin hashed data\n"); */ /* while ( (c=getc (info->hash_file)) != EOF) */ /* putchar (c); */ /* printf ("# End hashed data signature\n"); */ /* printf ("# Begin signature\n"); */ /* while ( (c=getc (info->sig_file)) != EOF) */ /* putchar (c); */ /* printf ("# End signature\n"); */ /* rewind (info->hash_file); */ /* rewind (info->sig_file); */ close_list[0] = -1; run_gnupg (1, fileno (info->sig_file), info->hash_file ? fileno (info->hash_file) : -1, close_list); } /* Prepare for a multipart/signed. FIELD_CTX is the parsed context of the content-type header.*/ static void mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg, rfc822parse_field_t field_ctx) { const char *s; s = rfc822parse_query_parameter (field_ctx, "protocol", 1); if (s) { printf ("h signed.protocol: %s\n", s); if (!strcmp (s, "application/pkcs7-signature") || !strcmp (s, "application/x-pkcs7-signature")) { if (info->gpgsm_mime) err ("note: ignoring nested pkcs7-signature"); else { info->gpgsm_mime = 1; free (info->signing_protocol); info->signing_protocol = xstrdup (s); } } else if (verbose) printf ("# this protocol is not supported\n"); } } /* Prepare for a multipart/encrypted. FIELD_CTX is the parsed context of the content-type header.*/ static void mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg, rfc822parse_field_t field_ctx) { const char *s; s = rfc822parse_query_parameter (field_ctx, "protocol", 0); if (s) printf ("h encrypted.protocol: %s\n", s); } /* Prepare for old-style pkcs7 messages. */ static void pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg, rfc822parse_field_t field_ctx) { const char *s; s = rfc822parse_query_parameter (field_ctx, "name", 0); if (s) printf ("h pkcs7.name: %s\n", s); if (info->is_pkcs7) err ("note: ignoring nested pkcs7 data"); else { info->is_pkcs7 = 1; if (opt_crypto) { assert (!info->sig_file); info->sig_file = tmpfile (); if (!info->sig_file) die ("error creating temp file: %s", strerror (errno)); } } } /* Print the event received by the parser for debugging as comment line. */ static void show_event (rfc822parse_event_t event) { const char *s; switch (event) { case RFC822PARSE_OPEN: s= "Open"; break; case RFC822PARSE_CLOSE: s= "Close"; break; case RFC822PARSE_CANCEL: s= "Cancel"; break; case RFC822PARSE_T2BODY: s= "T2Body"; break; case RFC822PARSE_FINISH: s= "Finish"; break; case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; case RFC822PARSE_BOUNDARY: s= "Boundary"; break; case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; case RFC822PARSE_PREAMBLE: s= "Preamble"; break; case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; default: s= "[unknown event]"; break; } printf ("# *** got RFC822 event %s\n", s); } /* This function is called by the parser to communicate events. This callback comminucates with the main program using a structure passed in OPAQUE. Should retrun 0 or set errno and return -1. */ static int message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg) { struct parse_info_s *info = opaque; if (debug) show_event (event); if (event == RFC822PARSE_OPEN) { /* Initialize for a new message. */ info->show_header = 1; } else if (event == RFC822PARSE_T2BODY) { rfc822parse_field_t ctx; ctx = rfc822parse_parse_field (msg, "Content-Type", -1); if (ctx) { const char *s1, *s2; s1 = rfc822parse_query_media_type (ctx, &s2); if (s1) { printf ("h media: %*s%s %s\n", info->nesting_level*2, "", s1, s2); if (info->gpgsm_mime == 3) { char *buf = xmalloc (strlen (s1) + strlen (s2) + 2); strcpy (stpcpy (stpcpy (buf, s1), "/"), s2); assert (info->signing_protocol); if (strcmp (buf, info->signing_protocol)) err ("invalid S/MIME structure; expected `%s', found `%s'", info->signing_protocol, buf); else { printf ("c begin_signature\n"); info->gpgsm_mime++; if (opt_crypto) { assert (!info->sig_file); info->sig_file = tmpfile (); if (!info->sig_file) die ("error creating temp file: %s", strerror (errno)); } } free (buf); } else if (!strcmp (s1, "multipart")) { if (!strcmp (s2, "signed")) mime_signed_begin (info, msg, ctx); else if (!strcmp (s2, "encrypted")) mime_encrypted_begin (info, msg, ctx); } else if (!strcmp (s1, "application") && (!strcmp (s2, "pkcs7-mime") || !strcmp (s2, "x-pkcs7-mime"))) pkcs7_begin (info, msg, ctx); } else printf ("h media: %*s none\n", info->nesting_level*2, ""); rfc822parse_release_field (ctx); } else printf ("h media: %*stext plain [assumed]\n", info->nesting_level*2, ""); info->show_header = 0; info->show_data = 1; info->skip_show = 1; } else if (event == RFC822PARSE_PREAMBLE) info->show_data_as_note = 1; else if (event == RFC822PARSE_LEVEL_DOWN) { printf ("b down\n"); info->nesting_level++; } else if (event == RFC822PARSE_LEVEL_UP) { printf ("b up\n"); if (info->nesting_level) info->nesting_level--; else err ("invalid structure (bad nesting level)"); } else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY) { info->show_data = 0; info->show_boundary = 1; if (event == RFC822PARSE_BOUNDARY) { info->show_header = 1; info->skip_show = 1; printf ("b part\n"); } else printf ("b last\n"); if (info->gpgsm_mime == 2 && info->nesting_level == info->hashing_level) { printf ("c end_hash\n"); info->gpgsm_mime++; info->hashing = 0; } else if (info->gpgsm_mime == 4) { printf ("c end_signature\n"); info->verify_now = 1; } } else if (event == RFC822PARSE_BEGIN_HEADER) { if (info->gpgsm_mime == 1) { printf ("c begin_hash\n"); info->hashing = 1; info->hashing_level = info->nesting_level; info->gpgsm_mime++; if (opt_crypto) { assert (!info->hash_file); info->hash_file = tmpfile (); if (!info->hash_file) die ("failed to create temporary file: %s", strerror (errno)); } } } return 0; } /* Read a message from FP and process it according to the global options. */ static void parse_message (FILE *fp) { char line[5000]; size_t length; rfc822parse_t msg; unsigned int lineno = 0; int no_cr_reported = 0; struct parse_info_s info; memset (&info, 0, sizeof info); msg = rfc822parse_open (message_cb, &info); if (!msg) die ("can't open parser: %s", strerror (errno)); /* Fixme: We should not use fgets becuase it can't cope with embedded nul characters. */ while (fgets (line, sizeof (line), fp)) { lineno++; if (lineno == 1 && !strncmp (line, "From ", 5)) continue; /* We better ignore a leading From line. */ length = strlen (line); if (length && line[length - 1] == '\n') line[--length] = 0; else err ("line number %u too long or last line not terminated", lineno); if (length && line[length - 1] == '\r') line[--length] = 0; else if (verbose && !no_cr_reported) { err ("non canonical ended line detected (line %u)", lineno); no_cr_reported = 1; } if (rfc822parse_insert (msg, line, length)) die ("parser failed: %s", strerror (errno)); if (info.hashing) { /* Delay hashing of the CR/LF because the last line ending belongs to the next boundary. */ if (debug) printf ("# hashing %s`%s'\n", info.hashing==2?"CR,LF+":"", line); if (opt_crypto) { if (info.hashing == 2) fputs ("\r\n", info.hash_file); fputs (line, info.hash_file); if (ferror (info.hash_file)) die ("error writing to temporary file: %s", strerror (errno)); } info.hashing = 2; } if (info.sig_file && opt_crypto) { if (info.verify_now) { verify_signature (&info); if (info.hash_file) fclose (info.hash_file); info.hash_file = NULL; fclose (info.sig_file); info.sig_file = NULL; info.gpgsm_mime = 0; info.is_pkcs7 = 0; } else { fputs (line, info.sig_file); fputs ("\r\n", info.sig_file); if (ferror (info.sig_file)) die ("error writing to temporary file: %s", strerror (errno)); } } if (info.show_boundary) { if (!opt_no_header) printf (":%s\n", line); info.show_boundary = 0; } if (info.skip_show) info.skip_show--; else if (info.show_data) { if (info.show_data_as_note) { if (verbose) printf ("# DATA: %s\n", line); info.show_data_as_note = 0; } else printf (" %s\n", line); } else if (info.show_header && !opt_no_header) printf (".%s\n", line); } if (info.sig_file && opt_crypto && info.is_pkcs7) { verify_signature (&info); fclose (info.sig_file); info.sig_file = NULL; info.is_pkcs7 = 0; } rfc822parse_close (msg); } int main (int argc, char **argv) { int last_argc = -1; if (argc) { argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--")) { argc--; argv++; break; } else if (!strcmp (*argv, "--help")) { puts ( "Usage: " PGM " [OPTION] [FILE]\n" "Parse a mail message into an annotated format.\n\n" " --crypto decrypt or verify messages\n" " --no-header don't output the header lines\n" " --verbose enable extra informational output\n" " --debug enable additional debug output\n" " --help display this help and exit\n\n" "With no FILE, or when FILE is -, read standard input.\n\n" "WARNING: This tool is under development.\n" " The semantics may change without notice\n\n" "Report bugs to ."); exit (0); } else if (!strcmp (*argv, "--verbose")) { verbose = 1; argc--; argv++; } else if (!strcmp (*argv, "--debug")) { verbose = debug = 1; argc--; argv++; } else if (!strcmp (*argv, "--crypto")) { opt_crypto = 1; argc--; argv++; } else if (!strcmp (*argv, "--no-header")) { opt_no_header = 1; argc--; argv++; } } if (argc > 1) die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n"); signal (SIGPIPE, SIG_IGN); if (argc && strcmp (*argv, "-")) { FILE *fp = fopen (*argv, "rb"); if (!fp) die ("can't open `%s': %s", *argv, strerror (errno)); parse_message (fp); fclose (fp); } else parse_message (stdin); return 0; } /* Local Variables: compile-command: "gcc -Wall -g -o gpgparsemail rfc822parse.c gpgparsemail.c" End: */ diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c index 82f6a8bb5..636df10c6 100644 --- a/tools/no-libgcrypt.c +++ b/tools/no-libgcrypt.c @@ -1,111 +1,112 @@ /* no-libgcrypt.c - Replacement functions for libgcrypt. * Copyright (C) 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include #include #include #include #include #include "../common/util.h" #include "i18n.h" /* Replace libgcrypt's malloc functions which are used by ../jnlib/libjnlib.a . ../common/util.h defines macros to map them to xmalloc etc. */ static void out_of_core (void) { log_fatal (_("error allocating enough memory: %s\n"), strerror (errno)); } void * gcry_malloc (size_t n) { return malloc (n); } void * gcry_xmalloc (size_t n) { void *p = malloc (n); if (!p) out_of_core (); return p; } char * gcry_strdup (const char *string) { return malloc (strlen (string)+1); } void * gcry_realloc (void *a, size_t n) { return realloc (a, n); } void * gcry_xrealloc (void *a, size_t n) { void *p = realloc (a, n); if (!p) out_of_core (); return p; } void * gcry_calloc (size_t n, size_t m) { return calloc (n, m); } void * gcry_xcalloc (size_t n, size_t m) { void *p = calloc (n, m); if (!p) out_of_core (); return p; } char * gcry_xstrdup (const char *string) { void *p = malloc (strlen (string)+1); if (!p) out_of_core (); strcpy( p, string ); return p; } void gcry_free (void *a) { if (a) free (a); } diff --git a/tools/symcryptrun.c b/tools/symcryptrun.c index 075e0b444..406cbb2a2 100644 --- a/tools/symcryptrun.c +++ b/tools/symcryptrun.c @@ -1,1072 +1,1073 @@ /* symcryptrun.c - Tool to call simple symmetric encryption tools. * Copyright (C) 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* Sometimes simple encryption tools are already in use for a long time and there is a desire to integrate them into the GnuPG framework. The protocols and encryption methods might be non-standard or not even properly documented, so that a full-fledged encryption tool with an interface like gpg is not doable. This simple wrapper program provides a solution: It operates by calling the encryption/decryption module and providing the passphrase for a key (or even the key directly) using the standard pinentry mechanism through gpg-agent. */ /* This program is invoked in the following way: symcryptrun --class CLASS --program PROGRAM --keyfile KEYFILE \ [--decrypt | --encrypt] For encryption, the plain text must be provided on STDIN, and the ciphertext will be output to STDOUT. For decryption vice versa. CLASS can currently only be "confucius". PROGRAM must be the path to the crypto engine. KEYFILE must contain the secret key, which may be protected by a passphrase. The passphrase is retrieved via the pinentry program. The GPG Agent _must_ be running before starting symcryptrun. The possible exit status codes: 0 Success 1 Some error occured 2 No valid passphrase was provided 3 The operation was canceled by the user Other classes may be added in the future. */ #define SYMC_BAD_PASSPHRASE 2 #define SYMC_CANCELED 3 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #include #define JNLIB_NEED_LOG_LOGV #include "i18n.h" #include "../common/util.h" #include "mkdtemp.h" /* FIXME: Bah. For spwq_secure_free. */ #define SIMPLE_PWQUERY_IMPLEMENTATION 1 #include "../common/simple-pwquery.h" /* 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); } /* From simple-gettext.c. */ /* We assume to have `unsigned long int' value with at least 32 bits. */ #define HASHWORDBITS 32 /* The so called `hashpjw' function by P.J. Weinberger [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools, 1986, 1987 Bell Telephone Laboratories, Inc.] */ static __inline__ ulong hash_string( const char *str_param ) { unsigned long int hval, g; const char *str = str_param; hval = 0; while (*str != '\0') { hval <<= 4; hval += (unsigned long int) *str++; g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4)); if (g != 0) { hval ^= g >> (HASHWORDBITS - 8); hval ^= g; } } return hval; } /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oNoVerbose = 500, oOptions, oNoOptions, oLogFile, oHomedir, oClass, oProgram, oKeyfile, oDecrypt, oEncrypt, oInput }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@\nCommands:\n ") }, { oDecrypt, "decrypt", 0, N_("decryption modus") }, { oEncrypt, "encrypt", 0, N_("encryption modus") }, { 302, NULL, 0, N_("@\nOptions:\n ") }, { oClass, "class", 2, N_("tool class (confucius)") }, { oProgram, "program", 2, N_("program filename") }, { oKeyfile, "keyfile", 2, N_("secret key file (required)") }, { oInput, "inputfile", 2, N_("input file name (default stdin)") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oLogFile, "log-file", 2, N_("use a log file for the server") }, { oOptions, "options" , 2, N_("|FILE|read options from FILE") }, /* Hidden options. */ { oNoVerbose, "no-verbose", 0, "@" }, { oHomedir, "homedir", 2, "@" }, { oNoOptions, "no-options", 0, "@" },/* shortcut for --options /dev/null */ {0} }; /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ int quiet; /* Be extra quiet. */ const char *homedir; /* Configuration directory name */ char *class; char *program; char *keyfile; char *input; } opt; /* Print usage information and and provide strings for help. */ static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "symcryptrun (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: symcryptrun [options] (-h for help)"); break; case 41: p = _("Syntax: symcryptrun --class CLASS --program PROGRAM " "--keyfile KEYFILE [options...] COMMAND [inputfile]\n" "Call a simple symmetric encryption tool\n"); break; case 31: p = "\nHome: "; break; case 32: p = opt.homedir; break; case 33: p = "\n"; break; default: p = NULL; break; } return p; } /* Initialize the gettext system. */ static void i18n_init(void) { #ifdef USE_SIMPLE_GETTEXT set_gettext_file (PACKAGE_GT); #else # ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE_GT, LOCALEDIR); textdomain (PACKAGE_GT); # endif #endif } /* This is in the GNU C library in unistd.h. */ #ifndef TEMP_FAILURE_RETRY /* Evaluate EXPRESSION, and repeat as long as it returns -1 with `errno' set to EINTR. */ # define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) #endif /* Unlink a file, and shred it if SHRED is true. */ int remove_file (char *name, int shred) { if (!shred) return unlink (name); else { int status; pid_t pid; pid = fork (); if (pid == 0) { /* Child. */ /* -f forces file to be writable, and -u unlinks it afterwards. */ char *args[] = { SHRED, "-uf", name, NULL }; execv (SHRED, args); _exit (127); } else if (pid < 0) { /* Fork failed. */ status = -1; } else { /* Parent. */ if (TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)) != pid) status = -1; } if (!WIFEXITED (status)) { log_error (_("%s on %s aborted with status %i\n"), SHRED, name, status); unlink (name); return 1; } else if (WEXITSTATUS (status)) { log_error (_("%s on %s failed with status %i\n"), SHRED, name, WEXITSTATUS (status)); unlink (name); return 1; } return 0; } } /* Class Confucius. "Don't worry that other people don't know you; worry that you don't know other people." Analects--1.16. */ /* Create temporary directory with mode 0700. Returns a dynamically allocated string with the filename of the directory. */ static char * confucius_mktmpdir (void) { char *name; name = strdup ("/tmp/gpg-XXXXXX"); if (!name || !mkdtemp (name)) { log_error (_("can't create temporary directory `%s': %s\n"), name?name:"", strerror (errno)); return NULL; } return name; } /* Buffer size for I/O operations. */ #define CONFUCIUS_BUFSIZE 4096 /* Buffer size for output lines. */ #define CONFUCIUS_LINESIZE 4096 /* Copy the file IN to OUT, either of which may be "-". If PLAIN is true, and the copying fails, and OUT is not STDOUT, then shred the file instead unlinking it. */ static int confucius_copy_file (char *infile, char *outfile, int plain) { FILE *in; int in_is_stdin = 0; FILE *out; int out_is_stdout = 0; char data[CONFUCIUS_BUFSIZE]; ssize_t data_len; if (infile[0] == '-' && infile[1] == '\0') { /* FIXME: Is stdin in binary mode? */ in = stdin; in_is_stdin = 1; } else { in = fopen (infile, "rb"); if (!in) { log_error (_("could not open %s for writing: %s\n"), infile, strerror (errno)); return 1; } } if (outfile[0] == '-' && outfile[1] == '\0') { /* FIXME: Is stdout in binary mode? */ out = stdout; out_is_stdout = 1; } else { out = fopen (outfile, "wb"); if (!out) { log_error (_("could not open %s for writing: %s\n"), infile, strerror (errno)); return 1; } } /* Now copy the data. */ while ((data_len = fread (data, 1, sizeof (data), in)) > 0) { if (fwrite (data, 1, data_len, out) != data_len) { log_error (_("error writing to %s: %s\n"), outfile, strerror (errno)); goto copy_err; } } if (data_len < 0 || ferror (in)) { log_error (_("error reading from %s: %s\n"), infile, strerror (errno)); goto copy_err; } /* Close IN if appropriate. */ if (!in_is_stdin && fclose (in) && ferror (in)) { log_error (_("error closing %s: %s\n"), infile, strerror (errno)); goto copy_err; } /* Close OUT if appropriate. */ if (!out_is_stdout && fclose (out) && ferror (out)) { log_error (_("error closing %s: %s\n"), infile, strerror (errno)); goto copy_err; } return 0; copy_err: if (!out_is_stdout) remove_file (outfile, plain); return 1; } /* Get a passphrase in secure storage (if possible). If AGAIN is true, then this is a repeated attempt. If CANCELED is not a null pointer, it will be set to true or false, depending on if the user canceled the operation or not. On error (including cancelation), a null pointer is returned. The passphrase must be deallocated with confucius_drop_pass. CACHEID is the ID to be used for passphrase caching and can be NULL to disable caching. */ char * confucius_get_pass (const char *cacheid, int again, int *canceled) { int err; char *pw; #ifdef HAVE_LANGINFO_CODESET char *orig_codeset = NULL; #endif if (canceled) *canceled = 0; #ifdef ENABLE_NLS /* The Assuan agent protocol requires us to transmit utf-8 strings */ orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); #ifdef HAVE_LANGINFO_CODESET if (!orig_codeset) orig_codeset = nl_langinfo (CODESET); #endif if (orig_codeset && !strcmp (orig_codeset, "UTF-8")) orig_codeset = NULL; if (orig_codeset) { /* We only switch when we are able to restore the codeset later. */ orig_codeset = xstrdup (orig_codeset); if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8")) orig_codeset = NULL; } #endif pw = simple_pwquery (cacheid, again ? _("does not match - try again"):NULL, _("Passphrase:"), NULL, &err); #ifdef ENABLE_NLS if (orig_codeset) { bind_textdomain_codeset (PACKAGE_GT, orig_codeset); xfree (orig_codeset); } #endif if (!pw) { if (err) log_error (_("error while asking for the passphrase: %s\n"), gpg_strerror (err)); else { log_info (_("cancelled\n")); if (canceled) *canceled = 1; } } return pw; } /* Drop a passphrase retrieved with confucius_get_pass. */ void confucius_drop_pass (char *pass) { if (pass) spwq_secure_free (pass); } /* Run a confucius crypto engine. If MODE is oEncrypt, encryption is requested. If it is oDecrypt, decryption is requested. INFILE and OUTFILE are the temporary files used in the process. */ int confucius_process (int mode, char *infile, char *outfile, int argc, char *argv[]) { char **args; int cstderr[2]; int master; int slave; int res; pid_t pid; pid_t wpid; int tries = 0; char cacheid[40]; signal (SIGPIPE, SIG_IGN); if (!opt.program) { log_error (_("no --program option provided\n")); return 1; } if (mode != oDecrypt && mode != oEncrypt) { log_error (_("only --decrypt and --encrypt are supported\n")); return 1; } if (!opt.keyfile) { log_error (_("no --keyfile option provided\n")); return 1; } /* Generate a hash from the keyfile name for caching. */ snprintf (cacheid, sizeof (cacheid), "confucius:%lu", hash_string (opt.keyfile)); cacheid[sizeof (cacheid) - 1] = '\0'; args = malloc (sizeof (char *) * (10 + argc)); if (!args) { log_error (_("cannot allocate args vector\n")); return 1; } args[0] = opt.program; args[1] = (mode == oEncrypt) ? "-m1" : "-m2"; args[2] = "-q"; args[3] = infile; args[4] = "-z"; args[5] = outfile; args[6] = "-s"; args[7] = opt.keyfile; args[8] = (mode == oEncrypt) ? "-af" : "-f"; args[9 + argc] = NULL; while (argc--) args[9 + argc] = argv[argc]; if (pipe (cstderr) < 0) { log_error (_("could not create pipe: %s\n"), strerror (errno)); free (args); return 1; } if (openpty (&master, &slave, NULL, NULL, NULL) == -1) { log_error (_("could not create pty: %s\n"), strerror (errno)); close (cstderr[0]); close (cstderr[1]); free (args); return -1; } /* We don't want to deal with the worst case scenarios. */ assert (master > 2); assert (slave > 2); assert (cstderr[0] > 2); assert (cstderr[1] > 2); pid = fork (); if (pid < 0) { log_error (_("could not fork: %s\n"), strerror (errno)); close (master); close (slave); close (cstderr[0]); close (cstderr[1]); free (args); return 1; } else if (pid == 0) { /* Child. */ /* Close the parent ends. */ close (master); close (cstderr[0]); /* Change controlling terminal. */ if (login_tty (slave)) { /* It's too early to output a debug message. */ _exit (1); } dup2 (cstderr[1], 2); close (cstderr[1]); /* Now kick off the engine program. */ execv (opt.program, args); log_error (_("execv failed: %s\n"), strerror (errno)); _exit (1); } else { /* Parent. */ char buffer[CONFUCIUS_LINESIZE]; int buffer_len = 0; fd_set fds; int slave_closed = 0; int stderr_closed = 0; close (slave); close (cstderr[1]); free (args); /* Listen on the output FDs. */ do { FD_ZERO (&fds); if (!slave_closed) FD_SET (master, &fds); if (!stderr_closed) FD_SET (cstderr[0], &fds); res = select (FD_SETSIZE, &fds, NULL, NULL, NULL); if (res < 0) { log_error (_("select failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); close (master); close (cstderr[0]); return 1; } if (FD_ISSET (cstderr[0], &fds)) { /* We got some output on stderr. This is just passed through via the logging facility. */ res = read (cstderr[0], &buffer[buffer_len], sizeof (buffer) - buffer_len - 1); if (res < 0) { log_error (_("read failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); close (master); close (cstderr[0]); return 1; } else { char *newline; buffer_len += res; for (;;) { buffer[buffer_len] = '\0'; newline = strchr (buffer, '\n'); if (newline) { *newline = '\0'; log_error ("%s\n", buffer); buffer_len -= newline + 1 - buffer; memmove (buffer, newline + 1, buffer_len); } else if (buffer_len == sizeof (buffer) - 1) { /* Overflow. */ log_error ("%s\n", buffer); buffer_len = 0; } else break; } if (res == 0) stderr_closed = 1; } } else if (FD_ISSET (master, &fds)) { char data[512]; res = read (master, data, sizeof (data)); if (res < 0) { if (errno == EIO) { /* Slave-side close leads to readable fd and EIO. */ slave_closed = 1; } else { log_error (_("pty read failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); close (master); close (cstderr[0]); return 1; } } else if (res == 0) /* This never seems to be what happens on slave-side close. */ slave_closed = 1; else { /* Check for password prompt. */ if (data[res - 1] == ':') { char *pass; int canceled; /* If this is not the first attempt, the passphrase seems to be wrong, so clear the cache. */ if (tries) simple_pwclear (cacheid); pass = confucius_get_pass (cacheid, tries ? 1 : 0, &canceled); if (!pass) { kill (pid, SIGTERM); close (master); close (cstderr[0]); return canceled ? SYMC_CANCELED : 1; } write (master, pass, strlen (pass)); write (master, "\n", 1); confucius_drop_pass (pass); tries++; } } } } while (!stderr_closed || !slave_closed); close (master); close (cstderr[0]); wpid = waitpid (pid, &res, 0); if (wpid < 0) { log_error (_("waitpid failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); /* State of cached password is unclear. Just remove it. */ simple_pwclear (cacheid); return 1; } else { /* Shouldn't happen, as we don't use WNOHANG. */ assert (wpid != 0); if (!WIFEXITED (res)) { log_error (_("child aborted with status %i\n"), res); /* State of cached password is unclear. Just remove it. */ simple_pwclear (cacheid); return 1; } if (WEXITSTATUS (res)) { /* The passphrase was wrong. Remove it from the cache. */ simple_pwclear (cacheid); /* We probably exceeded our number of attempts at guessing the password. */ if (tries >= 3) return SYMC_BAD_PASSPHRASE; else return 1; } return 0; } } /* Not reached. */ } /* Class confucius main program. If MODE is oEncrypt, encryption is requested. If it is oDecrypt, decryption is requested. The other parameters are taken from the global option data. */ int confucius_main (int mode, int argc, char *argv[]) { int res; char *tmpdir; char *infile; int infile_from_stdin = 0; char *outfile; tmpdir = confucius_mktmpdir (); if (!tmpdir) return 1; if (opt.input && !(opt.input[0] == '-' && opt.input[1] == '\0')) infile = xstrdup (opt.input); else { infile_from_stdin = 1; /* TMPDIR + "/" + "in" + "\0". */ infile = malloc (strlen (tmpdir) + 1 + 2 + 1); if (!infile) { log_error (_("cannot allocate infile string: %s\n"), strerror (errno)); rmdir (tmpdir); return 1; } strcpy (infile, tmpdir); strcat (infile, "/in"); } /* TMPDIR + "/" + "out" + "\0". */ outfile = malloc (strlen (tmpdir) + 1 + 3 + 1); if (!outfile) { log_error (_("cannot allocate outfile string: %s\n"), strerror (errno)); free (infile); rmdir (tmpdir); return 1; } strcpy (outfile, tmpdir); strcat (outfile, "/out"); if (infile_from_stdin) { /* Create INFILE and fill it with content. */ res = confucius_copy_file ("-", infile, mode == oEncrypt); if (res) { free (outfile); free (infile); rmdir (tmpdir); return res; } } /* Run the engine and thus create the output file, handling passphrase retrieval. */ res = confucius_process (mode, infile, outfile, argc, argv); if (res) { remove_file (outfile, mode == oDecrypt); if (infile_from_stdin) remove_file (infile, mode == oEncrypt); free (outfile); free (infile); rmdir (tmpdir); return res; } /* Dump the output file to stdout. */ res = confucius_copy_file (outfile, "-", mode == oDecrypt); if (res) { remove_file (outfile, mode == oDecrypt); if (infile_from_stdin) remove_file (infile, mode == oEncrypt); free (outfile); free (infile); rmdir (tmpdir); return res; } remove_file (outfile, mode == oDecrypt); if (infile_from_stdin) remove_file (infile, mode == oEncrypt); free (outfile); free (infile); rmdir (tmpdir); return 0; } /* symcryptrun's entry point. */ int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; unsigned configlineno; int mode = 0; int res; char *logfile = NULL; int default_config = 1; set_strusage (my_strusage); log_set_prefix ("symcryptrun", 1); /* Try to auto set the character set. */ set_native_charset (NULL); i18n_init(); opt.homedir = default_homedir (); /* Check whether we have a config file given 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 == 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; } if (default_config) configname = make_filename (opt.homedir, "symcryptrun.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) { log_error (_("option file `%s': %s\n"), configname, strerror(errno) ); exit(1); } xfree (configname); configname = NULL; } default_config = 0; } /* Parse the command line. */ while (optfile_parse (configfp, configname, &configlineno, &pargs, opts)) { switch (pargs.r_opt) { case oDecrypt: mode = oDecrypt; break; case oEncrypt: mode = oEncrypt; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oClass: opt.class = pargs.r.ret_str; break; case oProgram: opt.program = pargs.r.ret_str; break; case oKeyfile: opt.keyfile = pargs.r.ret_str; break; case oInput: opt.input = pargs.r.ret_str; break; case oLogFile: logfile = pargs.r.ret_str; 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 oNoOptions: break; /* no-options */ case oHomedir: /* Ignore this option here. */; break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (!mode) log_error (_("either %s or %s must be given\n"), "--decrypt", "--encrypt"); if (log_get_errorcount (0)) exit (1); if (logfile) log_set_file (logfile); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); 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); if (!opt.class) { log_error (_("no class provided\n")); res = 1; } else if (!strcmp (opt.class, "confucius")) res = confucius_main (mode, argc, argv); else { log_error (_("class %s is not supported\n"), opt.class); res = 1; } return res; } diff --git a/tools/watchgnupg.c b/tools/watchgnupg.c index 6cb570fbc..051ca50fe 100644 --- a/tools/watchgnupg.c +++ b/tools/watchgnupg.c @@ -1,401 +1,402 @@ /* watchgnupg.c - Socket server for GnuPG logs * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define PGM "watchgnupg" /* Allow for a standalone build. */ #ifdef VERSION #define MYVERSION_LINE PGM " (GnuPG) " VERSION #define BUGREPORT_LINE "\nReport bugs to .\n" #else #define MYVERSION_LINE PGM #define BUGREPORT_LINE "" #endif #ifndef PF_LOCAL # ifdef PF_UNIX # define PF_LOCAL PF_UNIX # else # define PF_LOCAL AF_UNIX # endif # ifndef AF_LOCAL # define AF_LOCAL AF_UNIX # endif #endif static int verbose; static void die (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", PGM); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); exit (1); } /* static void */ /* err (const char *format, ...) */ /* { */ /* va_list arg_ptr; */ /* fflush (stdout); */ /* fprintf (stderr, "%s: ", PGM); */ /* va_start (arg_ptr, format); */ /* vfprintf (stderr, format, arg_ptr); */ /* va_end (arg_ptr); */ /* putc ('\n', stderr); */ /* } */ static void * xmalloc (size_t n) { void *p = malloc (n); if (!p) die ("out of core"); return p; } static void * xcalloc (size_t n, size_t m) { void *p = calloc (n, m); if (!p) die ("out of core"); return p; } static void * xrealloc (void *old, size_t n) { void *p = realloc (old, n); if (!p) die ("out of core"); return p; } struct client_s { struct client_s *next; int fd; size_t size; /* Allocated size of buffer. */ size_t len; /* Current length of buffer. */ unsigned char *buffer; /* Buffer to with data already read. */ }; typedef struct client_s *client_t; static void print_fd_and_time (int fd) { struct tm *tp; time_t atime = time (NULL); tp = localtime (&atime); printf ("%3d - %04d-%02d-%02d %02d:%02d:%02d ", fd, 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec ); } /* Print LINE for the client identified by C. Calling this function witgh LINE set to NULL, will flush the internal buffer. */ static void print_line (client_t c, const char *line) { const char *s; size_t n; if (!line) { if (c->buffer && c->len) { print_fd_and_time (c->fd); fwrite (c->buffer, c->len, 1, stdout); putc ('\n', stdout); c->len = 0; } return; } while ((s = strchr (line, '\n'))) { print_fd_and_time (c->fd); if (c->buffer && c->len) { fwrite (c->buffer, c->len, 1, stdout); c->len = 0; } fwrite (line, s - line + 1, 1, stdout); line = s + 1; } n = strlen (line); if (n) { if (c->len + n >= c->size) { c->size += ((n + 255) & ~255); c->buffer = (c->buffer ? xrealloc (c->buffer, c->size) : xmalloc (c->size)); } memcpy (c->buffer + c->len, line, n); c->len += n; } } static void print_version (int with_help) { fputs (MYVERSION_LINE "\n" "Copyright (C) 2004 Free Software Foundation, Inc.\n" "This program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions. See the file COPYING for details.\n", stdout); if (with_help) fputs ("\n" "Usage: " PGM " [OPTIONS] SOCKETNAME\n" "Open the local socket SOCKETNAME and display log messages\n" "\n" " --force delete an already existing socket file\n" " --verbose enable extra informational output\n" " --version print version of the program and exit\n" " --help display this help and exit\n" BUGREPORT_LINE, stdout ); exit (0); } int main (int argc, char **argv) { int last_argc = -1; int force = 0; struct sockaddr_un srvr_addr; socklen_t addrlen; int server; int flags; client_t client_list = NULL; if (argc) { argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--")) { argc--; argv++; break; } else if (!strcmp (*argv, "--version")) print_version (0); else if (!strcmp (*argv, "--help")) print_version (1); else if (!strcmp (*argv, "--verbose")) { verbose = 1; argc--; argv++; } else if (!strcmp (*argv, "--force")) { force = 1; argc--; argv++; } } if (argc != 1) { fprintf (stderr, "usage: " PGM " socketname\n"); exit (1); } if (verbose) fprintf (stderr, "opening socket `%s'\n", *argv); setvbuf (stdout, NULL, _IOLBF, 0); server = socket (PF_LOCAL, SOCK_STREAM, 0); if (server == -1) die ("socket() failed: %s\n", strerror (errno)); /* We better set the listening socket to non-blocking so that we don't get bitten by race conditions in accept. The should not happen for Unix Domain sockets but well, shit happens. */ flags = fcntl (server, F_GETFL, 0); if (flags == -1) die ("fcntl (F_GETFL) failed: %s\n", strerror (errno)); if ( fcntl (server, F_SETFL, (flags | O_NONBLOCK)) == -1) die ("fcntl (F_SETFL) failed: %s\n", strerror (errno)); memset (&srvr_addr, 0, sizeof srvr_addr); srvr_addr.sun_family = AF_LOCAL; strncpy (srvr_addr.sun_path, *argv, sizeof (srvr_addr.sun_path) - 1); srvr_addr.sun_path[sizeof (srvr_addr.sun_path) - 1] = 0; addrlen = (offsetof (struct sockaddr_un, sun_path) + strlen (srvr_addr.sun_path) + 1); again: if (bind (server, (struct sockaddr *) &srvr_addr, addrlen)) { if (errno == EADDRINUSE && force) { force = 0; remove (srvr_addr.sun_path); goto again; } die ("bind to `%s' failed: %s\n", *argv, strerror (errno)); } if (listen (server, 5)) die ("listen failed: %s\n", strerror (errno)); for (;;) { fd_set rfds; int max_fd; client_t client; /* Usually we don't have that many connections, thus it is okay to set them allways from scratch and don't maintain an active fd_set. */ FD_ZERO (&rfds); FD_SET (server, &rfds); max_fd = server; for (client = client_list; client; client = client->next) if (client->fd != -1) { FD_SET (client->fd, &rfds); if (client->fd > max_fd) max_fd = client->fd; } if (select (max_fd + 1, &rfds, NULL, NULL, NULL) <= 0) continue; /* Ignore any errors. */ if (FD_ISSET (server, &rfds)) /* New connection. */ { struct sockaddr_un clnt_addr; int fd; addrlen = sizeof clnt_addr; fd = accept (server, (struct sockaddr *) &clnt_addr, &addrlen); if (fd == -1) { printf ("[accepting connection failed: %s]\n", strerror (errno)); } else if (fd >= FD_SETSIZE) { close (fd); printf ("[connection request denied: too many connections]\n"); } else { for (client = client_list; client && client->fd != -1; client = client->next) ; if (!client) { client = xcalloc (1, sizeof *client); client->next = client_list; client_list = client; } client->fd = fd; printf ("[client at fd %d connected]\n", client->fd); } } for (client = client_list; client; client = client->next) if (client->fd != -1 && FD_ISSET (client->fd, &rfds)) { char line[256]; int n; n = read (client->fd, line, sizeof line - 1); if (n < 0) { int save_errno = errno; print_line (client, NULL); /* flush */ printf ("[client at fd %d read error: %s]\n", client->fd, strerror (save_errno)); close (client->fd); client->fd = -1; } else if (!n) { print_line (client, NULL); /* flush */ close (client->fd); printf ("[client at fd %d disconnected]\n", client->fd); client->fd = -1; } else { line[n] = 0; print_line (client, line); } } } return 0; } /* Local Variables: compile-command: "gcc -Wall -g -o watchgnupg watchgnupg.c" End: */