diff --git a/autogen.sh b/autogen.sh index 5dc8669e6..ab4c7dad8 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,271 +1,271 @@ #! /bin/sh # Run this to generate all the initial makefiles, etc. # # Copyright (C) 2003 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. configure_ac="configure.ac" cvtver () { awk 'NR==1 {split($NF,A,".");X=1000000*A[1]+1000*A[2]+A[3];print X;exit 0}' } check_version () { if [ $(( `("$1" --version || echo "0") | cvtver` >= $2 )) = 1 ]; then return 0 fi echo "**Error**: "\`$1\'" not installed or too old." >&2 echo ' Version '$3' or newer is required.' >&2 [ -n "$4" ] && echo ' Note that this is part of '\`$4\''.' >&2 DIE="yes" return 1 } # Allow to override the default tool names AUTOCONF=${AUTOCONF_PREFIX}${AUTOCONF:-autoconf}${AUTOCONF_SUFFIX} AUTOHEADER=${AUTOCONF_PREFIX}${AUTOHEADER:-autoheader}${AUTOCONF_SUFFIX} AUTOMAKE=${AUTOMAKE_PREFIX}${AUTOMAKE:-automake}${AUTOMAKE_SUFFIX} ACLOCAL=${AUTOMAKE_PREFIX}${ACLOCAL:-aclocal}${AUTOMAKE_SUFFIX} GETTEXT=${GETTEXT_PREFIX}${GETTEXT:-gettext}${GETTEXT_SUFFIX} MSGMERGE=${GETTEXT_PREFIX}${MSGMERGE:-msgmerge}${GETTEXT_SUFFIX} DIE=no FORCE= if test x"$1" = x"--force"; then FORCE=" --force" shift fi # Begin list of optional variables sourced from ~/.gnupg-autogen.rc w32_toolprefixes= w32_extraoptions= w32ce_toolprefixes= w32ce_extraoptions= amd64_toolprefixes= # End list of optional variables sourced from ~/.gnupg-autogen.rc # What follows are variables which are sourced but default to # environment variables or lacking them hardcoded values. #w32root= #w32ce_root= #amd64root= if [ -f "$HOME/.gnupg-autogen.rc" ]; then echo "sourcing extra definitions from $HOME/.gnupg-autogen.rc" . "$HOME/.gnupg-autogen.rc" fi # Convenience option to use certain configure options for some hosts. myhost="" myhostsub="" case "$1" in --build-w32) myhost="w32" ;; --build-w32ce) myhost="w32" myhostsub="ce" ;; --build-amd64) myhost="amd64" ;; --build*) echo "**Error**: invalid build option $1" >&2 exit 1 ;; *) ;; esac # ***** W32 build script ******* # Used to cross-compile for Windows. if [ "$myhost" = "w32" ]; then tmp=`dirname $0` tsdir=`cd "$tmp"; pwd` shift if [ ! -f $tsdir/scripts/config.guess ]; then echo "$tsdir/scripts/config.guess not found" >&2 exit 1 fi build=`$tsdir/scripts/config.guess` case $myhostsub in ce) w32root="$w32ce_root" [ -z "$w32root" ] && w32root="$HOME/w32ce_root" toolprefixes="$w32ce_toolprefixes arm-mingw32ce" - extraoptions="--disable-scdaemon --disable-zip --disable-ldap --disable-dirmngr $w32ce_extraoptions" + extraoptions="--disable-scdaemon --disable-zip $w32ce_extraoptions" ;; *) [ -z "$w32root" ] && w32root="$HOME/w32root" toolprefixes="$w32_toolprefixes i586-mingw32msvc" toolprefixes="$toolprefixes i386-mingw32msvc mingw32" - extraoptions="$w32_extraoptions" + extraoptions="--enable-gpgtar $w32_extraoptions" ;; esac echo "Using $w32root as standard install directory" >&2 # Locate the cross compiler crossbindir= for host in $toolprefixes; do if ${host}-gcc --version >/dev/null 2>&1 ; then crossbindir=/usr/${host}/bin conf_CC="CC=${host}-gcc" break; fi done if [ -z "$crossbindir" ]; then echo "Cross compiler kit not installed" >&2 if [ -z "$sub" ]; then echo "Under Debian GNU/Linux, you may install it using" >&2 echo " apt-get install mingw32 mingw32-runtime mingw32-binutils" >&2 fi echo "Stop." >&2 exit 1 fi if [ -f "$tsdir/config.log" ]; then if ! head $tsdir/config.log | grep "$host" >/dev/null; then echo "Please run a 'make distclean' first" >&2 exit 1 fi fi ./configure --enable-maintainer-mode --prefix=${w32root} \ --host=${host} --build=${build} \ --with-gpg-error-prefix=${w32root} \ --with-ksba-prefix=${w32root} \ --with-libgcrypt-prefix=${w32root} \ --with-libassuan-prefix=${w32root} \ --with-zlib=${w32root} \ --with-regex=${w32root} \ --with-pth-prefix=${w32root} \ --with-adns=${w32root} \ ${extraoptions} --disable-g13 "$@" rc=$? exit $rc fi # ***** end W32 build script ******* # ***** AMD64 cross build script ******* # Used to cross-compile for AMD64 (for testing) if [ "$myhost" = "amd64" ]; then tmp=`dirname $0` tsdir=`cd "$tmp"; pwd` shift if [ ! -f $tsdir/scripts/config.guess ]; then echo "$tsdir/scripts/config.guess not found" >&2 exit 1 fi build=`$tsdir/scripts/config.guess` [ -z "$amd64root" ] && amd64root="$HOME/amd64root" echo "Using $amd64root as standard install directory" >&2 toolprefixes="$amd64_toolprefixes x86_64-linux-gnu amd64-linux-gnu" # Locate the cross compiler crossbindir= for host in $toolprefixes ; do if ${host}-gcc --version >/dev/null 2>&1 ; then crossbindir=/usr/${host}/bin conf_CC="CC=${host}-gcc" break; fi done if [ -z "$crossbindir" ]; then echo "Cross compiler kit not installed" >&2 echo "Stop." >&2 exit 1 fi if [ -f "$tsdir/config.log" ]; then if ! head $tsdir/config.log | grep "$host" >/dev/null; then echo "Please run a 'make distclean' first" >&2 exit 1 fi fi $tsdir/configure --enable-maintainer-mode --prefix=${amd64root} \ --host=${host} --build=${build} \ --with-gpg-error-prefix=${amd64root} \ --with-ksba-prefix=${amd64root} \ --with-libgcrypt-prefix=${amd64root} \ --with-libassuan-prefix=${amd64root} \ --with-zlib=/usr/x86_64-linux-gnu/usr \ --with-pth-prefix=/usr/x86_64-linux-gnu/usr rc=$? exit $rc fi # ***** end AMD64 cross build script ******* # Grep the required versions from configure.ac autoconf_vers=`sed -n '/^AC_PREREQ(/ { s/^.*(\(.*\))/\1/p q }' ${configure_ac}` autoconf_vers_num=`echo "$autoconf_vers" | cvtver` automake_vers=`sed -n '/^min_automake_version=/ { s/^.*="\(.*\)"/\1/p q }' ${configure_ac}` automake_vers_num=`echo "$automake_vers" | cvtver` gettext_vers=`sed -n '/^AM_GNU_GETTEXT_VERSION(/ { s/^.*\[\(.*\)])/\1/p q }' ${configure_ac}` gettext_vers_num=`echo "$gettext_vers" | cvtver` if [ -z "$autoconf_vers" -o -z "$automake_vers" -o -z "$gettext_vers" ] then echo "**Error**: version information not found in "\`${configure_ac}\'"." >&2 exit 1 fi if check_version $AUTOCONF $autoconf_vers_num $autoconf_vers ; then check_version $AUTOHEADER $autoconf_vers_num $autoconf_vers autoconf fi if check_version $AUTOMAKE $automake_vers_num $automake_vers; then check_version $ACLOCAL $automake_vers_num $autoconf_vers automake fi if check_version $GETTEXT $gettext_vers_num $gettext_vers; then check_version $MSGMERGE $gettext_vers_num $gettext_vers gettext fi if test "$DIE" = "yes"; then cat < + + * http.h (HTTP_FLAG_IGNORE_CL): Add flag . + * http.c (WITHOUT_GNU_PTH): Test macro for Pth support. + (http_parse_uri): s/xcalloc/xtrycalloc/. + (send_request): Replace of discrete allocation and sprintf by + xtryasprintf. + (http_wait_response): Replace HTTP_FLAG_NO_SHUTDOWN by + HTTP_FLAG_SHUTDOWN to change the default to no shutdown. + (cookie_read) [HAVE_PTH]: Use pth_read. + (longcounter_t): New. + (struct cookie_s): Add support for content length. Turn flag + fields into bit types. + (parse_response): Parse content length header. + (cookie_read): Take care of the content length. + +2010-07-08 Werner Koch + + * estream.c (estream_functions_file): Remove and replace by + identical estream_functions_fd. + +2010-07-06 Werner Koch + + * util.h (b64state): Add field STREAM. + * b64enc.c (b64enc_start): Factor code out to .. + (enc_start): new. + (b64enc_start_es, my_fputs): New. + (b64enc_write, b64enc_finish): Support estream. + 2010-06-24 Werner Koch * asshelp.c (lock_agent_spawning) [W32]: Use CreateMutexW. (start_new_gpg_agent): Use HANG option for gnupg_wait_progress. Fixes regression from 2010-06-09. 2010-06-21 Werner Koch * util.h (xfree_fnc): New. 2010-06-18 Werner Koch * util.h (GPG_ERR_MISSING_KEY) [!GPG_ERR_MISSING_KEY]: New. * sexputil.c (make_canon_sexp_pad): Add arg SECURE. 2010-06-17 Werner Koch * sexputil.c (make_canon_sexp_pad): New. 2010-06-14 Werner Koch * membuf.c (put_membuf): Add shortcut for !LEN. 2010-06-11 Marcus Brinkmann * sysutils.c (translate_sys2libc_fd): Revert last change. (translate_sys2libc_fd_int): Revert last change. 2010-06-10 Marcus Brinkmann * sysutils.c (translate_sys2libc_fd) [HAVE_W32CE_SYSTEM]: Implement. (translate_sys2libc_fd_int) [HAVE_W32CE_SYSTEM]: Don't call translate_sys2libc_fd. * estream.c (_es_get_std_stream): Fix cut&paste bug. 2010-06-09 Werner Koch * exechelp-posix.c, exechelp-w32.c * exechelp-w32ce.c (gnupg_wait_process): Add new arg HANG. Change all callers. (gnupg_release_process): New. Use it after all calls to gnupg_wait_process. * util.h (GNUPG_MODULE_NAME_DIRMNGR_LDAP): New. * homedir.c (gnupg_cachedir): New. (w32_try_mkdir): New. (dirmngr_socket_name): Change standard socket name. (gnupg_module_name): Support GNUPG_MODULE_NAME_DIRMNGR_LDAP. * logging.c (log_set_get_tid_callback): Replace by ... (log_set_pid_suffix_cb): .. new. (do_logv): Change accordingly. 2010-06-08 Marcus Brinkmann * Makefile.am (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS). (t_common_ldadd): Add $(LIBASSUAN_LIBS). * sysutils.c: Include . (translate_sys2libc_fd_int): Cast to silence gcc warning. * iobuf.c: Include (translate_file_handle): Fix syntax error. 2010-06-08 Werner Koch * iobuf.c (translate_file_handle) [W32CE]: Handle rendezvous ids. 2010-06-07 Werner Koch * sysutils.c [W32CE]: Finish pipe creation. * estream.c (es_fname_get, es_fname_set): New. (fname_set_internal): New. (struct estream_internal): Add fields printable_fname and printable_fname_inuse. (_es_get_std_stream): Set stream name. (es_fopen, es_freopen, es_deinitialize): Set fname. * exechelp-posix.c (gnupg_spawn_process): Allow passing INFILE or OUTFILE as NULL. * exechelp-w32.c (gnupg_spawn_process): Ditto. * exechelp-w32ce.c (gnupg_spawn_process): Return an error for INFILE or OUTFILE passed as NULL. 2010-06-01 Werner Koch * logging.c (log_get_stream): Make sture a log stream is available. 2010-05-30 Werner Koch * init.c (writestring_via_estream): New. (init_common_subsystems): Register with argparse. * argparse.c (argparse_register_outfnc): New. (writestrings, flushstrings): New. Use them instead of stdout or stderr based functions. 2010-05-04 Werner Koch * estream.c (_es_get_std_stream): Re-use registered standard fds. (IS_INVALID_FD, ESTREAM_SYS_YIELD): New. (es_func_fd_read, es_func_fd_write, es_func_fd_seek) (es_func_fd_destroy): Implement a dummy stream. * exechelp-w32ce.c (build_w32_commandline): Add args FD0_ISNULL and FD1_ISNULL. Remove arg PGMNAME. Change callers. (gnupg_spawn_process_detached): Implement. (gnupg_spawn_process_fd): Implement one special case for now. 2010-05-03 Werner Koch * asshelp.c (lock_agent_spawning, unlock_agent_spawning): New. (start_new_gpg_agent): Test for configured standard socket and try to fire up the agent in this case. * exechelp-posix.c (gnupg_wait_process): Do not log a message if EXITCODE is given. (gnupg_spawn_process_detached): Do not reuse PID for the second fork. 2010-04-26 Werner Koch * utf8conv.c (load_libiconv) [W32CE]: No libiconv warning * init.c (init_common_subsystems) [W32CE]: Register the sleep function before es_init. 2010-04-20 Werner Koch * estream.c (es_deinit): New. (es_init_do): Install atexit handler to flush all streams. * Makefile.am (common_sources): Add gettime.h. 2010-04-20 Marcus Brinkmann * logging.c (do_log_ignore_arg): New helper function. (log_string): Use it to remove ugly volatile hack that causes gcc warning. (log_flush): Likewise. * sysutils.c (gnupg_unsetenv) [!HAVE_W32CE_SYSTEM]: Return something. (gnupg_setenv) [!HAVE_W32CE_SYSTEM]: Likewise. * pka.c (get_pka_info): Solve strict aliasing rule violation. * t-exechelp.c (test_close_all_fds): Use dummy variables to silence gcc warning. 2010-04-15 Werner Koch * util.h: Factor time related functions out to ... * gettime.h: New. (gnupg_copy_time): Move to ... * gettime.c (gnupg_copy_time): New. * sysutils.c (gnupg_setenv) [!W32CE]: Add missing return. (gnupg_unsetenv) [!W32CE]: Add missing return. 2010-04-14 Werner Koch * Makefile.am (noinst_LIBRARIES) [W32CE]: Exclude libsimple-pwquery. * w32help.h (umask) [W32CE]: New. * sysutils.c (_gnupg_isatty): New. * util.h (gnupg_isatty): New. * asshelp.c (setup_libassuan_logging): Read ASSUAN_DEBUG envvar. (my_libassuan_log_handler): Use it. * sysutils.c (_gnupg_getenv): Implement ASSUAN_DEBUG. 2010-04-08 Werner Koch * w32help.h (_setmode, setmode) [W32CE]: Provide prototype and macro. 2010-04-07 Werner Koch * mischelp.c (timegm): Replace unsetenv/putenv by gnupg_unsetenv. * sysutils.c: Include setenv.h. (gnupg_setenv, gnupg_unsetenv): New. 2010-04-06 Werner Koch * sysutils.c (gnupg_mkdir): New. 2010-03-29 Werner Koch * init.c (sleep_on_exit): Change to 400ms. 2010-03-25 Werner Koch * init.c (sleep_on_exit) [W32CE]: New. (init_common_subsystems): Call it. 2010-03-24 Werner Koch * stringhelp.c (change_slashes, compare_filenames): Replace HAVE_DRIVE_LETTERS by HAVE_DOSISH_SYSTEM. (make_basename, make_dirname): Detect backslashes and drive letters separately. * dotlock.c (make_dotlock, create_dotlock, release_dotlock): Use LockFileEx and UnlockFileEx to support W32CE. * ttyio.c (USE_W32_CONSOLE): Replace all _WIN32 by this. (init_ttyfp) [W32CE]: Use stderr. * iobuf.c (FD_FOR_STDIN, FD_FOR_STDOUT) [W32CE]: Use estream. (translate_file_handle) [W32CE]: Remove handle translation. 2010-03-23 Werner Koch * sysutils.c (gnupg_remove): New. 2010-03-22 Werner Koch * exechelp-w32ce.c (build_w32_commandline): Replace by code from libassuan. (create_inheritable_pipe): Use _assuan_w32ce_prepare_pipe. (build_w32_commandline_copy, do_create_pipe): Remove. * exechelp-posix.c (gnupg_spawn_process): Change to use estream also for INFILE and STATUSFILE. * exechelp-w32.c (gnupg_spawn_process): Ditto. 2010-03-22 Werner Koch * exechelp.c: Remove after factoring all code out to ... * exechelp-posix.c, exechelp-w32.c, exechelp-w32ce.c: .. new. * exechelp.c (create_inheritable_pipe_r) (create_inheritable_pipe_w): Fold both into ... (create_inheritable_pipe): .. New. Change callers to use this. (gnupg_create_inbound_pipe, gnupg_create_outbound_pipe): Factor code out to ... (do_create_pipe): .. New. * init.c (parse_std_file_handles): Change to use rendezvous ids. 2010-03-15 Werner Koch * init.c (init_common_subsystems): Add args ARGCP and ARGVP. Change all callers to provide them. (parse_std_file_handles): New. * t-sysutils.c (rewind) [W32CE]: Provide a replacement. * Makefile.am (module_tests) [W32CE]: Don't build t-exechelp for now. * sysutils.c (gnupg_allow_set_foregound_window) [W32CE]: Don't call AllowSetForegroundWindow. * logging.c (isatty) [W32CE]: New. (fun_writer, set_file_fd): Use estream even for the internal error messages. (log_string, log_flush): Make DUMMY_ARG_PTR static. 2010-03-15 Werner Koch * asshelp.c (send_pinentry_environment) [!HAVE_SETLOCALE]: Do not define OLD_LC. * http.c (connect_server) [!USE_DNS_SRV]: Mark SRVTAG unused. * dns-cert.c (get_dns_cert) [!USE_DNS_CERT]: Mark args unused. * pka.c (get_pka_info): Ditto. * signal.c (pause_on_sigusr): Remove. It was used in ancient gpg version with shared memory IPC. Last caller removed on 2006-04-18. (do_block) [W32]: Mark arg unused. * exechelp.c (w32_open_null): Use CreateFileW. * init.c (init_common_subsystems): Add args ARGCP and ARGVP. Change all callers to pass them. * logging.c (S_IRGRP, S_IROTH, S_IWGRP, S_IWOTH) [W32]: New. (fun_writer, set_file_fd) [W32]: Disable socket code. * localename.c: Include gpg-error.h. * util.h (GPG_ERR_NOT_ENABLED): Remove this temporary definition. 2010-03-12 Werner Koch * status.h (STATUS_ENTER): New. * ttyio.c (tty_fprintf): Change to use estream. * miscellaneous.c (print_utf8_string): Rename to print_utf8_buffer and change FP arg to an estream. Change all callers. (print_utf8_string2): Ditto; new name is to print_utf8_buffer2. 2010-03-11 Werner Koch * miscellaneous.c (print_string): Remove. * estream.c (es_setvbuf): Fix parameter check. (es_set_buffering): Allow a SIZE of 0. * asshelp.c (setup_libassuan_logging, my_libassuan_log_handler): New. * logging.c (do_logv): Add arg IGNORE_ARG_PTR. Change all callers. (log_string): New. (log_flush): New. (set_file_fd): Simplify by using estreams es_stderr. * estream.h (es_stdout, es_stderr, es_stdin): New. 2010-03-10 Werner Koch * estream.c (es_func_fp_read, es_func_fp_write, es_func_fp_seek) (es_func_fp_destroy): Allow a NULL FP to implement a dummy stream. (do_fpopen): Ditto. (es_vfprintf_unlocked): New. (es_fprintf_unlocked): Make public. (es_fputs_unlocked): New. * logging.h: Replace FILE* by estream_t. * logging.c: Remove USE_FUNWRITER cpp conditional because we now use estream. (my_funopen_hook_ret_t, my_funopen_hook_size_t): Replace by ssize_t. (log_get_stream): Change to return an estream_t. (set_file_fd): Always close the log stream because it can't be assigned to stderr or stdout directly. Use a dummy estream as last resort log stream. (log_test_fd, log_get_fd): Use es_fileno. (log_get_stream): Assert that we have a log stream. (do_logv): Use estream functions and lock the output. 2010-03-10 Werner Koch * util.h: Replace jnlib path part by common. (snprintf): Use the replacement macro on all platforms. * Makefile.am (jnlib_sources): New. (libcommon_a_SOURCES, libcommonpth_a_SOURCES): Add jnlib_sources. (jnlib_tests): New. (noinst_PROGRAMS, TESTS): Add jnlib_tests. (t_common_ldadd): Remove libjnlib.a. * README.jnlib, ChangeLog.jnlib, libjnlib-config.h, argparse.c * argparse.h, dotlock.c, dotlock.h, dynload.h, logging.c * logging.h, mischelp.c, mischelp.h, stringhelp.c, stringhelp.h * strlist.c, strlist.h, types.h, utf8conv.c, utf8conv.h * w32-afunix.c, w32-afunix.h, w32-reg.c, w32help.h, xmalloc.c * xmalloc.h, t-stringhelp.c, t-support.c, t-support.h * t-timestuff.c, t-w32-reg.c: Move from jnlib to here. * init.c: Remove "estream.h". * util.h: Include "estream.h". * xasprintf.c, ttyio.c: Remove "estream-printf.h". 2010-03-08 Werner Koch * exechelp.c [!HAVE_SIGNAL_H]: Do not include signal.h. (DETACHED_PROCESS, CREATE_NEW_PROCESS_GROUP) [W32CE]: Provide stubs. * iobuf.h (iobuf_ioctl_t): New. Use the new macros instead of the hard wired values. * iobuf.c (iobuf_append): Remove. (iobuf_fdopen): Factor code out to ... (do_iobuf_fdopen): ... new. (iobuf_fdopen_nc): New. (iobuf_open_fd_or_name): Implement using iobuf_fdopen_nc. * iobuf.c (INVALID_FD): Replace by GNUPG_INVALID_FD. (fp_or_fd_t): Replace by gnupg_fd_t. (my_fileno): Replace by the FD2INT macro. (FILEP_OR_FD_FOR_STDIN, FILEP_OR_FD_FOR_STDOUT): Rename to FD_FOR_STDIN, FD_FOR_STDOUT. (file_filter): Make full use of FD_FOR_STDIN. (USE_SETMODE): Remove. Not needed without stdio. (my_fopen_ro, my_fopen): Replace unneeded macros. * iobuf.c [FILE_FILTER_USES_STDIO]: Remove all code. It has not been used for a long time. * exechelp.h: Include "estream.h". * exechelp.c (gnupg_spawn_process): Change OUTFILE to an estream_t. 2010-03-02 Werner Koch * estream.c, estream.h, estream-printf.c, estream-printf.h: Update from libestream. 2010-03-01 Werner Koch * signal.c [!HAVE_SIGNAL_H]: Don't include signal.h. * iobuf.c (direct_open) [W32CE]: Make filename to wchar_t. (iobuf_cancel) [W32CE]: Use DeleteFile. * gettime.c (dump_isotime): Use "%s" to print "none". * homedir.c (standard_homedir) [W32CE]: Use wchar_t to create the directory. (w32_rootdir) [W32CE]: Likewise. * sysutils.c (translate_sys2libc_fd) [W32CE]: Add support. (gnupg_tmpfile) [W32CE]: Ditto. (_gnupg_getenv) [W32CE]: New. * util.h (getpid, getenv) [W32CE]: New. * i18n.c (i18n_switchto_utf8) (i18n_switchback) [USE_SIMPLE_GETTEXT]: Use new function from libgpg-error which supports proper restoring. * sysutils.c (get_session_marker): Simplified by using gcrypt. 2009-12-08 Marcus Brinkmann * Makefile.am (audit-events.h, status.h) [!MAINTAINER_MODE]: No longer include these rules if not in maintainer mode. 2009-12-08 Werner Koch * userids.h, userids.c: New. (classify_user_id): Merged from similar fucntions in sm/ and g10/. * dns-cert.c (get_dns_cert): Add support for ADNS. 2009-12-08 Marcus Brinkmann * asshelp.c (start_new_gpg_agent): Convert posix FD to assuan FD. * asshelp.c (start_new_gpg_agent) [HAVE_W32_SYSTEM]: Add missing argument in assuan_socket_connect invocation. * iobuf.c (iobuf_open_fd_or_name): Fix type of FD in function declaration. 2009-12-07 Werner Koch * pka.c (get_pka_info): Add support for ADNS. * src.v (getsrv): Add support for ADNS. * srv.c (getsrv): s/xrealloc/xtryrealloc/. 2009-12-04 Werner Koch * Makefile.am (audit-events.h, status-codes.h): Create files in the source dir. Fixes bug#1164. 2009-12-02 Werner Koch * audit.c (proc_type_decrypt, proc_type_sign): Implemented. (proc_type_verify): Print hash algo infos. * audit.h (AUDIT_DATA_CIPHER_ALGO, AUDIT_BAD_DATA_CIPHER_ALSO) (AUDIT_NEW_RECP, AUDIT_DECRYPTION_RESULT, AUDIT_RECP_RESULT) (AUDIT_ATTR_HASH_ALGO, AUDIT_SIGNED_BY, AUDIT_SIGNING_DONE): 2009-11-05 Marcus Brinkmann * asshelp.c (start_new_gpg_agent): Update use of assuan_socket_connect and assuan_pipe_connect. 2009-11-02 Marcus Brinkmann * get-passphrase.c (default_inq_cb, membuf_data_cb): Change return type to gpg_error_t. 2009-10-28 Werner Koch * status.h (STATUS_MOUNTPOINT): New. 2009-10-16 Marcus Brinkmann * Makefile.am (libcommon_a_CFLAGS): Use LIBASSUAN_CFLAGS instead of LIBASSUAN_PTH_CFLAGS. 2009-10-13 Werner Koch * exechelp.c (gnupg_kill_process): New. 2009-09-29 Werner Koch * exechelp.c (create_inheritable_pipe): Rename to create_inheritable_pipe_w. (create_inheritable_pipe_r): New. (gnupg_create_outbound_pipe): New. * iobuf.h: Include "sysutils.h" * iobuf.c (iobuf_open_fd_or_name): New. (iobuf_get_fname_nonnull): New. 2009-09-23 Marcus Brinkmann * asshelp.c (start_new_gpg_agent): Allocate assuan context before starting server. 2009-09-03 Werner Koch Update from libestream: * estream-printf.c: Include stdint.h only if HAVE_STDINT_H is defined. * estream-printf.c: Remove all test code. Use macro DEBUG instead of TEST for debugging. * estream-printf.c (pr_float): Make buffer larger for silly high numbers. 2009-08-11 David Shaw * ttyio.h, ttyio.c (tty_enable_completion): Some ifdefs around HAVE_LIBREADLINE to allow building when readline isn't available. 2009-08-06 Werner Koch * status.h (STATUS_INV_SGNR, STATUS_NO_SGNR): New. * status.c (get_inv_recpsgnr_code): New. 2009-07-23 David Shaw * srv.c (getsrv): Fix type-punning warning. 2009-07-23 Werner Koch * util.h (GPG_ERR_NOT_ENABLED): New. * audit.h (enum): Add AUDIT_CRL_CHECK. * audit.c (proc_type_verify): Show CRL check result. 2009-07-06 Werner Koch * get-passphrase.c (struct agentargs): Add SESSION_ENV and remove obsolete args. (gnupg_prepare_get_passphrase): Ditto. * session-env.c, session-env.h: New. * t-session-env.c: New. * Makefile.am (common_sources, module_tests): Add them. * asshelp.h: Include "session-env.h" * asshelp.c (send_one_option): Add arg PUTENV. (send_pinentry_environment): Replace most args by SESSION_ENV and rewrite fucntion. (start_new_gpg_agent): Likewise. * t-exechelp.c (test_close_all_fds): Remove debug code. 2009-07-01 Werner Koch * sexputil.c (get_pk_algo_from_canon_sexp): New. 2009-06-29 Werner Koch * estream.c (BUFFER_ROUND_TO_BLOCK): Remove unused macro. (es_func_mem_write): Rewrite reallocation part. * estream.c (es_write_sanitized_utf8_buffer): Typo typo fix. 2009-06-25 Werner Koch * estream.c (es_write_sanitized_utf8_buffer): Typo fix. 2009-06-24 Werner Koch * estream.c (es_read_line): In the malloc error case, set MAX_LENGTH to 0 only if requested. * xreadline.c (read_line): Ditto. * estream.c (es_write_sanitized_utf8_buffer): Pass on error from es_fputs. * sexputil.c (get_rsa_pk_from_canon_sexp): Check for error after the loop. Reported by Fabian Keil. 2009-06-22 Werner Koch * estream.c (es_pth_read, es_pth_write) [W32]: New. (ESTREAM_SYS_READ, ESTREAM_SYS_WRITE) [HAVE_PTH]: Use them. 2009-06-03 Werner Koch * estream.c (es_convert_mode): Rewrite and support the "x" flag. 2009-05-28 David Shaw From 1.4: * http.h, http.c (send_request) Pass in a STRLIST for additional headers. Change all callers. 2009-05-27 David Shaw From 1.4: * http.h, http.c (send_request): Pass in srvtag and make its presence sufficient to turn the feature on. (http_open): From here. (http_document): And here. * srv.c (getsrv): Raise maximum packet size to 2048, as PACKETSZ is too small these days. 2009-05-22 Werner Koch * ttyio.c (tty_cleanup_after_signal): New. 2009-05-19 Werner Koch * simple-pwquery.c (agent_open): Use SUN_LEN (JNLIB_NEED_AFLOCAL): Define and include mischelp.h. 2009-05-07 Werner Koch * sexputil.c (get_rsa_pk_from_canon_sexp): New. * t-sexputil.c (test_make_canon_sexp_from_rsa_pk): Extend the test. 2009-04-28 Werner Koch * sexputil.c (make_canon_sexp_from_rsa_pk): New. * t-sexputil.c (test_make_canon_sexp_from_rsa_pk): New. 2009-04-01 Werner Koch * iobuf.c: Port David's changes from 1.4: (fd_cache_invalidate): Pass return code from close back. (direct_open, iobuf_ioctl): Check that return value. (fd_cache_synchronize): New. (iobuf_ioctl): Add new sub command 4 (fsync). * iobuf.c (fd_cache_strcmp): New. Taken from 1.4. (fd_cache_invalidate, fd_cache_close, fd_cache_open): Use it. * exechelp.c (gnupg_spawn_process): Implement new flag bit 6. * sysutils.c (gnupg_allow_set_foregound_window): Allow the use of ASFW_ANY. * membuf.c (put_membuf, get_membuf): Wipe memory on out of core. 2009-03-31 Werner Koch * percent.c (percent_unescape, percent_plus_unescape): New. (percent_plus_unescape_inplace, percent_unescape_inplace): New. (do_plus_or_plain_unescape, count_unescape, do_unescape): New. (do_unescape_inplace): New. * t-percent.c (test_percent_plus_escape): Test percent_plus_unescape. * get-passphrase.c, get-passphrase.h: New. * Makefile.am (without_pth_sources): New. 2009-03-18 Werner Koch * exechelp.c: Include sys/resource.h and sys/stat.h. (get_max_open_fds): New. (do_exec): Use it. (get_all_open_fds): New. (close_all_fds): New. (do_exec): Use close_all_fds. * t-exechelp.c: New. 2009-03-13 David Shaw * http.c (do_parse_uri): Properly handle IPv6 literal addresses as per RFC-2732. Adapted from patch by Phil Pennock. 2009-03-12 Werner Koch * gettime.c: Include i18n.h. (dump_isotime): New. 2009-03-06 Werner Koch * sexputil.c (make_canon_sexp): New. 2009-03-03 Werner Koch * exechelp.c (do_exec): Make sure that /dev/null connected FDs are not closed. 2009-01-19 Werner Koch * audit.c (writeout_li): Translate a few more result strings. Fixes bug#970. * convert.c (hex2str): Fix optimization to append a nul character. 2008-12-05 Werner Koch * percent.c, t-percent.c: New. * exechelp.c (gnupg_spawn_process, gnupg_spawn_process_fd) (gnupg_spawn_process_detached) [W32]: Remove debug output. 2008-11-20 Werner Koch * audit.c (writeout_li): Translate OKTEXT. 2008-11-04 Werner Koch * i18n.c (i18n_init) [USE_SIMPLE_GETTEXT]: Adjust for changed w32-gettext.c. * homedir.c (gnupg_localedir): New. 2008-10-20 Werner Koch * http.c (http_register_tls_callback) [!HTTP_USE_GNUTLS]: Mark unused arg. * localename.c (do_nl_locale_name): Ditto. * audit.c (event2str): Silent gcc warning. * sysutils.c (translate_sys2libc_fd): Mark unused arg. (translate_sys2libc_fd_int): Ditto. * iobuf.c (translate_file_handle): Ditto. * asshelp.c (send_one_option): Ditto. * exechelp.c (gnupg_spawn_process): Ditto. * signal.c (got_usr_signal): Ditto * estream.c (es_func_fd_create) [!W32]: Ditto. (es_func_fp_create) [!W32]: Ditto. (es_write_hexstring): Ditto. (dummy_mutex_call_void, dummy_mutex_call_int) [HAVE_PTH]: New. (ESTREAM_MUTEX_LOCK, ESTREAM_MUTEX_UNLOCK, ESTREAM_MUTEX_TRYLOCK) (ESTREAM_MUTEX_INITIALIZE) [HAVE_PTH]: Use dummy calls so to mark unused arg. 2008-10-19 Werner Koch * estream-printf.c (estream_vsnprintf): Fix return value. (check_snprintf): Add a new test. (one_test) [W32]: Disable test. 2008-10-17 Werner Koch * util.h (snprintf) [W32]: Redefine to estream_snprintf. 2008-09-03 Werner Koch * convert.c (hex2str): New. (hex2str_alloc): New. * t-convert.c (test_hex2str): New. 2008-08-19 Werner Koch * iobuf.c: Avoid passing a NULL (iobuf_t)->desc to the log function. Should in general never be NULL, but well. Reported by M. Heneka. 2008-06-26 Werner Koch * estream.c (es_write_sanitized): Loose check for control characters to better cope with utf-8. The range 0x80..0x9f is nowadays not anymore accidently used for control charaters. 2008-06-25 Marcus Brinkmann Revert last three changes related to handle translation. * sysutils.c: (FD_TRANSLATE_MAX, fd_translate, fd_translate_len) (translate_table_init, translate_table_lookup): Removed. * iobuf.c (check_special_filename): Do not use translate_table_lookup. * sysutils.h (translate_table_init, translate_table_lookup): Remove prototypes. 2008-06-19 Werner Koch * sysutils.c: Remove . (fd_translate_max): Use macro for the size. (translate_table_init): Protect read against EINTR and replace isspace by spacep. 2008-06-18 Marcus Brinkmann * sysutils.c (TRANS_MAX): Bump up to 350 to be on the safe side. * sysutils.h (translate_table_init, translate_table_lookup): New prototypes. * sysutils.c: Include . (FD_TRANSLATE_MAX): New macro. (fd_translate, fd_translate_len): New static variables. (translate_table_init, translate_table_lookup): New functions. (translate_sys2libc_fd_int): Translate file descriptor. * iobuf.c (check_special_filename): Translate handle values from special filenames. 2008-06-16 Werner Koch * homedir.c (w32_commondir): New. (gnupg_sysconfdir): Use it. 2008-06-09 Werner Koch * b64dec.c: New. 2008-06-05 Werner Koch * util.h (gnupg_copy_time): Replace strcpy by memcpy. 2008-05-26 Werner Koch * asshelp.c (send_one_option, send_pinentry_environment): use xfree and xtrystrdup. * i18n.c (i18n_switchto_utf8) [USE_SIMPLE_GETTEXT]: Return NULL. * homedir.c (gnupg_module_name): Add GNUPG_MODULE_NAME_CONNECT_AGENT and GNUPG_MODULE_NAME_GPGCONF. 2008-04-21 Werner Koch * http.c (http_wait_response) [W32]: Use DuplicateHandle because it is a socket. (cookie_read) [W32]: Use recv in place of read. 2008-04-08 Werner Koch * i18n.c (i18n_switchto_utf8, i18n_switchback) [USE_SIMPLE_GETTEXT]: Implement. 2008-04-07 Werner Koch * b64enc.c (b64enc_start): Detect PGP mode. (b64enc_finish): Write PGP CRC. * util.h (struct b64state): Add field CRC. * t-b64.c: New. * pka.c (get_pka_info): Use xtrymalloc and check result. 2008-03-25 Werner Koch * localename.c: Strip all W32 code. Include w32help.h. (gnupg_messages_locale_name) [W32]: Use the gettext_localename. 2008-03-17 Werner Koch * iobuf.c (IOBUF_BUFFER_SIZE): Actually use this macro. * simple-pwquery.c (agent_send_all_options): Fix last change. 2008-03-06 Werner Koch * simple-pwquery.c (agent_send_all_options): Add support for XAUTHORITY and PINENTRY_USER_DATA. 2008-02-15 Marcus Brinkmann * exechelp.c (gnupg_spawn_process_fd): Add flag DETACHED_PROCESS unconditionally (required for all callers at the moment). 2008-02-14 Werner Koch * sysutils.c (gnupg_allow_set_foregound_window): New. (WINVER) [W32]: Define. 2008-01-31 Werner Koch * audit.c (audit_print_result): Make sure that the output is always UTF8. 2008-01-27 Werner Koch * exechelp.c (gnupg_spawn_process): Add arg FLAGS and changed all callers to pass 0 for it. 2007-12-13 Werner Koch * sexputil.c (hash_algo_from_sigval): New. * t-sexputil.c: New. * Makefile.am (module_tests): Add it. 2007-12-11 Werner Koch * asshelp.c (send_pinentry_environment): Allow using of old gpg-agents not capabale of the xauthority and pinentry_user_data options. 2007-12-04 Werner Koch * Makefile.am (t_helpfile_LDADD, module_maint_tests): New. * t-helpfile.c: New. * helpfile.c: New. * membuf.h (is_membuf_ready, MEMBUF_ZERO): New. * localename.c: New. Taken from gettext with modifications as done for GpgOL. Export one new function. * util.h (gnupg_messages_locale_name, gnupg_get_help_string): Added. * sysutils.c (gnupg_reopen_std): New. Taken from ../g10/gpg.c. 2007-11-27 Werner Koch * Makefile.am (CLEANFILES): New. * homedir.c (dirmngr_socket_name): Use CSIDL_WINDOWS. 2007-11-15 Werner Koch * asshelp.c (send_pinentry_environment): Add args XAUTHORITY and PINENTRY_USER_DATA. (start_new_gpg_agent): Ditto. 2007-11-07 Werner Koch * status.h: New. * errors.h: Remove. 2007-11-05 Werner Koch * audit.c, audit.h: New. * Makefile.am: Add rules to build audit-events.h. * exaudit.awk: New. * mkstrtable.awk: New. Taken from libgpg-error. 2007-10-19 Werner Koch * i18n.c (i18n_switchto_utf8, i18n_switchback): New. 2007-10-01 Werner Koch * sysutils.h (FD2INT, INT2FD): New. 2007-09-21 Werner Koch * homedir.c (default_homedir): Make registry work. Reported by Marc Mutz. 2007-08-29 Werner Koch * exechelp.c (gnupg_wait_process): Add arg EXITCODE. Changed all callers. (gnupg_create_inbound_pipe): New. * util.h (GNUPG_MODULE_NAME_GPGSM, GNUPG_MODULE_NAME_GPG): New. * homedir.c (gnupg_module_name): Add them 2007-08-28 Werner Koch * gettime.c (check_isotime, add_isotime): New. Originally written for DirMngr by me. (add_days_to_isotime): New. (date2jd, jd2date, days_per_month, days_per_year): New. Taken from my ancient (1988) code used in Wedit (time2.c). 2007-08-27 Werner Koch * util.h (GNUPG_MODULE_NAME_CHECK_PATTERN): New. * homedir.c (gnupg_module_name): Add it. * exechelp.c (w32_fd_or_null) [W32]: New. (gnupg_spawn_process_fd): New. (gnupg_wait_process) [W32]: Close the handle after if the process has returned. 2007-08-22 Werner Koch Updated estream from libestream. * estream.c (mem_malloc, mem_realloc, mem_free): New. Use them instead of the ES_MEM_foo. * estream.c (estream_cookie_mem): Remove members DONT_FREE, APPEND_ZERO, PTR and SIZE. Add MEMORY_LIMIT. Put GROW into a new FLAGS struct. (es_func_mem_create): Remove APPEND_ZERO, DONT_FREE, PTR and SIZE. Add MEMORY_LIMIT. (es_func_mem_write, es_func_mem_seek, es_func_mem_destroy): Revamp. (es_open_memstream): Change API to just take a memory limit and a mode argument. Rename to .. (es_fopenmem): .. this. (HAVE_W32_SYSTEM) [_WIN32]: Define if not defined. (tmpfd) [W32]: Implement directly using the W32 API. (es_fgets): Rewrite without using doreadline. 2007-08-21 Werner Koch * sysutils.c (gnupg_tmpfile): New. * t-sysutils.c: New. * Makefile.am (module_tests): Add t-sysutils. 2007-08-20 Werner Koch * exechelp.c [W32]: Redefine X_OK to F_OK. 2007-08-16 Werner Koch * Makefile.am (t_convert_DEPENDENCIES): Remove ($(PROGRAMS)): Remove. (t_common_ldadd): Use libcommon.a and not the macro. 2007-08-14 Werner Koch * homedir.c (dirmngr_socket_name): New. 2007-08-07 Werner Koch * tlv.c, tlv.h: Move from ../scd/. * tlv.c (parse_sexp, parse_ber_header): Add ERRSOURCE arg and prefix name with a _. * tlv.h: Use macro to convey ERRSOURCE. 2007-08-02 Werner Koch * gc-opt-flags.h: New. 2007-08-01 Werner Koch * estream-printf.c (read_dummy_value): Removed as it is useless now. (read_values): Remove check on !vaargs which is not anymore needed and anyway not portable. Reported by Peter O'Gorman. 2007-07-16 Werner Koch * estream.c (es_func_file_create): Clear NO_CLOSE flag. 2007-07-12 Werner Koch * sysutils.h (gnupg_fd_t): New. * sysutils.c (translate_sys2libc_fd): Use that type instead of int. (translate_sys2libc_fd_int): New. 2007-07-09 Werner Koch * t-gettime.c (test_isotime2epoch): Use time_t and not u32. 2007-07-05 Werner Koch * t-gettime.c: New. * gettime.c (isotime2epoch, epoch2isotime): New. 2007-07-04 Werner Koch * estream.c (es_init_do): Do not throw an error if pth has already been initialized. 2007-06-26 Werner Koch * Makefile.am ($(PROGRAMS)): New. * util.h (init_common_subsystems): Moved to .. * init.h: .. New. * util.h: Include init.h. * homedir.c (standard_homedir): New. (default_homedir) [W32]: Reimplemented in terms of standard_homedir. Fixed memory leak. 2007-06-25 Werner Koch * iobuf.c: Add more documentation and slighly restructured macro defintion for better readability. (FILEP_OR_FD): Rename to fp_or_fd_t. (CLOSE_CACHE): Rename to close_cache_t. * sysutils.c (translate_sys2libc_fd): New using the code from iobuf.c. * iobuf.c: Include sysutils.h. (iobuf_translate_file_handle): Remove. (translate_file_handle): Use new function. * estream-printf.c [TEST]: Header including fixes. (do_format): Do not append a trailing Nul. This avoids spurious Nuls in the es_printf output. (estream_vsnprintf, estream_vasprintf): Take this in account. * estream.h (struct es__stream): Change FLAGS to a bit structure. (ES__FLAG_WRITING): Replace by a bit from FLAGS. * estream.c (struct estream_internal): Rename FLAGS to MODEFLAGS so that they are not confused with the estream flags. (es_initialize, es_create): Add arg MODEFLAGS so that we can setup the intial writemode. Changed all callers to pass them. (es_convert_mode): Set O_BINARY. (es_func_fd_create, es_func_fp_create, es_func_file_create) [W32]: Call setmode if requested. 2007-06-24 Werner Koch * estream.c (do_fpopen, es_fpopen, es_fpopen_nc): New. (es_func_fp_create, es_func_fp_read, es_func_fp_write) (es_func_fp_seek, es_func_fp_destroy): New. 2007-06-22 Werner Koch * estream.c (es_fdopen): Factored code out to.. (do_fdopen): .. new. (es_fdopen_nc): New. (estream_cookie_fd): Add field NO_CLOSE. (es_func_fd_create): Add arg NO_CLOSE and changed all callers. (es_func_fd_destroy): Handle the new flag. * homedir.c (gnupg_libexecdir) [W32]: Factor code out to .. (w32_rootdir): .. new. (gnupg_sysconfdir, gnupg_libdir, gnupg_datadir) [W32]: Return name based on w32_rootdir(). 2007-06-21 Werner Koch * membuf.h (get_membuf_len): New. * membuf.c (init_membuf_secure): Really allocate in secure memory. (put_membuf_str): New. * ttyio.c (tty_getf): New. * util.h (ctrl_t): Declare it here. * asshelp.c (start_new_gpg_agent): New. Based on code from ../sm/call-agent.c 2007-06-20 Werner Koch * sysutils.c (gnupg_sleep): New. * sysutils.h [W32]: Remove _sleep wrapper. Changed all callers to use gnupg_sleep. * exechelp.c (build_w32_commandline_copy): New. (build_w32_commandline): Factored some code out to new function and correctly process a PGMNAME with spaces. (gnupg_spawn_process_detached) [W32]: Implement. 2007-06-14 Werner Koch * simple-pwquery.h (MAP_SPWQ_ERROR_IMPL): New. (SPWQ_NO_PIN_ENTRY): New. * simple-pwquery.c (simple_pw_set_socket): New. (agent_open): Use it if GPG_AGENT_INFO is not set. (simple_pwquery): Extended to allow returning of otehyr error codes. * util.h (GNUPG_MODULE_NAME_AGENT, GNUPG_MODULE_NAME_PINENTRY) (GNUPG_MODULE_NAME_SCDAEMON, GNUPG_MODULE_NAME_DIRMNGR) (GNUPG_MODULE_NAME_PROTECT_TOOL): New. * homedir.c (gnupg_module_name): New. (gnupg_bindir): New. 2007-06-12 Werner Koch * homedir.c (gnupg_sysconfdir): New. (gnupg_libexecdir): New. Taken from g10/misc.c:get_libexecdir. (gnupg_datadir): New. (gnupg_libdir): New. * http.c (connect_server) [W32]: Do not call init_sockets if HTTP_NO_WSASTARTUP is defined. * init.c: New. * estream.c (es_init_do): Init stream lock here because we can't use a static initialization with W32pth. 2007-06-11 Werner Koch * Makefile.am (t_common_ldadd): Use libcommonstd macro. 2007-06-06 Werner Koch * Makefile.am: Include am/cmacros.am. * sysutils.h [W32]: Remove prototypes for the registry access. * w32reg.c: Move to ../jnlib/w32-reg.c. * i18n.c (i18n_init): New. * simple-gettext.c: Remove. * iobuf.c (iobuf_get_filelength): Rename SIZE to EXSIZE to silent shadowing warning. 2007-06-04 Werner Koch * http.c [W32]: Include unistd.h also in this case. (write_server) [W32]: Fixed error code. (init_sockets): Fixed syntax error. (cookie_close): Replace close by sock_close macro. * estream.c [w32]: Do not init Mutex. * Makefile.am (common_sources) [USE_SNS_SRV]: Build srv.c only when needed. * ttyio.c (init_ttyfp) [W32]: Do not use TTYFP. * util.h: Include ../jnlib/dynload.h. * dynload.h: Move to ../jnlib. 2007-05-30 Werner Koch * estream.c (MEM_FREE, MEM_ALLOC, MEM_REALLOC): Prefix with ES_ as windows.h also has such definitions, 2007-05-15 Werner Koch * util.h: Do not include gnulib's vasprintf. Redefine asprintf and vasprintf. * xasprintf.c (xasprintf, xtryasprintf): Use estream_vasprintf. * estream-printf.h, estream-printf.c: New. Taken from current libestream SVN. * Makefile.am (common_sources): Add them. 2007-05-14 Werner Koch * sexp-parse.h (smklen): New. * sexputil.c: Include sexp-parse.h. (make_simple_sexp_from_hexstr): Replace sprintf by smklen. 2007-05-07 Werner Koch * signal.c (got_fatal_signal): Protect SIG from being clobbered by a faulty signal implementaion. Suggested by James Juran. 2007-04-25 Werner Koch * i18n.h (ngettext): New. * simple-gettext.c (ngettext): New. 2007-04-20 Werner Koch * miscellaneous.c (my_gcry_logger, my_gcry_outofcore_handler): Moved from gpg-agent to here. (my_gcry_fatalerror_handler): new. (setup_libgcrypt_logging): New. 2007-03-19 Werner Koch * miscellaneous.c (print_hexstring): New. * estream.c (es_fprintf_unlocked): New. (es_write_sanitized): New. (es_write_hexstring): New. (es_write_sanitized_utf8_buffer) [GNUPG_MAJOR_VERSION]: New. 2007-03-09 David Shaw From STABLE-BRANCH-1-4 * http.c (do_parse_uri): Remove the hkp port 11371 detection. We implement hkp in the keyserver handler, and the support here makes it appear like a bad hkp request actually succeeded. 2007-01-31 Werner Koch * Makefile.am (t_common_ldadd): Add LIBINCONV and LIBINTL. 2007-01-25 Werner Koch * simple-pwquery.c (simple_pwquery): New arg OPT_CHECK. 2006-12-13 David Shaw * Makefile.am (AM_CPPFLAGS): Include intl/ so we can reference the built-in headers. 2006-11-23 Werner Koch * http.c: Include i18n.h 2006-11-21 Werner Koch * estream.c: Remove explicit Pth soft mapping diabling becuase it is now done in config.h. 2006-11-15 Werner Koch * estream.c: Disabled Pth soft mapping. (my_funopen_hook_ret_t): New. (print_fun_writer): Use it here. * iobuf.c (fd_cache_close): Use %d instead of %p for debug output. 2006-11-03 Werner Koch * Makefile.am (t_convert_DEPENDENCIES): Add libcommon. From Gentoo. 2006-10-24 Marcus Brinkmann * Makefile.am (libcommon_a_CFLAGS): Add $(LIBASSUAN_CFLAGS). (libsimple_pwquery_a_CFLAGS): New variable. 2006-10-20 Werner Koch * convert.c (hex2bin): New. 2006-10-17 Werner Koch * estream.c (struct estream_internal, es_initialize) (es_deinitialize, print_fun_writer, es_print): New and modified functions to avoid tempfiles for printf style printing. * Makefile.am (libcommonpth_a_SOURCES): New. We now build a secon version of the library with explicit Pth support. * exechelp.c, estream.c: Make use of WITHOUT_GNU_PTH. 2006-10-08 Werner Koch * gpgrlhelp.c: Trun all functions into dummies if readline is not available. 2006-10-06 Werner Koch * Makefile.am (AM_CFLAGS): Use PTH version of libassuan. * util.h (GNUPG_GCC_A_SENTINEL): Defined for gcc >= 4. 2006-10-04 David Shaw * gpgrlhelp.c: readline requires stdio.h. 2006-10-04 Werner Koch * membuf.c (init_membuf_secure): New. (put_membuf): Make sure that ERRNO is set even if the underlying malloc code does not work properly. (get_membuf): Set ERRNO on error. (get_membuf): Allow to pass LEN as NULL. 2006-10-02 Werner Koch * iobuf.c (iobuf_unread): Removed. This code is not required. Also removed the entire unget buffer stuff. 2006-09-27 Werner Koch * util.h: Do not include strsep.h and strpbrk.h. (isascii): Removed as it is now in jnlib. * iobuf.c (pop_filter, underflow, iobuf_close): Free the unget buffer. 2006-09-27 Florian Weimer (wk) * iobuf.c (iobuf_unread): New. 2006-09-22 Werner Koch * i18n.h: Changed license to an all permissive one. * ttyio.c (tty_get): We need to use readline too. Added two more hooks. 2006-09-21 Werner Koch * ttyio.c (tty_private_set_rl_hooks): New. (tty_enable_completion, tty_disable_completion): Use a hook to enable readline support. Now always available. (tty_cleanup_rl_after_signal): New. * ttyio.h: Removed readline specific stuff. Included util.h. * common-defs.h: New. 2006-09-15 Werner Koch * convert.c: New. (hexcolon2bin): New. (bin2hex, bin2hexcolon, do_binhex): New. * t-convert.c: New 2006-09-14 Werner Koch * util.h (out_of_core): Use new gpg_error_from_syserror function. * http.c (init_sockets): Changed it to require 2.2 unless it is build within gnupg 1 where we require 1.1 (and not anymore allow for 1.0). 2006-09-07 Werner Koch * exechelp.c (gnupg_spawn_process): Factor out post fork code to .. (do_exec): .. new function. Allow passing of -1 for the fds. (gnupg_spawn_process): Terminate gcrypt's secure memory in the child. (gnupg_spawn_process_detached): New. 2006-09-06 Werner Koch * maperror.c: Removed. * util.h (out_of_core): New. 2006-09-04 Werner Koch * http.c (http_get_header): New. (capitalize_header_name, store_header): New. (parse_response): Store headers away. (send_request): Return GPG_ERR_NOT_FOUND if connect_server failed. * http.h: New flag HTTP_FLAG_NEED_HEADER. 2006-08-21 Werner Koch * Makefile.am (libcommon_a_SOURCES): Added keyserver.h * openpgpdefs.h: New. Stripped from ..g10/packet.h. 2006-08-16 Werner Koch * keyserver.h: Moved from ../include to here. * http.c: Include srv.h. * srv.c, srv.h: New. Taken from GnuPG 1.4 2006-08-14 Werner Koch * http.h (struct http_context_s): Moved to implementation. * http.c (http_open): Changed call to return a context. (http_open_document): Ditto. (http_get_read_ptr, http_get_read_ptr, http_get_status_code): New. (do_parse_uri): Replaced strlwr by straight code to ease standalone use of this file. (http_wait_response): Removed arg STATUS_CODE as it is available through an accessor function. Adjusted caller. (http_escape_string): New. * estream.c (es_read_line): Renamed to .. (doreadline): .. this. Changed all callers. (es_read_line): New. This is theusual limited getline variabnt as used at several places. Here taken and adjusted from xreadline.c (es_free): New. 2006-08-11 Werner Koch * http.c: Major internal changes to optionallly support GNUTLS and ESTREAM. (http_open): Move initialization of the stream ... (send_request): .. here. (http_register_tls_callback): New. * estream.c (es_writen): Try to seek only is a seek function has been registered. 2006-08-09 Werner Koch * http.c, http.h: New. Taken from gnupg 1.4.5, merged with changes done for the Dirmngr project (by g10 Code) and cleaned up some stuff. (make_header_line): New. Change all caller to make user of the new * Makefile.am (libcommon_a_SOURCES): Added http.c and http.h. 2006-05-23 Werner Koch * gettime.c (isotimestamp): New. * ttyio.c (tty_get_ttyname): Posixly correct usage of ctermid. * dns-cert.c: New. Taken from 1.4.3's util/cert.c. * dns-cert.h: New. 2006-05-22 Werner Koch * pka.c: New. Taked from 1.4.3. * pka.h: New. * Makefile.am: Added pka. 2006-05-19 Werner Koch * yesno.c (answer_is_yes_no_default, answer_is_yes_no_quit): Updated from 1.4.3. (answer_is_okay_cancel): new. From 1.4.3. * miscellaneous.c (match_multistr): New. Taken from 1.4.3. * ttyio.c (tty_enable_completion, tty_disable_completion): New dummy functions. * ttyio.h: Add prototypes and stubs. 2006-04-19 Werner Koch * iobuf.c (iobuf_get_fd): New. Taken from 1.4.3. (iobuf_is_pipe_filename): New. (pop_filter): Made static. (iobuf_skip_rest): New. Orginal patch by Florian Weimer. Added new argument PARTIAL. (block_filter): Remove the old gpg indeterminate length mode. (block_filter): Properly handle a partial body stream that ends with a 5-byte length that happens to be zero. (iobuf_set_block_mode, iobuf_in_block_mode): Removed as superfluous. (iobuf_get_filelength): New arg OVERFLOW. (iobuf_get_filelength) [W32]: Use GetFileSizeEx if available * miscellaneous.c (is_file_compressed): Take care of OVERFLOW. 2006-04-18 Werner Koch * homedir.c (w32_shgetfolderpath): New. Taken from gpg 1.4.3. (default_homedir): Use it. 2005-10-08 Marcus Brinkmann * signal.c (get_signal_name): Check value of HAVE_DECL_SYS_SIGLIST instead of just if it is defined. 2005-09-28 Marcus Brinkmann * Makefile.am (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS). 2005-07-04 Marcus Brinkmann * simple-pwquery.h (simple_pwclear): New prototype. * simple-pwquery.c (simple_pwclear): New function. 2005-06-15 Werner Koch * miscellaneous.c (make_printable_string): Made P a void*. * sexputil.c (keygrip_from_canon_sexp, cmp_simple_canon_sexp): Fixed signed/unsigned pointer mismatch. (make_simple_sexp_from_hexstr): Ditto. This is all too ugly; I wonder why gcc-4's default is to warn about them and forcing us to use cast the warning away. * iobuf.c (block_filter): Ditto. (iobuf_flush): Ditto. (iobuf_read_line): Ditto. (iobuf_read): Make BUFFER a void *. (iobuf_write): Make BUFFER a const void *. * ttyio.c (tty_print_utf8_string2): Ditto. * estream.c (estream_cookie_mem): Make MEMORY unsigned char*. (es_write): Make BUFFER a void *. (es_writen): Ditto. (es_func_fd_read, es_func_fd_write, es_func_mem_read) (es_func_mem_write): Ditto. (es_read, es_readn): Ditto. (es_func_mem_write): Made MEMORY_NEW an unsigned char *. * estream.h (es_cookie_read_function_t) (es_cookie_write_function_t): Changed buffer arg to void*. 2005-06-03 Werner Koch * estream.c: Use HAVE_CONFIG_H and not USE_CONFIG_H! (es_func_fd_read, es_func_fd_write): Protect against EINTR. 2005-06-01 Werner Koch * Makefile.am (AM_CPPFLAGS): Added. * util.h: Add some includes for gnulib. (ttyname, isascii): Define them inline. * fseeko.c, ftello.c: Removed. * strsep.c, mkdtemp.c: Removed. * ttyname.c, isascii.c: Removed. 2005-05-31 Werner Koch * dynload.h: s/__inline__/inline/. 2005-05-13 Werner Koch * signal.c (got_fatal_signal): Print the signal number if we can't get a name for it. (get_signal_name): Return NULL if no name is available. Fixed conditional for sys_siglist to the correct one. 2005-04-17 Werner Koch * sexputil.c (cmp_simple_canon_sexp): New. (make_simple_sexp_from_hexstr): New. 2005-04-07 Werner Koch * sexputil.c: New. 2005-04-11 Marcus Brinkmann * simple-pwquery.c (simple_pwquery): Use spwq_secure_free. 2005-03-03 Werner Koch * Makefile.am (AM_CFLAGS): Added PTH_CFLAGS. Noted by Kazu Yamamoto. 2005-02-25 Werner Koch * xasprintf.c (xtryasprintf): New. 2005-01-26 Moritz Schulte * Makefile.am (libcommon_a_SOURCES): New source files: estream.c, estream.h. * estream.c, estream.h: New files. 2005-01-03 Werner Koch * asshelp.c (send_pinentry_environment): Fixed changed from 2004-12-18; cut+paste error for lc-messages. 2004-12-21 Werner Koch * simple-pwquery.c (agent_open) [W32]: Implement for W32. (readline) [W32]: Use recv instead of read. (writen) [W32]: Use send instead of write. (my_stpcpy): Define a stpcpy replacement so that this file continues to be self-contained. (agent_send_all_options) [W32]: Don't call ttyname. 2004-12-21 Marcus Brinkmann * simple-pwquery.h (simple_query): Add prototype. * simple-pwquery.c (simple_query): New function. 2004-12-21 Werner Koch * signal.c (got_fatal_signal, got_usr_signal) (got_fatal_signal) [DOSISH]: Don't build. * simple-gettext.c: Include sysutils.h * homedir.c: New. Use CSIDL_APPDATA for W32 as the default home directory. * Makefile.am (libcommon_a_SOURCES): Add it. (EXTRA_DIST): Removed mkerror and mkerrtok. 2004-12-20 Werner Koch * sysutils.h [W32]: Define sleep. * util.h: Add prototype for mkdtemp. * membuf.c (put_membuf): Wipe out buffer after a failed realloc. 2004-12-19 Werner Koch * maperror.c (map_assuan_err_with_source): Oops, args were swapped. 2004-12-18 Werner Koch * maperror.c (map_assuan_err): Renamed to .. (map_assuan_err_with_source): .. this and add arg SOURCE.c * asshelp.c (send_pinentry_environment, send_one_option): Add arg ERRSOURCE. 2004-12-15 Werner Koch * sysutils.h [W32]: Prototypes for registry functions. * w32reg.c: Include sysutils.h * simple-pwquery.c [W32]: Dummy code to allow a build. * exechelp.c [W32]: Implemented for W32 . * ttyname.c: New. * asshelp.c (send_one_option): New. (send_pinentry_environment): Cleaned up and made sure that empty values are not send. 2004-12-07 Werner Koch * asshelp.c (send_pinentry_environment) [W32]: Do not use ttyname. 2004-12-06 Werner Koch * exechelp.h, exechelp.c: New. Based on code from ../sm/import.c. 2004-12-03 Werner Koch * strsep.c: Fixed copyright comments. 2004-11-26 Werner Koch * simple-gettext.c: New taken from gnupg 1.3.x * simple-pwquery.c [_WIN32]: Include winsock2.h. (agent_open): Disable it until we have our AF_UNIX implementation ready. * fseeko.c, ftello.c: Include sys/types for the sake of W32. 2004-11-23 Werner Koch * b64enc.c: Include stdio.h and string.h 2004-08-18 Werner Koch * simple-pwquery.c (simple_pwquery): Handle gpg-error style return code for canceled. 2004-07-20 Werner Koch * maperror.c: Removed header ksba.h. Not required anymore. 2004-06-14 Werner Koch * xreadline.c: New. Based on the iobuf_read_line function. 2004-05-12 Werner Koch * util.h (xtrycalloc_secure,xtrymalloc_secure): New. 2004-05-11 Werner Koch * sysutils.c (disable_core_dumps): Only set the current limit. (enable_core_dumps): New. 2004-04-13 Werner Koch * simple-pwquery.c (copy_and_escape): Relaxed quoting. 2004-04-05 Werner Koch * errors.h (STATUS_NEWSIG): New. 2004-03-11 Werner Koch * dynload.h [__MINGW32__]: Define RTLD_LAZY. 2004-03-09 Werner Koch * maperror.c (map_assuan_err): Map the Locale_Problem item. 2004-03-03 Werner Koch * asshelp.c, asshelp.h: New. (send_pinentry_environment): New. Code taken from ../sm/call-agent.c. 2004-02-19 Werner Koch * simple-pwquery.c (agent_open): Don't mangle INFOSTR. 2004-02-17 Werner Koch * simple-pwquery.c (agent_open): Ignore an empty GPG_AGENT_INFO. * errors.h: Added STATUS_IMPORT_OK. 2004-02-10 Werner Koch * b64enc.c: New. Based on code from ../sm/base64.c. 2004-01-30 Marcus Brinkmann * Makefile.am (libcommon_a_SOURCES): Add xasprintf.c. * miscellaneous.c (xasprintf): Moved to ... * xasprintf (xasprintf): ... here. New file. This allows to use xasprintf without sucking in gpg-error. 2004-01-27 Werner Koch * sexp-parse.h: New; moved from../agent. * util.h (xtoi_4): New. 2003-12-23 Werner Koch * maperror.c (map_assuan_err): Prepared for a new error code. 2003-12-17 Werner Koch * gettime.c (asctimestamp): Add a note on a non-avoidable gcc warning. * util.h [!HAVE_VASPRINTF]: Add printf format attribute to the replacement function. * miscellaneous.c (xasprintf): New. 2003-11-14 Werner Koch * mkdtemp.c (mkdtemp): Use gcry_create_nonce. * cryptmiss.c: Removed. 2003-11-13 Werner Koch * util.h (vasprintf): Also fixed the prototype. * vasprintf.c (vasprintf): ARGS should not be a pointer. Fixed segv on Solaris. Reported by Andrew J. Schorr. 2003-11-12 Werner Koch * maperror.c (map_ksba_err, map_gcry_err, map_kbx_err): Removed. 2003-10-31 Werner Koch * util.h (gnupg_isotime_t): New. (gnupg_copy_time): New. * gettime.c (gnupg_get_isotime): New. 2003-09-23 Werner Koch * iobuf.c (check_special_filename): Replaced is isdigit by digitp to avoid passing negative values and potential locale problems. Problem noted by Christian Biere. * util.h (ascii_isspace): New. 2003-09-18 Werner Koch * ttyio.c (tty_fprintf): New. (tty_print_string, tty_print_utf8_string2) (tty_print_utf8_string): Made P argument const byte*. 2003-08-20 Marcus Brinkmann * maperror.c (map_ksba_err): Map -1. Use gpg_err_make to set the error source. 2003-08-14 Timo Schulz * dynload.h. New. W32 wrapper around the dynload mechanism. 2003-07-15 Werner Koch * simple-pwquery.c, simple-pwquery.h: New; moved from ../agent. * Makefile.am (libsimple_pwquery_a_LIBADD): New. 2003-06-25 Werner Koch * maperror.c (map_to_assuan_status): Directly map 0 to 0. 2003-06-17 Werner Koch * gettime.c (scan_isodatestr,add_days_to_timestamp,strtimevalue) (strtimestamp,asctimestamp): New. Code taken from gnupg 1.3.2 mischelp.c. * yesno.c: New. Code taken from gnupg 1.3.2 mischelp.c * miscellaneous.c: New. * util.h: Include utf8conf.h 2003-06-16 Werner Koch * gettime.c (make_timestamp): New. * ttyio.c: New. Taken from gnupg 1.2. * ttyio.h: Move from ../include. 2003-06-13 Werner Koch * util.h (seterr): Removed macro. (xmalloc_secure,xcalloc_secure): New. 2003-06-11 Werner Koch * iobuf.c (iobuf_writebyte,iobuf_write): Return error code from iobuf_flush. (iobuf_writestr): Ditto. 2003-06-10 Werner Koch * iobuf.c, iobuf.h: New. Taken from current gnupg 1.3 CVS. Run indent on it and adjusted error handling to libgpg-error style. Replaced IOBUF by iobuf_t. Renamed malloc functions. 2003-06-04 Werner Koch * errors.h: Removed all error codes. We keep the status codes for now. * Makefile.am: Do not create errors.c anymore; remove it from the sources. * maperror.c: Don't include error.h. Change all error codes to libgpg-error style. (map_assuan_err): Changed to new Assuan error code convention. (map_to_assuan_status): Likewise. (map_gcry_err,map_kbx_err): Not needed. For now dummy functions. * membuf.c, membuf.h: New. Code taken from ../sm/call-agent.h. * Makefile.am: Added above. 2003-04-29 Werner Koch * util.h (fopencokokie): Removed prototype and struct. * fopencookie.c: Removed. * maperror.c: Use system assuan.h 2002-10-31 Neal H. Walfield * isascii.c: New file. * putc_unlocked.c: Likewise. 2002-10-28 Neal H. Walfield * signal.c (caught_fatal_sig): Remove superfluous zero initializer. (caught_sigusr1): Likewise. 2002-09-04 Neal H. Walfield * vasprintf.c (vasprintf) [va_copy]: Use va_copy. [!va_copy && __va_copy]: Use __va_copy. [!va_copy && !__va_copy]: Only now fall back to using memcpy. 2002-08-21 Werner Koch * errors.h: Added STATUS_IMPORT_PROBLEM. 2002-08-20 Werner Koch * vasprintf.c: Hack to handle NULL for %s. 2002-08-09 Werner Koch * signal.c: New. Taken from GnuPG 1.1.91. 2002-07-23 Werner Koch * util.h (_IO_cookie_io_functions_t): Fixed typo. Noted by Richard Lefebvre. 2002-07-22 Werner Koch * fseeko.c, ftello.c: New. 2002-06-28 Werner Koch * maperror.c (map_to_assuan_status): Map more errorcodes to Bad Certificate. 2002-06-26 Werner Koch * maperror.c (map_to_assuan_status): Map EOF to No_Data_Available. 2002-06-10 Werner Koch * errors.h (gnupg_error_token): Add new prototype. (STATUS_ERROR): New. * mkerrtok: New. * Makefile.am: Use it to create the new error token function. 2002-06-04 Werner Koch * maperror.c (map_to_assuan_status): Map Bad_CA_Certificate. 2002-05-23 Werner Koch * no-pth.c, Makefile.am: Removed. 2002-05-22 Werner Koch * mkdtemp.c: Replaced byte by unsigned char because it is no longer defined in gcrypt.h. 2002-05-21 Werner Koch * maperror.c (map_gcry_err): Add libgcrypt's new S-expression errors. (map_ksba_err): Add a few mappings. 2002-05-14 Werner Koch * gettime.c: New. 2002-05-03 Werner Koch * errors.h: Added STARUS_EXPSIG and STATUS_EXPKEYSIG. 2002-04-15 Werner Koch * cryptmiss.c: New. 2002-02-14 Werner Koch * maperror.c: Add more assuan<->gnupg mappings. 2002-02-12 Werner Koch * fopencookie.c: Dummy function. * vasprintf.c: New. Taken from binutils-2.9.1 and dropped all non ANSI-C stuff. Merged with asprintf version. * no-pth.c: New. 2002-01-23 Werner Koch * mkdtemp.c: Copied from gnupg-1.0.6c and changed to use libgcrypt. 2002-01-19 Werner Koch * sysutils.c: New. This is the misc.c file from gnupg 1.0.6 with the OpenPGP stuff removed. * sysutils.h: New. 2002-01-15 Werner Koch * maperror.c: Add mapping for Not_Trusted. 2002-01-11 Werner Koch * maperror.c (map_assuan_err): Codes for CRL 2002-01-08 Werner Koch * util.h (spacep): New. 2002-01-02 Werner Koch * maperror.c (map_to_assuan_status): New. Merged from ../agent and ../sm. 2001-12-20 Werner Koch * maperror.c (map_gcry_err): Add some mappings. 2001-12-18 Werner Koch * Makefile.am (AM_CPPFLAGS): Include flags for gcrypt and ksba 2001-12-14 Werner Koch * util.h (digitp, hexdigitp): New ctype like macros. (atoi_1,atoi_2,atoi_4,xtoi_1,xtoi_2): New. Copyright 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without modifications, as long as this notice is preserved. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, to the extent permitted by law; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/common/b64enc.c b/common/b64enc.c index 4722bd1e1..1e277f4cb 100644 --- a/common/b64enc.c +++ b/common/b64enc.c @@ -1,351 +1,409 @@ /* b64enc.c - Simple Base64 encoder. - * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc. + * Copyright (C) 2001, 2003, 2004, 2008, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #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 #define B64ENC_USE_PGPCRC 32 /* The base-64 character list */ static unsigned char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /* Stuff required to create the OpenPGP CRC. This crc_table has been created using this code: #include #include #define CRCPOLY 0x864CFB int main (void) { int i, j; uint32_t t; uint32_t crc_table[256]; crc_table[0] = 0; for (i=j=0; j < 128; j++ ) { t = crc_table[j]; if ( (t & 0x00800000) ) { t <<= 1; crc_table[i++] = t ^ CRCPOLY; crc_table[i++] = t; } else { t <<= 1; crc_table[i++] = t; crc_table[i++] = t ^ CRCPOLY; } } puts ("static const u32 crc_table[256] = {"); for (i=j=0; i < 256; i++) { printf ("%s 0x%08lx", j? "":" ", (unsigned long)crc_table[i]); if (i != 255) { putchar (','); if ( ++j > 5) { j = 0; putchar ('\n'); } } } puts ("\n};"); return 0; } */ #define CRCINIT 0xB704CE static const u32 crc_table[256] = { 0x00000000, 0x00864cfb, 0x018ad50d, 0x010c99f6, 0x0393e6e1, 0x0315aa1a, 0x021933ec, 0x029f7f17, 0x07a18139, 0x0727cdc2, 0x062b5434, 0x06ad18cf, 0x043267d8, 0x04b42b23, 0x05b8b2d5, 0x053efe2e, 0x0fc54e89, 0x0f430272, 0x0e4f9b84, 0x0ec9d77f, 0x0c56a868, 0x0cd0e493, 0x0ddc7d65, 0x0d5a319e, 0x0864cfb0, 0x08e2834b, 0x09ee1abd, 0x09685646, 0x0bf72951, 0x0b7165aa, 0x0a7dfc5c, 0x0afbb0a7, 0x1f0cd1e9, 0x1f8a9d12, 0x1e8604e4, 0x1e00481f, 0x1c9f3708, 0x1c197bf3, 0x1d15e205, 0x1d93aefe, 0x18ad50d0, 0x182b1c2b, 0x192785dd, 0x19a1c926, 0x1b3eb631, 0x1bb8faca, 0x1ab4633c, 0x1a322fc7, 0x10c99f60, 0x104fd39b, 0x11434a6d, 0x11c50696, 0x135a7981, 0x13dc357a, 0x12d0ac8c, 0x1256e077, 0x17681e59, 0x17ee52a2, 0x16e2cb54, 0x166487af, 0x14fbf8b8, 0x147db443, 0x15712db5, 0x15f7614e, 0x3e19a3d2, 0x3e9fef29, 0x3f9376df, 0x3f153a24, 0x3d8a4533, 0x3d0c09c8, 0x3c00903e, 0x3c86dcc5, 0x39b822eb, 0x393e6e10, 0x3832f7e6, 0x38b4bb1d, 0x3a2bc40a, 0x3aad88f1, 0x3ba11107, 0x3b275dfc, 0x31dced5b, 0x315aa1a0, 0x30563856, 0x30d074ad, 0x324f0bba, 0x32c94741, 0x33c5deb7, 0x3343924c, 0x367d6c62, 0x36fb2099, 0x37f7b96f, 0x3771f594, 0x35ee8a83, 0x3568c678, 0x34645f8e, 0x34e21375, 0x2115723b, 0x21933ec0, 0x209fa736, 0x2019ebcd, 0x228694da, 0x2200d821, 0x230c41d7, 0x238a0d2c, 0x26b4f302, 0x2632bff9, 0x273e260f, 0x27b86af4, 0x252715e3, 0x25a15918, 0x24adc0ee, 0x242b8c15, 0x2ed03cb2, 0x2e567049, 0x2f5ae9bf, 0x2fdca544, 0x2d43da53, 0x2dc596a8, 0x2cc90f5e, 0x2c4f43a5, 0x2971bd8b, 0x29f7f170, 0x28fb6886, 0x287d247d, 0x2ae25b6a, 0x2a641791, 0x2b688e67, 0x2beec29c, 0x7c3347a4, 0x7cb50b5f, 0x7db992a9, 0x7d3fde52, 0x7fa0a145, 0x7f26edbe, 0x7e2a7448, 0x7eac38b3, 0x7b92c69d, 0x7b148a66, 0x7a181390, 0x7a9e5f6b, 0x7801207c, 0x78876c87, 0x798bf571, 0x790db98a, 0x73f6092d, 0x737045d6, 0x727cdc20, 0x72fa90db, 0x7065efcc, 0x70e3a337, 0x71ef3ac1, 0x7169763a, 0x74578814, 0x74d1c4ef, 0x75dd5d19, 0x755b11e2, 0x77c46ef5, 0x7742220e, 0x764ebbf8, 0x76c8f703, 0x633f964d, 0x63b9dab6, 0x62b54340, 0x62330fbb, 0x60ac70ac, 0x602a3c57, 0x6126a5a1, 0x61a0e95a, 0x649e1774, 0x64185b8f, 0x6514c279, 0x65928e82, 0x670df195, 0x678bbd6e, 0x66872498, 0x66016863, 0x6cfad8c4, 0x6c7c943f, 0x6d700dc9, 0x6df64132, 0x6f693e25, 0x6fef72de, 0x6ee3eb28, 0x6e65a7d3, 0x6b5b59fd, 0x6bdd1506, 0x6ad18cf0, 0x6a57c00b, 0x68c8bf1c, 0x684ef3e7, 0x69426a11, 0x69c426ea, 0x422ae476, 0x42aca88d, 0x43a0317b, 0x43267d80, 0x41b90297, 0x413f4e6c, 0x4033d79a, 0x40b59b61, 0x458b654f, 0x450d29b4, 0x4401b042, 0x4487fcb9, 0x461883ae, 0x469ecf55, 0x479256a3, 0x47141a58, 0x4defaaff, 0x4d69e604, 0x4c657ff2, 0x4ce33309, 0x4e7c4c1e, 0x4efa00e5, 0x4ff69913, 0x4f70d5e8, 0x4a4e2bc6, 0x4ac8673d, 0x4bc4fecb, 0x4b42b230, 0x49ddcd27, 0x495b81dc, 0x4857182a, 0x48d154d1, 0x5d26359f, 0x5da07964, 0x5cace092, 0x5c2aac69, 0x5eb5d37e, 0x5e339f85, 0x5f3f0673, 0x5fb94a88, 0x5a87b4a6, 0x5a01f85d, 0x5b0d61ab, 0x5b8b2d50, 0x59145247, 0x59921ebc, 0x589e874a, 0x5818cbb1, 0x52e37b16, 0x526537ed, 0x5369ae1b, 0x53efe2e0, 0x51709df7, 0x51f6d10c, 0x50fa48fa, 0x507c0401, 0x5542fa2f, 0x55c4b6d4, 0x54c82f22, 0x544e63d9, 0x56d11cce, 0x56575035, 0x575bc9c3, 0x57dd8538 }; -/* 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. - If TITLE starts with "PGP " the OpenPGP CRC checksum will be - written as well. 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) +static gpg_error_t +enc_start (struct b64state *state, FILE *fp, estream_t stream, + const char *title) { memset (state, 0, sizeof *state); state->fp = fp; + state->stream = stream; if (title && !*title) state->flags |= B64ENC_NO_LINEFEEDS; else if (title) { if (!strncmp (title, "PGP ", 4)) { state->flags |= B64ENC_USE_PGPCRC; state->crc = CRCINIT; } state->title = xtrystrdup (title); if (!state->title) return gpg_error_from_syserror (); } return 0; } +/* 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. + If TITLE starts with "PGP " the OpenPGP CRC checksum will be + written as well. 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) +{ + return enc_start (state, fp, NULL, title); +} + +/* Same as b64enc_start but takes an estream. */ +gpg_error_t +b64enc_start_es (struct b64state *state, estream_t fp, const char *title) +{ + return enc_start (state, NULL, fp, title); +} + + +static int +my_fputs (const char *string, struct b64state *state) +{ + if (state->stream) + return es_fputs (string, state->stream); + else + return fputs (string, state->fp); +} + + /* 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; + if (buffer) + if (state->stream? es_fflush (state->stream) : fflush (state->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) + if ( my_fputs ("-----BEGIN ", state) == EOF + || my_fputs (state->title, state) == EOF + || my_fputs ("-----\n", state) == EOF) goto write_error; if ( (state->flags & B64ENC_USE_PGPCRC) - && fputs ("\n", fp) == EOF) + && my_fputs ("\n", state) == 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); if ( (state->flags & B64ENC_USE_PGPCRC) ) { size_t n; u32 crc = state->crc; for (p=buffer, n=nbytes; n; p++, n-- ) crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ *p]; state->crc = (crc & 0x00ffffff); } 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 (state->stream) + { + for (idx=0; idx < 4; idx++) + es_putc (tmp[idx], state->stream); + idx = 0; + if (es_ferror (state->stream)) + goto write_error; + } + else + { + for (idx=0; idx < 4; idx++) + putc (tmp[idx], state->fp); + idx = 0; + if (ferror (state->fp)) + goto write_error; + } if (++quad_count >= (64/4)) { quad_count = 0; if (!(state->flags & B64ENC_NO_LINEFEEDS) - && fputs ("\n", fp) == EOF) + && my_fputs ("\n", state) == 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_syserror (); } + gpg_error_t b64enc_finish (struct b64state *state) { gpg_error_t err = 0; unsigned char radbuf[4]; int idx, quad_count; - FILE *fp; char tmp[4]; 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) { 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 (state->stream) + { + for (idx=0; idx < 4; idx++) + es_putc (tmp[idx], state->stream); + idx = 0; + if (es_ferror (state->stream)) + goto write_error; + } + else + { + for (idx=0; idx < 4; idx++) + putc (tmp[idx], state->fp); + idx = 0; + if (ferror (state->fp)) + goto write_error; + } + if (++quad_count >= (64/4)) { quad_count = 0; if (!(state->flags & B64ENC_NO_LINEFEEDS) - && fputs ("\n", fp) == EOF) + && my_fputs ("\n", state) == EOF) goto write_error; } } /* Finish the last line and write the trailer. */ if (quad_count && !(state->flags & B64ENC_NO_LINEFEEDS) - && fputs ("\n", fp) == EOF) + && my_fputs ("\n", state) == EOF) goto write_error; if ( (state->flags & B64ENC_USE_PGPCRC) ) { /* Write the CRC. */ - putc ('=', fp); + my_fputs ("=", state); radbuf[0] = state->crc >>16; radbuf[1] = state->crc >> 8; radbuf[2] = state->crc; 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); - if (ferror (fp)) - goto write_error; + if (state->stream) + { + for (idx=0; idx < 4; idx++) + es_putc (tmp[idx], state->stream); + if (es_ferror (state->stream)) + goto write_error; + } + else + { + for (idx=0; idx < 4; idx++) + putc (tmp[idx], state->fp); + if (ferror (state->fp)) + goto write_error; + } if (!(state->flags & B64ENC_NO_LINEFEEDS) - && fputs ("\n", fp) == EOF) + && my_fputs ("\n", state) == EOF) goto write_error; } if (state->title) { - if ( fputs ("-----END ", fp) == EOF - || fputs (state->title, fp) == EOF - || fputs ("-----\n", fp) == EOF) + if ( my_fputs ("-----END ", state) == EOF + || my_fputs (state->title, state) == EOF + || my_fputs ("-----\n", state) == EOF) goto write_error; } goto cleanup; write_error: err = gpg_error_from_syserror (); cleanup: if (state->title) { xfree (state->title); state->title = NULL; } state->fp = NULL; + state->stream = NULL; return err; } diff --git a/common/estream.c b/common/estream.c index bf7955d06..3ab68b5ff 100644 --- a/common/estream.c +++ b/common/estream.c @@ -1,3649 +1,3641 @@ /* estream.c - Extended Stream I/O Library * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Libestream; if not, see . * * ALTERNATIVELY, Libestream may be distributed under the terms of the * following license, in which case the provisions of this license are * required INSTEAD OF the GNU General Public License. If you wish to * allow use of your version of this file only under the terms of the * GNU General Public License, and not to allow others to use your * version of this file under the terms of the following license, * indicate your decision by deleting this paragraph and the license * below. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef USE_ESTREAM_SUPPORT_H # include #endif #ifdef HAVE_CONFIG_H # include #endif #if defined(_WIN32) && !defined(HAVE_W32_SYSTEM) # define HAVE_W32_SYSTEM 1 # if defined(__MINGW32CE__) && !defined (HAVE_W32CE_SYSTEM) # define HAVE_W32CE_SYSTEM # endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # include #endif #ifdef HAVE_W32CE_SYSTEM # include /* ERRNO replacement. */ #endif #ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth. */ # undef HAVE_PTH # undef USE_GNU_PTH #endif #ifdef HAVE_PTH # include #endif /* This is for the special hack to use estream.c in GnuPG. */ #ifdef GNUPG_MAJOR_VERSION # include "../common/util.h" #endif #ifndef HAVE_MKSTEMP int mkstemp (char *template); #endif #ifndef HAVE_MEMRCHR void *memrchr (const void *block, int c, size_t size); #endif #include #include #ifndef O_BINARY #define O_BINARY 0 #endif #ifdef HAVE_W32CE_SYSTEM # define _set_errno(a) gpg_err_set_errno ((a)) /* Setmode is missing in cegcc but available since CE 5.0. */ int _setmode (int handle, int mode); # define setmode(a,b) _setmode ((a),(b)) #else # define _set_errno(a) do { errno = (a); } while (0) #endif #ifdef HAVE_W32_SYSTEM # define IS_INVALID_FD(a) ((void*)(a) == (void*)(-1)) #else # define IS_INVALID_FD(a) ((a) == -1) #endif /* 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 /* 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)) #else typedef void *estream_mutex_t; static inline void dummy_mutex_call_void (estream_mutex_t mutex) { (void)mutex; } static inline int dummy_mutex_call_int (estream_mutex_t mutex) { (void)mutex; return 0; } # define ESTREAM_MUTEX_INITIALIZER NULL # define ESTREAM_MUTEX_LOCK(mutex) dummy_mutex_call_void ((mutex)) # define ESTREAM_MUTEX_UNLOCK(mutex) dummy_mutex_call_void ((mutex)) # define ESTREAM_MUTEX_TRYLOCK(mutex) dummy_mutex_call_int ((mutex)) # define ESTREAM_MUTEX_INITIALIZE(mutex) dummy_mutex_call_void ((mutex)) #endif /* Primitive system I/O. */ #ifdef HAVE_PTH # define ESTREAM_SYS_READ es_pth_read # define ESTREAM_SYS_WRITE es_pth_write # define ESTREAM_SYS_YIELD() pth_yield (NULL) #else # define ESTREAM_SYS_READ read # define ESTREAM_SYS_WRITE write # define ESTREAM_SYS_YIELD() do { } while (0) #endif /* Misc definitions. */ #define ES_DEFAULT_OPEN_MODE (S_IRUSR | S_IWUSR) /* 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 modeflags; /* Flags for the backend. */ char *printable_fname; /* Malloced filename for es_fname_get. */ 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; unsigned int is_stdstream:1; /* This is a standard stream. */ unsigned int stdstream_fd:2; /* 0, 1 or 2 for a standard stream. */ unsigned int print_err: 1; /* Error in print_fun_writer. */ unsigned int printable_fname_inuse: 1; /* es_fname_get has been used. */ int print_errno; /* Errno from print_fun_writer. */ size_t print_ntotal; /* Bytes written from in print_fun_writer. */ FILE *print_fp; /* Stdio stream used by print_fun_writer. */ }; 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; static estream_mutex_t estream_list_lock; #define ESTREAM_LIST_LOCK ESTREAM_MUTEX_LOCK (estream_list_lock) #define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock) /* File descriptors registered to be used as the standard file handles. */ static int custom_std_fds[3]; static unsigned char custom_std_fds_valid[3]; #ifndef EOPNOTSUPP # define EOPNOTSUPP ENOSYS #endif /* Local prototypes. */ static void fname_set_internal (estream_t stream, const char *fname, int quote); /* Macros. */ /* Calculate array dimension. */ #ifndef DIM #define DIM(array) (sizeof (array) / sizeof (*array)) #endif #define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) /* 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) /* Malloc wrappers to overcome problems on some older OSes. */ static void * mem_alloc (size_t n) { if (!n) n++; return malloc (n); } static void * mem_realloc (void *p, size_t n) { if (!p) return mem_alloc (n); return realloc (p, n); } static void mem_free (void *p) { if (p) free (p); } /* * List manipulation. */ /* Add STREAM to the list of registered stream objects. If WITH_LOCKED_LIST is true we assumed that the list of streams is already locked. */ static int es_list_add (estream_t stream, int with_locked_list) { estream_list_t list_obj; int ret; list_obj = mem_alloc (sizeof (*list_obj)); if (! list_obj) ret = -1; else { if (!with_locked_list) 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; if (!with_locked_list) ESTREAM_LIST_UNLOCK; ret = 0; } return ret; } /* Remove STREAM from the list of registered stream objects. */ static void es_list_remove (estream_t stream, int with_locked_list) { estream_list_t list_obj; if (!with_locked_list) 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; } if (!with_locked_list) 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; } /* * I/O Helper * * Unfortunately our Pth emulation for Windows expects system handles * for pth_read and pth_write. We use a simple approach to fix this: * If the function returns an error we fall back to a vanilla read or * write, assuming that we do I/O on a plain file where the operation * can't block. */ #ifdef HAVE_PTH static int es_pth_read (int fd, void *buffer, size_t size) { # ifdef HAVE_W32_SYSTEM int rc = pth_read (fd, buffer, size); if (rc == -1 && errno == EINVAL) rc = read (fd, buffer, size); return rc; # else /*!HAVE_W32_SYSTEM*/ return pth_read (fd, buffer, size); # endif /* !HAVE_W32_SYSTEM*/ } static int es_pth_write (int fd, const void *buffer, size_t size) { # ifdef HAVE_W32_SYSTEM int rc = pth_write (fd, buffer, size); if (rc == -1 && errno == EINVAL) rc = write (fd, buffer, size); return rc; # else /*!HAVE_W32_SYSTEM*/ return pth_write (fd, buffer, size); # endif /* !HAVE_W32_SYSTEM*/ } #endif /*HAVE_PTH*/ static void es_deinit (void) { /* Flush all streams. */ es_fflush (NULL); } /* * Initialization. */ static int es_init_do (void) { static int initialized; if (!initialized) { #ifdef HAVE_PTH if (!pth_init () && errno != EPERM ) return -1; if (pth_mutex_init (&estream_list_lock)) initialized = 1; #else initialized = 1; #endif atexit (es_deinit); } return 0; } /* * I/O methods. */ /* Implementation of Memory I/O. */ /* Cookie for memory objects. */ typedef struct estream_cookie_mem { unsigned int modeflags; /* Open flags. */ unsigned char *memory; /* Allocated data buffer. */ size_t memory_size; /* Allocated size of MEMORY. */ size_t memory_limit; /* Caller supplied maximum allowed allocation size or 0 for no limit. */ size_t offset; /* Current offset in MEMORY. */ size_t data_len; /* Used length of data in MEMORY. */ size_t block_size; /* Block size. */ struct { unsigned int grow: 1; /* MEMORY is allowed to grow. */ } flags; func_realloc_t func_realloc; func_free_t func_free; } *estream_cookie_mem_t; /* Create function for memory objects. DATA is either NULL or a user supplied buffer with the initial conetnt of the memory buffer. If DATA is NULL, DATA_N and DATA_LEN need to be 0 as well. If DATA is not NULL, DATA_N gives the allocated size of DATA and DATA_LEN the used length in DATA. */ 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, func_realloc_t func_realloc, func_free_t func_free, unsigned int modeflags, size_t memory_limit) { estream_cookie_mem_t mem_cookie; int err; if (!data && (data_n || data_len)) { _set_errno (EINVAL); return -1; } mem_cookie = mem_alloc (sizeof (*mem_cookie)); if (!mem_cookie) err = -1; else { mem_cookie->modeflags = modeflags; mem_cookie->memory = data; mem_cookie->memory_size = data_n; mem_cookie->memory_limit = memory_limit; mem_cookie->offset = 0; mem_cookie->data_len = data_len; mem_cookie->block_size = block_size; mem_cookie->flags.grow = !!grow; mem_cookie->func_realloc = func_realloc ? func_realloc : mem_realloc; mem_cookie->func_free = func_free ? func_free : mem_free; *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; ssize_t ret; size_t nleft; if (!size) return 0; /* A flush is a NOP for memory objects. */ if (mem_cookie->modeflags & O_APPEND) { /* Append to data. */ mem_cookie->offset = mem_cookie->data_len; } assert (mem_cookie->memory_size >= mem_cookie->offset); nleft = mem_cookie->memory_size - mem_cookie->offset; /* If we are not allowed to grow limit the size to the left space. */ if (!mem_cookie->flags.grow && size > nleft) size = nleft; /* Enlarge the memory buffer if needed. */ if (size > nleft) { unsigned char *newbuf; size_t newsize; if (!mem_cookie->memory_size) newsize = size; /* Not yet allocated. */ else newsize = mem_cookie->memory_size + (nleft - size); if (newsize < mem_cookie->offset) { _set_errno (EINVAL); return -1; } /* Round up to the next block length. BLOCK_SIZE should always be set; we check anyway. */ if (mem_cookie->block_size) { newsize += mem_cookie->block_size - 1; if (newsize < mem_cookie->offset) { _set_errno (EINVAL); return -1; } newsize /= mem_cookie->block_size; newsize *= mem_cookie->block_size; } /* Check for a total limit. */ if (mem_cookie->memory_limit && newsize > mem_cookie->memory_limit) { _set_errno (ENOSPC); return -1; } newbuf = mem_cookie->func_realloc (mem_cookie->memory, newsize); if (!newbuf) return -1; mem_cookie->memory = newbuf; mem_cookie->memory_size = newsize; assert (mem_cookie->memory_size >= mem_cookie->offset); nleft = mem_cookie->memory_size - mem_cookie->offset; assert (size <= nleft); } 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; 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; 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: _set_errno (EINVAL); return -1; } if (pos_new > mem_cookie->memory_size) { size_t newsize; void *newbuf; if (!mem_cookie->flags.grow) { _set_errno (ENOSPC); return -1; } newsize = pos_new + mem_cookie->block_size - 1; if (newsize < pos_new) { _set_errno (EINVAL); return -1; } newsize /= mem_cookie->block_size; newsize *= mem_cookie->block_size; if (mem_cookie->memory_limit && newsize > mem_cookie->memory_limit) { _set_errno (ENOSPC); return -1; } newbuf = mem_cookie->func_realloc (mem_cookie->memory, newsize); if (!newbuf) return -1; mem_cookie->memory = newbuf; mem_cookie->memory_size = newsize; } 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->data_len = pos_new; } mem_cookie->offset = pos_new; *offset = pos_new; return 0; } /* Destroy function for memory objects. */ static int es_func_mem_destroy (void *cookie) { estream_cookie_mem_t mem_cookie = cookie; if (cookie) { mem_cookie->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; /* The file descriptor we are using for actual output. */ int no_close; /* If set we won't close the file descriptor. */ } *estream_cookie_fd_t; /* Create function for fd objects. */ static int es_func_fd_create (void **cookie, int fd, unsigned int modeflags, int no_close) { estream_cookie_fd_t fd_cookie; int err; fd_cookie = mem_alloc (sizeof (*fd_cookie)); if (! fd_cookie) err = -1; else { #ifdef HAVE_DOSISH_SYSTEM /* Make sure it is in binary mode if requested. */ if ( (modeflags & O_BINARY) ) setmode (fd, O_BINARY); #else (void)modeflags; #endif fd_cookie->fd = fd; fd_cookie->no_close = no_close; *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; if (IS_INVALID_FD (file_cookie->fd)) { ESTREAM_SYS_YIELD (); bytes_read = 0; } else { 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; if (IS_INVALID_FD (file_cookie->fd)) { ESTREAM_SYS_YIELD (); bytes_written = size; /* Yeah: Success writing to the bit bucket. */ } else { 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; if (IS_INVALID_FD (file_cookie->fd)) { _set_errno (ESPIPE); err = -1; } else { 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) { if (IS_INVALID_FD (fd_cookie->fd)) err = 0; else err = fd_cookie->no_close? 0 : 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. */ /* Cookie for fp objects. */ typedef struct estream_cookie_fp { FILE *fp; /* The file pointer we are using for actual output. */ int no_close; /* If set we won't close the file pointer. */ } *estream_cookie_fp_t; /* Create function for fd objects. */ static int es_func_fp_create (void **cookie, FILE *fp, unsigned int modeflags, int no_close) { estream_cookie_fp_t fp_cookie; int err; fp_cookie = mem_alloc (sizeof *fp_cookie); if (!fp_cookie) err = -1; else { #ifdef HAVE_DOSISH_SYSTEM /* Make sure it is in binary mode if requested. */ if ( (modeflags & O_BINARY) ) setmode (fileno (fp), O_BINARY); #else (void)modeflags; #endif fp_cookie->fp = fp; fp_cookie->no_close = no_close; *cookie = fp_cookie; err = 0; } return err; } /* Read function for FILE* objects. */ static ssize_t es_func_fp_read (void *cookie, void *buffer, size_t size) { estream_cookie_fp_t file_cookie = cookie; ssize_t bytes_read; if (file_cookie->fp) bytes_read = fread (buffer, 1, size, file_cookie->fp); else bytes_read = 0; if (!bytes_read && ferror (file_cookie->fp)) return -1; return bytes_read; } /* Write function for FILE* objects. */ static ssize_t es_func_fp_write (void *cookie, const void *buffer, size_t size) { estream_cookie_fp_t file_cookie = cookie; size_t bytes_written; if (file_cookie->fp) bytes_written = fwrite (buffer, 1, size, file_cookie->fp); else bytes_written = size; /* Successfully written to the bit bucket. */ if (bytes_written != size) return -1; return bytes_written; } /* Seek function for FILE* objects. */ static int es_func_fp_seek (void *cookie, off_t *offset, int whence) { estream_cookie_fp_t file_cookie = cookie; long int offset_new; if (!file_cookie->fp) { _set_errno (ESPIPE); return -1; } if ( fseek (file_cookie->fp, (long int)*offset, whence) ) { /* fprintf (stderr, "\nfseek failed: errno=%d (%s)\n", */ /* errno,strerror (errno)); */ return -1; } offset_new = ftell (file_cookie->fp); if (offset_new == -1) { /* fprintf (stderr, "\nftell failed: errno=%d (%s)\n", */ /* errno,strerror (errno)); */ return -1; } *offset = offset_new; return 0; } -/* Destroy function for fd objects. */ +/* Destroy function for FILE* objects. */ static int es_func_fp_destroy (void *cookie) { estream_cookie_fp_t fp_cookie = cookie; int err; if (fp_cookie) { if (fp_cookie->fp) { fflush (fp_cookie->fp); err = fp_cookie->no_close? 0 : fclose (fp_cookie->fp); } else err = 0; mem_free (fp_cookie); } else err = 0; return err; } static es_cookie_io_functions_t estream_functions_fp = { es_func_fp_read, es_func_fp_write, es_func_fp_seek, es_func_fp_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 modeflags) { 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, modeflags, ES_DEFAULT_OPEN_MODE); if (fd == -1) { err = -1; goto out; } #ifdef HAVE_DOSISH_SYSTEM /* Make sure it is in binary mode if requested. */ if ( (modeflags & O_BINARY) ) setmode (fd, O_BINARY); #endif file_cookie->fd = fd; file_cookie->no_close = 0; *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 - }; - static int es_convert_mode (const char *mode, unsigned int *modeflags) { unsigned int omode, oflags; switch (*mode) { case 'r': omode = O_RDONLY; oflags = 0; break; case 'w': omode = O_WRONLY; oflags = O_TRUNC | O_CREAT; break; case 'a': omode = O_WRONLY; oflags = O_APPEND | O_CREAT; break; default: _set_errno (EINVAL); return -1; } for (mode++; *mode; mode++) { switch (*mode) { case '+': omode = O_RDWR; break; case 'b': oflags |= O_BINARY; break; case 'x': oflags |= O_EXCL; break; default: /* Ignore unknown flags. */ break; } } *modeflags = (omode | oflags); return 0; } /* * Low level stream functionality. */ static int es_fill (estream_t stream) { size_t bytes_read = 0; int err; if (!stream->intern->func_read) { _set_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.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.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, unsigned int modeflags) { 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->print_err = 0; stream->intern->print_errno = 0; stream->intern->print_ntotal = 0; stream->intern->print_fp = NULL; stream->intern->indicators.err = 0; stream->intern->indicators.eof = 0; stream->intern->is_stdstream = 0; stream->intern->stdstream_fd = 0; stream->intern->deallocate_buffer = 0; stream->intern->printable_fname = NULL; stream->intern->printable_fname_inuse = 0; stream->data_len = 0; stream->data_offset = 0; stream->data_flushed = 0; stream->unread_data_len = 0; /* Depending on the modeflags we set whether we start in writing or reading mode. This is required in case we are working on a stream which is not seeekable (like stdout). Without this pre-initialization we would do a seek at the first write call and as this will fail no utput will be delivered. */ if ((modeflags & O_WRONLY) || (modeflags & O_RDWR) ) stream->flags.writing = 1; else stream->flags.writing = 0; } /* Deinitialize STREAM. */ static int es_deinitialize (estream_t stream) { es_cookie_close_function_t func_close; int err, tmp_err; if (stream->intern->print_fp) { int save_errno = errno; fclose (stream->intern->print_fp); stream->intern->print_fp = NULL; _set_errno (save_errno); } func_close = stream->intern->func_close; err = 0; if (stream->flags.writing) SET_UNLESS_NONZERO (err, tmp_err, es_flush (stream)); if (func_close) SET_UNLESS_NONZERO (err, tmp_err, (*func_close) (stream->intern->cookie)); mem_free (stream->intern->printable_fname); stream->intern->printable_fname = NULL; stream->intern->printable_fname_inuse = 0; 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, unsigned int modeflags, int with_locked_list) { 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, modeflags); err = es_list_add (stream_new, with_locked_list); 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 with_locked_list) { int err = 0; if (stream) { es_list_remove (stream, with_locked_list); 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.writing) { /* Switching to reading mode -> flush output. */ err = es_flush (stream); if (err) goto out; stream->flags.writing = 0; } /* 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 successfully 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) { _set_errno (EOPNOTSUPP); err = -1; goto out; } if (stream->flags.writing) { /* Flush data first in order to prevent flushing it to the wrong offset. */ err = es_flush (stream); if (err) goto out; stream->flags.writing = 0; } 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.writing) { /* Switching to writing mode -> discard input data and seek to position at which reading has stopped. We can do this only if a seek function has been registered. */ if (stream->intern->func_seek) { 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.writing) stream->flags.writing = 1; 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.writing) { /* Switching to reading mode -> flush output. */ err = es_flush (stream); if (err) goto out; stream->flags.writing = 0; } 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) { _set_errno (EINVAL); err = -1; } else { stream->data_offset += size; err = 0; } return err; } static int doreadline (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, mem_realloc, mem_free, O_RDWR, 0); if (err) goto out; err = es_create (&line_stream, line_stream_cookie, -1, estream_functions_mem, O_RDWR, 0); 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, 0); 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; } /* Output fucntion used for estream_format. */ static int print_writer (void *outfncarg, const char *buf, size_t buflen) { estream_t stream = outfncarg; size_t nwritten; int rc; nwritten = 0; rc = es_writen (stream, buf, buflen, &nwritten); stream->intern->print_ntotal += nwritten; return rc; } /* The core of our printf function. This is called in locked state. */ static int es_print (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap) { int rc; stream->intern->print_ntotal = 0; rc = estream_format (print_writer, stream, format, ap); if (rc) return -1; return (int)stream->intern->print_ntotal; } 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.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 { if (!size) size = BUFSIZ; 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 modeflags; int create_called; estream_t stream; void *cookie; int err; int fd; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &modeflags); if (err) goto out; err = es_func_file_create (&cookie, &fd, path, modeflags); if (err) goto out; create_called = 1; - err = es_create (&stream, cookie, fd, estream_functions_file, modeflags, 0); + err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags, 0); if (err) goto out; if (stream && path) fname_set_internal (stream, path, 1); out: if (err && create_called) - (*estream_functions_file.func_close) (cookie); + (*estream_functions_fd.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 modeflags; int create_called; estream_t stream; void *cookie; int err; cookie = 0; stream = NULL; create_called = 0; err = es_convert_mode (mode, &modeflags); if (err) goto out; err = es_func_mem_create (&cookie, data, data_n, data_len, BUFFER_BLOCK_SIZE, grow, func_realloc, func_free, modeflags, 0); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, -1, estream_functions_mem, modeflags, 0); out: if (err && create_called) (*estream_functions_mem.func_close) (cookie); return stream; } estream_t es_fopenmem (size_t memlimit, const char *ES__RESTRICT mode) { unsigned int modeflags; estream_t stream = NULL; void *cookie = NULL; /* Memory streams are always read/write. We use MODE only to get the append flag. */ if (es_convert_mode (mode, &modeflags)) return NULL; modeflags |= O_RDWR; if (es_func_mem_create (&cookie, NULL, 0, 0, BUFFER_BLOCK_SIZE, 1, mem_realloc, mem_free, modeflags, memlimit)) return NULL; if (es_create (&stream, cookie, -1, estream_functions_mem, modeflags, 0)) (*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 modeflags; estream_t stream; int err; stream = NULL; modeflags = 0; err = es_convert_mode (mode, &modeflags); if (err) goto out; err = es_create (&stream, cookie, -1, functions, modeflags, 0); if (err) goto out; out: return stream; } estream_t do_fdopen (int filedes, const char *mode, int no_close, int with_locked_list) { unsigned int modeflags; int create_called; estream_t stream; void *cookie; int err; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &modeflags); if (err) goto out; err = es_func_fd_create (&cookie, filedes, modeflags, no_close); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, filedes, estream_functions_fd, modeflags, with_locked_list); out: if (err && create_called) (*estream_functions_fd.func_close) (cookie); return stream; } estream_t es_fdopen (int filedes, const char *mode) { return do_fdopen (filedes, mode, 0, 0); } /* A variant of es_fdopen which does not close FILEDES at the end. */ estream_t es_fdopen_nc (int filedes, const char *mode) { return do_fdopen (filedes, mode, 1, 0); } estream_t do_fpopen (FILE *fp, const char *mode, int no_close, int with_locked_list) { unsigned int modeflags; int create_called; estream_t stream; void *cookie; int err; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &modeflags); if (err) goto out; if (fp) fflush (fp); err = es_func_fp_create (&cookie, fp, modeflags, no_close); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, fp? fileno (fp):-1, estream_functions_fp, modeflags, with_locked_list); out: if (err && create_called) (*estream_functions_fp.func_close) (cookie); return stream; } /* Create an estream from the stdio stream FP. This mechanism is useful in case the stdio streams have special properties and may not be mixed with fd based functions. This is for example the case under Windows where the 3 standard streams are associated with the console whereas a duped and fd-opened stream of one of this stream won't be associated with the console. As this messes things up it is easier to keep on using the standard I/O stream as a backend for estream. */ estream_t es_fpopen (FILE *fp, const char *mode) { return do_fpopen (fp, mode, 0, 0); } /* Same as es_fpopen but does not close FP at the end. */ estream_t es_fpopen_nc (FILE *fp, const char *mode) { return do_fpopen (fp, mode, 1, 0); } /* Set custom standard descriptors to be used for stdin, stdout and stderr. This function needs to be called before any of the standard streams are accessed. */ void _es_set_std_fd (int no, int fd) { ESTREAM_LIST_LOCK; if (no >= 0 && no < 3 && !custom_std_fds_valid[no]) { custom_std_fds[no] = fd; custom_std_fds_valid[no] = 1; } ESTREAM_LIST_UNLOCK; } /* Return the stream used for stdin, stdout or stderr. */ estream_t _es_get_std_stream (int fd) { estream_list_t list_obj; estream_t stream = NULL; fd %= 3; /* We only allow 0, 1 or 2 but we don't want to return an error. */ ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) if (list_obj->car->intern->is_stdstream && list_obj->car->intern->stdstream_fd == fd) { stream = list_obj->car; break; } if (!stream) { /* Standard stream not yet created. We first try to create them from registered file descriptors. */ if (!fd && custom_std_fds_valid[0]) stream = do_fdopen (custom_std_fds[0], "r", 1, 1); else if (fd == 1 && custom_std_fds_valid[1]) stream = do_fdopen (custom_std_fds[1], "a", 1, 1); else if (custom_std_fds_valid[2]) stream = do_fdopen (custom_std_fds[2], "a", 1, 1); if (!stream) { /* Second try is to use the standard C streams. */ if (!fd) stream = do_fpopen (stdin, "r", 1, 1); else if (fd == 1) stream = do_fpopen (stdout, "a", 1, 1); else stream = do_fpopen (stderr, "a", 1, 1); } if (!stream) { /* Last try: Create a bit bucket. */ stream = do_fpopen (NULL, fd? "a":"r", 0, 1); if (!stream) { fprintf (stderr, "fatal: error creating a dummy estream" " for %d: %s\n", fd, strerror (errno)); abort(); } } stream->intern->is_stdstream = 1; stream->intern->stdstream_fd = fd; if (fd == 2) es_set_buffering (stream, NULL, _IOLBF, 0); fname_set_internal (stream, fd == 0? "[stdin]" : fd == 1? "[stdout]" : "[stderr]", 0); } ESTREAM_LIST_UNLOCK; 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 modeflags; int create_called; void *cookie; int fd; cookie = NULL; create_called = 0; ESTREAM_LOCK (stream); es_deinitialize (stream); err = es_convert_mode (mode, &modeflags); if (err) goto leave; err = es_func_file_create (&cookie, &fd, path, modeflags); if (err) goto leave; create_called = 1; - es_initialize (stream, cookie, fd, estream_functions_file, modeflags); + es_initialize (stream, cookie, fd, estream_functions_fd, modeflags); leave: if (err) { if (create_called) es_func_fd_destroy (cookie); es_destroy (stream, 0); stream = NULL; } else { if (stream && path) fname_set_internal (stream, path, 1); ESTREAM_UNLOCK (stream); } } else { /* FIXME? We don't support re-opening at the moment. */ _set_errno (EINVAL); es_deinitialize (stream); es_destroy (stream, 0); stream = NULL; } return stream; } int es_fclose (estream_t stream) { int err; err = es_destroy (stream, 0); 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); } static int do_fflush (estream_t stream) { int err; if (stream->flags.writing) err = es_flush (stream); else { es_empty (stream); err = 0; } return err; } int es_fflush (estream_t stream) { int err; if (stream) { ESTREAM_LOCK (stream); err = do_fflush (stream); ESTREAM_UNLOCK (stream); } else err = es_list_iterate (do_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 buffer, int length, estream_t ES__RESTRICT stream) { unsigned char *s = (unsigned char*)buffer; int c; if (!length) return NULL; c = EOF; ESTREAM_LOCK (stream); while (length > 1 && (c = es_getc_unlocked (stream)) != EOF && c != '\n') { *s++ = c; length--; } ESTREAM_UNLOCK (stream); if (c == EOF && s == (unsigned char*)buffer) return NULL; /* Nothing read. */ if (c != EOF && length > 1) *s++ = c; *s = 0; return buffer; } int es_fputs_unlocked (const char *ES__RESTRICT s, estream_t ES__RESTRICT stream) { size_t length; int err; length = strlen (s); err = es_writen (stream, s, length, NULL); return err ? EOF : 0; } 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 = doreadline (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 : (ssize_t)line_n; } /* 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 or another error occurred; ERRNO is then 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 starts with the next line. Note that MAX_LENGTH must be re-initialzied in this case. The caller initially needs to provide the address of a variable, initialized to NULL, at ADDR_OF_BUFFER and don't change this value anymore with the following invocations. LENGTH_OF_BUFFER should be the address of a variable, initialized to 0, which is also maintained by this function. Thus, both paramaters should be considered the state of this function. Note: The returned buffer is allocated with enough extra space to allow the caller to append a CR,LF,Nul. The buffer should be released using es_free. */ ssize_t es_read_line (estream_t stream, 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 = mem_alloc (length); *addr_of_buffer = buffer; if (!buffer) { *length_of_buffer = 0; if (max_length) *max_length = 0; return -1; } *length_of_buffer = length; } if (length < 4) { /* This should never happen. If it does, the function has been called with wrong arguments. */ _set_errno (EINVAL); return -1; } length -= 3; /* Reserve 3 bytes for CR,LF,EOL. */ ESTREAM_LOCK (stream); p = buffer; while ((c = es_getc_unlocked (stream)) != EOF) { if (nbytes == length) { /* Enlarge the buffer. */ if (maxlen && length > maxlen) { /* We are beyond our limit: Skip the rest of the line. */ while (c != '\n' && (c=es_getc_unlocked (stream)) != 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 = mem_realloc (buffer, length); if (!*addr_of_buffer) { int save_errno = errno; mem_free (buffer); *length_of_buffer = 0; if (max_length) *max_length = 0; ESTREAM_UNLOCK (stream); _set_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. */ ESTREAM_UNLOCK (stream); return nbytes; } /* Wrapper around free() to match the memory allocation system used by estream. Should be used for all buffers returned to the caller by libestream. */ void es_free (void *a) { mem_free (a); } int es_vfprintf_unlocked (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, va_list ap) { return es_print (stream, format, ap); } 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_unlocked (estream_t ES__RESTRICT stream, const char *ES__RESTRICT format, ...) { int ret; va_list ap; va_start (ap, format); ret = es_print (stream, format, ap); va_end (ap); 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; } /* A variant of asprintf. The function returns the allocated buffer or NULL on error; ERRNO is set in the error case. The caller should use es_free to release the buffer. This function actually belongs into estream-printf but we put it here as a convenience and because es_free is required anyway. */ char * es_asprintf (const char *ES__RESTRICT format, ...) { int rc; va_list ap; char *buf; va_start (ap, format); rc = estream_vasprintf (&buf, format, ap); va_end (ap); if (rc < 0) return NULL; return buf; } /* A variant of vasprintf. The function returns the allocated buffer or NULL on error; ERRNO is set in the error case. The caller should use es_free to release the buffer. This function actually belongs into estream-printf but we put it here as a convenience and because es_free is required anyway. */ char * es_vasprintf (const char *ES__RESTRICT format, va_list ap) { int rc; char *buf; rc = estream_vasprintf (&buf, format, ap); if (rc < 0) return NULL; return buf; } static int tmpfd (void) { #ifdef HAVE_W32_SYSTEM int attempts, n; #ifdef HAVE_W32CE_SYSTEM wchar_t buffer[MAX_PATH+9+12+1]; # define mystrlen(a) wcslen (a) wchar_t *name, *p; #else char buffer[MAX_PATH+9+12+1]; # define mystrlen(a) strlen (a) char *name, *p; #endif HANDLE file; int pid = GetCurrentProcessId (); unsigned int value; int i; n = GetTempPath (MAX_PATH+1, buffer); if (!n || n > MAX_PATH || mystrlen (buffer) > MAX_PATH) { _set_errno (ENOENT); return -1; } p = buffer + mystrlen (buffer); #ifdef HAVE_W32CE_SYSTEM wcscpy (p, L"_estream"); #else strcpy (p, "_estream"); #endif p += 8; /* We try to create the directory but don't care about an error as it may already exist and the CreateFile would throw an error anyway. */ CreateDirectory (buffer, NULL); *p++ = '\\'; name = p; for (attempts=0; attempts < 10; attempts++) { p = name; value = (GetTickCount () ^ ((pid<<16) & 0xffff0000)); for (i=0; i < 8; i++) { *p++ = tohex (((value >> 28) & 0x0f)); value <<= 4; } #ifdef HAVE_W32CE_SYSTEM wcscpy (p, L".tmp"); #else strcpy (p, ".tmp"); #endif file = CreateFile (buffer, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); if (file != INVALID_HANDLE_VALUE) { #ifdef HAVE_W32CE_SYSTEM int fd = (int)file; #else int fd = _open_osfhandle ((long)file, 0); if (fd == -1) { CloseHandle (file); return -1; } #endif return fd; } Sleep (1); /* One ms as this is the granularity of GetTickCount. */ } _set_errno (ENOENT); return -1; #else /*!HAVE_W32_SYSTEM*/ 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; #endif /*!HAVE_W32_SYSTEM*/ } estream_t es_tmpfile (void) { unsigned int modeflags; int create_called; estream_t stream; void *cookie; int err; int fd; create_called = 0; stream = NULL; modeflags = O_RDWR | O_TRUNC | O_CREAT; cookie = NULL; fd = tmpfd (); if (fd == -1) { err = -1; goto out; } err = es_func_fd_create (&cookie, fd, modeflags, 0); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags, 0); 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) && (!buf || size || type == _IONBF)) { ESTREAM_LOCK (stream); err = es_set_buffering (stream, buf, type, size); ESTREAM_UNLOCK (stream); } else { _set_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; } static void fname_set_internal (estream_t stream, const char *fname, int quote) { if (stream->intern->printable_fname && !stream->intern->printable_fname_inuse) { mem_free (stream->intern->printable_fname); stream->intern->printable_fname = NULL; } if (stream->intern->printable_fname) return; /* Can't change because it is in use. */ if (*fname != '[') quote = 0; else quote = !!quote; stream->intern->printable_fname = mem_alloc (strlen (fname) + quote + 1); if (fname) { if (quote) stream->intern->printable_fname[0] = '\\'; strcpy (stream->intern->printable_fname+quote, fname); } } /* Set the filename attribute of STREAM. There is no error return. as long as STREAM is valid. This function is called internally by functions which open a filename. */ void es_fname_set (estream_t stream, const char *fname) { if (fname) { ESTREAM_LOCK (stream); fname_set_internal (stream, fname, 1); ESTREAM_UNLOCK (stream); } } /* Return the filename attribute of STREAM. In case no filename has been set, "[?]" will be returned. The returned file name is valid as long as STREAM is valid. */ const char * es_fname_get (estream_t stream) { const char *fname; ESTREAM_LOCK (stream); fname = stream->intern->printable_fname; if (fname) stream->intern->printable_fname_inuse = 1; ESTREAM_UNLOCK (stream); if (!fname) fname = "[?]"; return fname; } /* Print a BUFFER to STREAM while replacing all control characters and the characters in DELIMITERS by standard C escape sequences. Returns 0 on success or -1 on error. If BYTES_WRITTEN is not NULL the number of bytes actually written are stored at this address. */ int es_write_sanitized (estream_t ES__RESTRICT stream, const void * ES__RESTRICT buffer, size_t length, const char * delimiters, size_t * ES__RESTRICT bytes_written) { const unsigned char *p = buffer; size_t count = 0; int ret; ESTREAM_LOCK (stream); for (; length; length--, p++, count++) { if (*p < 0x20 || *p == 0x7f || (delimiters && (strchr (delimiters, *p) || *p == '\\'))) { es_putc_unlocked ('\\', stream); count++; if (*p == '\n') { es_putc_unlocked ('n', stream); count++; } else if (*p == '\r') { es_putc_unlocked ('r', stream); count++; } else if (*p == '\f') { es_putc_unlocked ('f', stream); count++; } else if (*p == '\v') { es_putc_unlocked ('v', stream); count++; } else if (*p == '\b') { es_putc_unlocked ('b', stream); count++; } else if (!*p) { es_putc_unlocked('0', stream); count++; } else { es_fprintf_unlocked (stream, "x%02x", *p); count += 3; } } else { es_putc_unlocked (*p, stream); count++; } } if (bytes_written) *bytes_written = count; ret = es_ferror_unlocked (stream)? -1 : 0; ESTREAM_UNLOCK (stream); return ret; } /* Write LENGTH bytes of BUFFER to STREAM as a hex encoded string. RESERVED must be 0. Returns 0 on success or -1 on error. If BYTES_WRITTEN is not NULL the number of bytes actually written are stored at this address. */ int es_write_hexstring (estream_t ES__RESTRICT stream, const void *ES__RESTRICT buffer, size_t length, int reserved, size_t *ES__RESTRICT bytes_written ) { int ret; const unsigned char *s; size_t count = 0; (void)reserved; #define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) if (!length) return 0; ESTREAM_LOCK (stream); for (s = buffer; length; s++, length--) { es_putc_unlocked ( tohex ((*s>>4)&15), stream); es_putc_unlocked ( tohex (*s&15), stream); count += 2; } if (bytes_written) *bytes_written = count; ret = es_ferror_unlocked (stream)? -1 : 0; ESTREAM_UNLOCK (stream); return ret; #undef tohex } #ifdef GNUPG_MAJOR_VERSION /* Special estream function to print an UTF8 string in the native encoding. The interface is the same as es_write_sanitized, however only one delimiter may be supported. THIS IS NOT A STANDARD ESTREAM FUNCTION AND ONLY USED BY GNUPG!. */ int es_write_sanitized_utf8_buffer (estream_t stream, const void *buffer, size_t length, const char *delimiters, size_t *bytes_written) { 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) { int delim = delimiters? *delimiters : 0; char *buf; int ret; /*(utf8 conversion already does the control character quoting). */ buf = utf8_to_native (p, length, delim); if (bytes_written) *bytes_written = strlen (buf); ret = es_fputs (buf, stream); xfree (buf); return ret == EOF? ret : (int)i; } else return es_write_sanitized (stream, p, length, delimiters, bytes_written); } #endif /*GNUPG_MAJOR_VERSION*/ diff --git a/common/http.c b/common/http.c index bb39768e4..ab159e28e 100644 --- a/common/http.c +++ b/common/http.c @@ -1,2071 +1,2128 @@ /* http.c - HTTP protocol handler * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, - * 2009 Free Software Foundation, Inc. + * 2009, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* Simple HTTP client implementation. We try to keep the code as self-contained as possible. There are some contraints however: - stpcpy is required - fixme: list other requirements. - With HTTP_USE_ESTREAM defined, all I/O is done through estream. - With HTTP_USE_GNUTLS support for https is provided (this also requires estream). - With HTTP_NO_WSASTARTUP the socket initialization is not done under Windows. This is useful if the socket layer has already been initialized elsewhere. This also avoids the installation of an exit handler to cleanup the socket layer. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # include #else /*!HAVE_W32_SYSTEM*/ # include # include # include # include # include # include # include #endif /*!HAVE_W32_SYSTEM*/ +#ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth. */ +# undef HAVE_PTH +# undef USE_GNU_PTH +#endif + +#ifdef HAVE_PTH +# include +#endif + + #ifdef HTTP_USE_GNUTLS # include /* For non-understandable reasons GNUTLS dropped the _t suffix from all types. yes, ISO-C might be read as this but there are still other name space conflicts and using _t is actually a Good Thing. */ typedef gnutls_session gnutls_session_t; typedef gnutls_transport_ptr gnutls_transport_ptr_t; #endif /*HTTP_USE_GNUTLS*/ #ifdef TEST #undef USE_DNS_SRV #endif #include "util.h" #include "i18n.h" #include "http.h" #ifdef USE_DNS_SRV -#include "srv.h" +# include "srv.h" #else /*!USE_DNS_SRV*/ -/* If we are not compiling with SRV record support we provide stub - data structures. */ -#ifndef MAXDNAME -#define MAXDNAME 1025 -#endif + /* If we are not compiling with SRV record support we provide stub + data structures. */ +# ifndef MAXDNAME +# define MAXDNAME 1025 +# endif struct srventry { unsigned short priority; unsigned short weight; unsigned short port; int run_count; char target[MAXDNAME]; }; #endif/*!USE_DNS_SRV*/ #ifdef HAVE_W32_SYSTEM #define sock_close(a) closesocket(a) #else #define sock_close(a) close(a) #endif #ifndef EAGAIN #define EAGAIN EWOULDBLOCK #endif #define HTTP_PROXY_ENV "http_proxy" #define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */ #define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "01234567890@" \ "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" +/* A long counter type. */ +#ifdef HAVE_STRTOULL +typedef unsigned long long longcounter_t; +# define counter_strtoul(a) strtoull ((a), NULL, 10) +#else +typedef unsigned long longcounter_t; +# define counter_strtoul(a) strtoul ((a), NULL, 10) +#endif + /* Define a prefix to map stream functions to the estream library. */ #ifdef HTTP_USE_ESTREAM #define P_ES(a) es_ ## a #else #define P_ES(a) a #endif #ifndef HTTP_USE_GNUTLS typedef void * gnutls_session_t; #endif #if defined(HTTP_USE_GNUTLS) && !defined(HTTP_USE_ESTREAM) #error Use of GNUTLS also requires support for Estream #endif static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part); static int remove_escapes (char *string); static int insert_escapes (char *buffer, const char *string, const char *special); static uri_tuple_t parse_tuple (char *string); static gpg_error_t send_request (http_t hd, const char *auth,const char *proxy, const char *srvtag,strlist_t headers); static char *build_rel_path (parsed_uri_t uri); static gpg_error_t parse_response (http_t hd); static int connect_server (const char *server, unsigned short port, unsigned int flags, const char *srvtag); static gpg_error_t write_server (int sock, const char *data, size_t length); #ifdef HTTP_USE_ESTREAM static ssize_t cookie_read (void *cookie, void *buffer, size_t size); static ssize_t cookie_write (void *cookie, const void *buffer, size_t size); static int cookie_close (void *cookie); static es_cookie_io_functions_t cookie_functions = { cookie_read, cookie_write, NULL, cookie_close }; struct cookie_s { - int fd; /* File descriptor or -1 if already closed. */ - gnutls_session_t tls_session; /* TLS session context or NULL if not used. */ - int keep_socket; /* Flag to communicate with teh close handler. */ + /* File descriptor or -1 if already closed. */ + int fd; + + /* TLS session context or NULL if not used. */ + gnutls_session_t tls_session; + + /* The remaining content length and a flag telling whether to use + the content length. */ + longcounter_t content_length; + unsigned int content_length_valid:1; + + /* Flag to communicate with the close handler. */ + unsigned int keep_socket:1; }; typedef struct cookie_s *cookie_t; #endif /*HTTP_USE_ESTREAM*/ #ifdef HTTP_USE_GNUTLS static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int); #endif /*HTTP_USE_GNUTLS*/ /* An object to save header lines. */ struct header_s { struct header_s *next; char *value; /* The value of the header (malloced). */ char name[1]; /* The name of the header (canonicalized). */ }; typedef struct header_s *header_t; /* Our handle context. */ struct http_context_s { unsigned int status_code; int sock; - int in_data; + unsigned int in_data:1; + unsigned int is_http_0_9:1; #ifdef HTTP_USE_ESTREAM estream_t fp_read; estream_t fp_write; void *write_cookie; + void *read_cookie; #else /*!HTTP_USE_ESTREAM*/ FILE *fp_read; FILE *fp_write; #endif /*!HTTP_USE_ESTREAM*/ void *tls_context; - int is_http_0_9; parsed_uri_t uri; http_req_t req_type; char *buffer; /* Line buffer. */ size_t buffer_size; unsigned int flags; header_t headers; /* Received headers. */ }; #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP) #if GNUPG_MAJOR_VERSION == 1 #define REQ_WINSOCK_MAJOR 1 #define REQ_WINSOCK_MINOR 1 #else #define REQ_WINSOCK_MAJOR 2 #define REQ_WINSOCK_MINOR 2 #endif static void deinit_sockets (void) { WSACleanup(); } static void init_sockets (void) { static int initialized; static WSADATA wsdata; if (initialized) return; if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) ) { log_error ("error initializing socket library: ec=%d\n", (int)WSAGetLastError () ); return; } if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR ) { log_error ("socket library version is %x.%x - but %d.%d needed\n", LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion), REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR); WSACleanup(); return; } atexit ( deinit_sockets ); initialized = 1; } #endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/ /* * Helper function to create an HTTP header with hex encoded data. A * new buffer is returned. This buffer is the concatenation of the * string PREFIX, the hex-encoded DATA of length LEN and the string * SUFFIX. On error NULL is returned and ERRNO set. */ static char * make_header_line (const char *prefix, const char *suffix, const void *data, size_t len ) { static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; const unsigned int *s = data; char *buffer, *p; buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1); if (!buffer) return NULL; p = stpcpy (buffer, prefix); for ( ; len >= 3 ; len -= 3, s += 3 ) { *p++ = bintoasc[(s[0] >> 2) & 077]; *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; *p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077]; *p++ = bintoasc[s[2]&077]; } if ( len == 2 ) { *p++ = bintoasc[(s[0] >> 2) & 077]; *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; *p++ = bintoasc[((s[1]<<2)&074)]; *p++ = '='; } else if ( len == 1 ) { *p++ = bintoasc[(s[0] >> 2) & 077]; *p++ = bintoasc[(s[0] <<4)&060]; *p++ = '='; *p++ = '='; } strcpy (p, suffix); return buffer; } void http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) ) { #ifdef HTTP_USE_GNUTLS tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb; #else (void)cb; #endif } /* Start a HTTP retrieval and return on success in R_HD a context pointer for completing the the request and to wait for the response. */ gpg_error_t http_open (http_t *r_hd, http_req_t reqtype, const char *url, const char *auth, unsigned int flags, const char *proxy, void *tls_context, const char *srvtag,strlist_t headers) { gpg_error_t err; http_t hd; *r_hd = NULL; if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST)) return gpg_error (GPG_ERR_INV_ARG); /* Create the handle. */ hd = xtrycalloc (1, sizeof *hd); if (!hd) return gpg_error_from_syserror (); hd->sock = -1; hd->req_type = reqtype; hd->flags = flags; hd->tls_context = tls_context; err = http_parse_uri (&hd->uri, url); if (!err) err = send_request (hd, auth, proxy, srvtag, headers); if (err) { if (!hd->fp_read && !hd->fp_write && hd->sock != -1) sock_close (hd->sock); if (hd->fp_read) P_ES(fclose) (hd->fp_read); if (hd->fp_write) P_ES(fclose) (hd->fp_write); http_release_parsed_uri (hd->uri); xfree (hd); } else *r_hd = hd; return err; } void http_start_data (http_t hd) { if (!hd->in_data) { #ifdef HTTP_USE_ESTREAM es_fputs ("\r\n", hd->fp_write); es_fflush (hd->fp_write); #else fflush (hd->fp_write); write_server (hd->sock, "\r\n", 2); #endif hd->in_data = 1; } else P_ES(fflush) (hd->fp_write); } gpg_error_t http_wait_response (http_t hd) { gpg_error_t err; /* Make sure that we are in the data. */ http_start_data (hd); /* We dup the socket, to cope with the fact that fclose closes the underlying socket. In TLS mode we don't do that because we can't close the socket gnutls is working on; instead we make sure that the fclose won't close the socket in this case. */ #ifdef HTTP_USE_ESTREAM if (hd->write_cookie) { /* The write cookie is only set in the TLS case. */ cookie_t cookie = hd->write_cookie; cookie->keep_socket = 1; } else #endif /*HTTP_USE_ESTREAM*/ { #ifdef HAVE_W32_SYSTEM HANDLE handle = (HANDLE)hd->sock; if (!DuplicateHandle (GetCurrentProcess(), handle, GetCurrentProcess(), &handle, 0, TRUE, DUPLICATE_SAME_ACCESS )) return gpg_error_from_syserror (); hd->sock = (int)handle; #else hd->sock = dup (hd->sock); #endif if (hd->sock == -1) return gpg_error_from_syserror (); } P_ES(fclose) (hd->fp_write); hd->fp_write = NULL; #ifdef HTTP_USE_ESTREAM hd->write_cookie = NULL; #endif - if (!(hd->flags & HTTP_FLAG_NO_SHUTDOWN)) + /* Shutdown one end of the socket is desired. As per HTTP/1.0 this + is not required but some very old servers (e.g. the original pksd + key server didn't worked without it. */ + if ((hd->flags & HTTP_FLAG_SHUTDOWN)) shutdown (hd->sock, 1); hd->in_data = 0; #ifdef HTTP_USE_ESTREAM { cookie_t cookie; cookie = xtrycalloc (1, sizeof *cookie); if (!cookie) return gpg_error_from_syserror (); cookie->fd = hd->sock; if (hd->uri->use_tls) cookie->tls_session = hd->tls_context; hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); if (!hd->fp_read) { xfree (cookie); return gpg_error_from_syserror (); } } #else /*!HTTP_USE_ESTREAM*/ hd->fp_read = fdopen (hd->sock, "r"); if (!hd->fp_read) return gpg_error_from_syserror (); #endif /*!HTTP_USE_ESTREAM*/ err = parse_response (hd); return err; } /* Convenience function to send a request and wait for the response. Closes the handle on error. If PROXY is not NULL, this value will be used as an HTTP proxy and any enabled $http_proxy gets ignored. */ gpg_error_t http_open_document (http_t *r_hd, const char *document, const char *auth, unsigned int flags, const char *proxy, void *tls_context, const char *srvtag,strlist_t headers) { gpg_error_t err; err = http_open (r_hd, HTTP_REQ_GET, document, auth, flags, proxy, tls_context, srvtag, headers); if (err) return err; err = http_wait_response (*r_hd); if (err) http_close (*r_hd, 0); return err; } void http_close (http_t hd, int keep_read_stream) { if (!hd) return; if (!hd->fp_read && !hd->fp_write && hd->sock != -1) sock_close (hd->sock); if (hd->fp_read && !keep_read_stream) P_ES(fclose) (hd->fp_read); if (hd->fp_write) P_ES(fclose) (hd->fp_write); http_release_parsed_uri (hd->uri); while (hd->headers) { header_t tmp = hd->headers->next; xfree (hd->headers->value); xfree (hd->headers); hd->headers = tmp; } xfree (hd->buffer); xfree (hd); } #ifdef HTTP_USE_ESTREAM estream_t http_get_read_ptr (http_t hd) { return hd?hd->fp_read:NULL; } estream_t http_get_write_ptr (http_t hd) { return hd?hd->fp_write:NULL; } #else /*!HTTP_USE_ESTREAM*/ FILE * http_get_read_ptr (http_t hd) { return hd?hd->fp_read:NULL; } FILE * http_get_write_ptr (http_t hd) { return hd?hd->fp_write:NULL; } #endif /*!HTTP_USE_ESTREAM*/ unsigned int http_get_status_code (http_t hd) { return hd?hd->status_code:0; } /* * Parse an URI and put the result into the newly allocated RET_URI. * The caller must always use release_parsed_uri() to releases the * resources (even on error). */ gpg_error_t http_parse_uri (parsed_uri_t * ret_uri, const char *uri) { - *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri)); + *ret_uri = xtrycalloc (1, sizeof **ret_uri + strlen (uri)); + if (!*ret_uri) + return gpg_error_from_syserror (); strcpy ((*ret_uri)->buffer, uri); return do_parse_uri (*ret_uri, 0); } void http_release_parsed_uri (parsed_uri_t uri) { if (uri) { uri_tuple_t r, r2; for (r = uri->query; r; r = r2) { r2 = r->next; xfree (r); } xfree (uri); } } static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part) { uri_tuple_t *tail; char *p, *p2, *p3, *pp; int n; p = uri->buffer; n = strlen (uri->buffer); /* Initialize all fields to an empty string or an empty list. */ uri->scheme = uri->host = uri->path = p + n; uri->port = 0; uri->params = uri->query = NULL; uri->use_tls = 0; /* A quick validity check. */ if (strspn (p, VALID_URI_CHARS) != n) return gpg_error (GPG_ERR_BAD_URI); /* Invalid characters found. */ if (!only_local_part) { /* Find the scheme. */ if (!(p2 = strchr (p, ':')) || p2 == p) return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */ *p2++ = 0; for (pp=p; *pp; pp++) *pp = tolower (*(unsigned char*)pp); uri->scheme = p; if (!strcmp (uri->scheme, "http")) uri->port = 80; #ifdef HTTP_USE_GNUTLS else if (!strcmp (uri->scheme, "https")) { uri->port = 443; uri->use_tls = 1; } #endif else return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */ p = p2; /* Find the hostname */ if (*p != '/') return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */ p++; if (*p == '/') /* There seems to be a hostname. */ { p++; if ((p2 = strchr (p, '/'))) *p2++ = 0; /* Check for username/password encoding */ if ((p3 = strchr (p, '@'))) { uri->auth = p; *p3++ = '\0'; p = p3; } for (pp=p; *pp; pp++) *pp = tolower (*(unsigned char*)pp); /* Handle an IPv6 literal */ if( *p == '[' && (p3=strchr( p, ']' )) ) { *p3++ = '\0'; /* worst case, uri->host should have length 0, points to \0 */ uri->host = p + 1; p = p3; } else uri->host = p; if ((p3 = strchr (p, ':'))) { *p3++ = '\0'; uri->port = atoi (p3); } if ((n = remove_escapes (uri->host)) < 0) return gpg_error (GPG_ERR_BAD_URI); if (n != strlen (uri->host)) return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */ p = p2 ? p2 : NULL; } } /* End global URI part. */ /* Parse the pathname part */ if (!p || !*p) return 0; /* We don't have a path. Okay. */ /* TODO: Here we have to check params. */ /* Do we have a query part? */ if ((p2 = strchr (p, '?'))) *p2++ = 0; uri->path = p; if ((n = remove_escapes (p)) < 0) return gpg_error (GPG_ERR_BAD_URI); if (n != strlen (p)) return gpg_error (GPG_ERR_BAD_URI); /* Path includes a Nul. */ p = p2 ? p2 : NULL; if (!p || !*p) return 0; /* We don't have a query string. Okay. */ /* Now parse the query string. */ tail = &uri->query; for (;;) { uri_tuple_t elem; if ((p2 = strchr (p, '&'))) *p2++ = 0; if (!(elem = parse_tuple (p))) return gpg_error (GPG_ERR_BAD_URI); *tail = elem; tail = &elem->next; if (!p2) break; /* Ready. */ p = p2; } return 0; } /* * Remove all %xx escapes; this is done in-place. Returns: New length * of the string. */ static int remove_escapes (char *string) { int n = 0; unsigned char *p, *s; for (p = s = (unsigned char*)string; *s; s++) { if (*s == '%') { if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2])) { s++; *p = *s >= '0' && *s <= '9' ? *s - '0' : *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; *p <<= 4; s++; *p |= *s >= '0' && *s <= '9' ? *s - '0' : *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; p++; n++; } else { *p++ = *s++; if (*s) *p++ = *s++; if (*s) *p++ = *s++; if (*s) *p = 0; return -1; /* Bad URI. */ } } else { *p++ = *s; n++; } } *p = 0; /* Make sure to keep a string terminator. */ return n; } static int insert_escapes (char *buffer, const char *string, const char *special) { const unsigned char *s = (const unsigned char*)string; int n = 0; for (; *s; s++) { if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s)) { if (buffer) *(unsigned char*)buffer++ = *s; n++; } else { if (buffer) { sprintf (buffer, "%%%02X", *s); buffer += 3; } n += 3; } } return n; } /* Allocate a new string from STRING using standard HTTP escaping as well as escaping of characters given in SPECIALS. A common pattern for SPECIALS is "%;?&=". However it depends on the needs, for example "+" and "/: often needs to be escaped too. Returns NULL on failure and sets ERRNO. */ char * http_escape_string (const char *string, const char *specials) { int n; char *buf; n = insert_escapes (NULL, string, specials); buf = xtrymalloc (n+1); if (buf) { insert_escapes (buf, string, specials); buf[n] = 0; } return buf; } static uri_tuple_t parse_tuple (char *string) { char *p = string; char *p2; int n; uri_tuple_t tuple; if ((p2 = strchr (p, '='))) *p2++ = 0; if ((n = remove_escapes (p)) < 0) return NULL; /* Bad URI. */ if (n != strlen (p)) return NULL; /* Name with a Nul in it. */ tuple = xtrycalloc (1, sizeof *tuple); if (!tuple) return NULL; /* Out of core. */ tuple->name = p; if (!p2) /* We have only the name, so we assume an empty value string. */ { tuple->value = p + strlen (p); tuple->valuelen = 0; tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */ } else /* Name and value. */ { if ((n = remove_escapes (p2)) < 0) { xfree (tuple); return NULL; /* Bad URI. */ } tuple->value = p2; tuple->valuelen = n; } return tuple; } /* * Send a HTTP request to the server * Returns 0 if the request was successful */ static gpg_error_t send_request (http_t hd, const char *auth, const char *proxy,const char *srvtag,strlist_t headers) { gnutls_session_t tls_session; gpg_error_t err; const char *server; char *request, *p; unsigned short port; const char *http_proxy = NULL; char *proxy_authstr = NULL; char *authstr = NULL; int save_errno; tls_session = hd->tls_context; if (hd->uri->use_tls && !tls_session) { log_error ("TLS requested but no GNUTLS context provided\n"); return gpg_error (GPG_ERR_INTERNAL); } server = *hd->uri->host ? hd->uri->host : "localhost"; port = hd->uri->port ? hd->uri->port : 80; if ( (proxy && *proxy) || ( (hd->flags & HTTP_FLAG_TRY_PROXY) && (http_proxy = getenv (HTTP_PROXY_ENV)) && *http_proxy )) { parsed_uri_t uri; if (proxy) http_proxy = proxy; err = http_parse_uri (&uri, http_proxy); if (err) { log_error ("invalid HTTP proxy (%s): %s\n", http_proxy, gpg_strerror (err)); http_release_parsed_uri (uri); return gpg_error (GPG_ERR_CONFIGURATION); } if (uri->auth) { remove_escapes (uri->auth); proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", "\r\n", uri->auth, strlen(uri->auth)); if (!proxy_authstr) { err = gpg_error_from_syserror (); http_release_parsed_uri (uri); return err; } } hd->sock = connect_server (*uri->host ? uri->host : "localhost", uri->port ? uri->port : 80, hd->flags, srvtag); save_errno = errno; http_release_parsed_uri (uri); } else { hd->sock = connect_server (server, port, hd->flags, srvtag); save_errno = errno; } if (hd->sock == -1) { xfree (proxy_authstr); return (save_errno ? gpg_error_from_errno (save_errno) : gpg_error (GPG_ERR_NOT_FOUND)); } #ifdef HTTP_USE_GNUTLS if (hd->uri->use_tls) { int rc; gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock); do { rc = gnutls_handshake (tls_session); } while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN); if (rc < 0) { log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); xfree (proxy_authstr); return gpg_error (GPG_ERR_NETWORK); } if (tls_callback) { err = tls_callback (hd, tls_session, 0); if (err) { log_info ("TLS connection authentication failed: %s\n", gpg_strerror (err)); xfree (proxy_authstr); return err; } } } #endif /*HTTP_USE_GNUTLS*/ if (auth || hd->uri->auth) { char *myauth; if (auth) { myauth = xtrystrdup (auth); if (!myauth) { xfree (proxy_authstr); return gpg_error_from_syserror (); } remove_escapes (myauth); } else { remove_escapes (hd->uri->auth); myauth = hd->uri->auth; } authstr = make_header_line ("Authorization: Basic %s", "\r\n", myauth, strlen (myauth)); if (auth) xfree (myauth); if (!authstr) { xfree (proxy_authstr); return gpg_error_from_syserror (); } } p = build_rel_path (hd->uri); if (!p) return gpg_error_from_syserror (); - request = xtrymalloc (2 * strlen (server) - + strlen (p) - + (authstr?strlen(authstr):0) - + (proxy_authstr?strlen(proxy_authstr):0) - + 100); - if (!request) - { - err = gpg_error_from_syserror (); - xfree (p); - xfree (authstr); - xfree (proxy_authstr); - return err; - } - if (http_proxy && *http_proxy) { - sprintf (request, "%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s", - hd->req_type == HTTP_REQ_GET ? "GET" : - hd->req_type == HTTP_REQ_HEAD ? "HEAD" : - hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - server, port, *p == '/' ? "" : "/", p, - authstr ? authstr : "", - proxy_authstr ? proxy_authstr : ""); + request = xtryasprintf + ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + server, port, *p == '/' ? "" : "/", p, + authstr ? authstr : "", + proxy_authstr ? proxy_authstr : ""); } else { char portstr[35]; if (port == 80) *portstr = 0; else - sprintf (portstr, ":%u", port); + snprintf (portstr, sizeof portstr, ":%u", port); - sprintf (request, "%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", - hd->req_type == HTTP_REQ_GET ? "GET" : - hd->req_type == HTTP_REQ_HEAD ? "HEAD" : - hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - *p == '/' ? "" : "/", p, server, portstr, - authstr? authstr:""); + request = xtryasprintf + ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + *p == '/' ? "" : "/", p, server, portstr, + authstr? authstr:""); } xfree (p); + if (!request) + { + err = gpg_error_from_syserror (); + xfree (authstr); + xfree (proxy_authstr); + return err; + } #ifdef HTTP_USE_ESTREAM /* First setup estream so that we can write even the first line using estream. This is also required for the sake of gnutls. */ { cookie_t cookie; cookie = xtrycalloc (1, sizeof *cookie); if (!cookie) { err = gpg_error_from_syserror (); goto leave; } cookie->fd = hd->sock; if (hd->uri->use_tls) { cookie->tls_session = tls_session; hd->write_cookie = cookie; } hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); if (!hd->fp_write) { xfree (cookie); err = gpg_error_from_syserror (); } else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) err = gpg_error_from_syserror (); else err = 0; if(err==0) for(;headers;headers=headers->next) { if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write))) { err = gpg_error_from_syserror (); break; } } } leave: #else /*!HTTP_USE_ESTREAM*/ /* We send out the start of the request through our own send function and only then assign a stdio stream. This allows for better error reporting that through standard stdio means. */ err = write_server (hd->sock, request, strlen (request)); - - if(err==0) - for(;headers;headers=headers->next) + if (!err) + for (;headers;headers=headers->next) { - err = write_server( hd->sock, headers->d, strlen(headers->d) ); - if(err) + err = write_server (hd->sock, headers->d, strlen(headers->d)); + if (err) break; - err = write_server( hd->sock, "\r\n", 2 ); - if(err) + err = write_server (hd->sock, "\r\n", 2); + if (err) break; } - if (!err) { hd->fp_write = fdopen (hd->sock, "w"); if (!hd->fp_write) err = gpg_error_from_syserror (); } #endif /*!HTTP_USE_ESTREAM*/ xfree (request); xfree (authstr); xfree (proxy_authstr); return err; } /* * Build the relative path from the parsed URI. Minimal * implementation. May return NULL in case of memory failure; errno * is then set accordingly. */ static char * build_rel_path (parsed_uri_t uri) { uri_tuple_t r; char *rel_path, *p; int n; /* Count the needed space. */ n = insert_escapes (NULL, uri->path, "%;?&"); /* TODO: build params. */ for (r = uri->query; r; r = r->next) { n++; /* '?'/'&' */ n += insert_escapes (NULL, r->name, "%;?&="); if (!r->no_value) { n++; /* '=' */ n += insert_escapes (NULL, r->value, "%;?&="); } } n++; /* Now allocate and copy. */ p = rel_path = xtrymalloc (n); if (!p) return NULL; n = insert_escapes (p, uri->path, "%;?&"); p += n; /* TODO: add params. */ for (r = uri->query; r; r = r->next) { *p++ = r == uri->query ? '?' : '&'; n = insert_escapes (p, r->name, "%;?&="); p += n; if (!r->no_value) { *p++ = '='; /* TODO: Use valuelen. */ n = insert_escapes (p, r->value, "%;?&="); p += n; } } *p = 0; return rel_path; } /* 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. If MAX_LENGTH is zero on return, the line has been truncated. If the returned buffer is NULL, not enough memory was enable to increase it, the return value will also be 0 and some bytes might have been lost which should be no problem becuase out-of-memory is pretty fatal for most applications. If a line has been truncated, the file pointer is internally moved forward to the end of the line. Note: The returned buffer is allocated with enough extra space to append a CR,LF,Nul */ static size_t my_read_line ( #ifdef HTTP_USE_ESTREAM estream_t fp, #else FILE *fp, #endif 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; char *p; if (!buffer) /* Must allocate a new buffer. */ { length = 256; buffer = xtrymalloc (length); *addr_of_buffer = buffer; if (!buffer) { *length_of_buffer = *max_length = 0; return 0; } *length_of_buffer = length; } length -= 3; /* Reserve 3 bytes (cr,lf,eol). */ p = buffer; while ((c = P_ES(getc) (fp)) != EOF) { if (nbytes == length) /* Increase the buffer. */ { if (length > maxlen) /* Limit reached. */ { /* Skip the rest of the line. */ while (c != '\n' && (c = P_ES(getc) (fp)) != EOF) ; *p++ = '\n'; /* Always append a LF (we reserved some space). */ nbytes++; *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; gpg_err_set_errno (save_errno); return 0; } buffer = *addr_of_buffer; *length_of_buffer = length; length -= 3; /* And re-adjust for the reservation. */ p = buffer + nbytes; } *p++ = c; nbytes++; if (c == '\n') break; } *p = 0; /* Make sure the line is a string. */ return nbytes; } /* Transform a header name into a standard capitalized format; e.g. "Content-Type". Conversion stops at the colon. As usual we don't use the localized versions of ctype.h. */ static void capitalize_header_name (char *name) { int first = 1; for (; *name && *name != ':'; name++) { if (*name == '-') first = 1; else if (first) { if (*name >= 'a' && *name <= 'z') *name = *name - 'a' + 'A'; first = 0; } else if (*name >= 'A' && *name <= 'Z') *name = *name - 'A' + 'a'; } } /* Store an HTTP header line in LINE away. Line continuation is supported as well as merging of headers with the same name. This function may modify LINE. */ static gpg_error_t store_header (http_t hd, char *line) { size_t n; char *p, *value; header_t h; n = strlen (line); if (n && line[n-1] == '\n') { line[--n] = 0; if (n && line[n-1] == '\r') line[--n] = 0; } if (!n) /* we are never called to hit this. */ return gpg_error (GPG_ERR_BUG); if (*line == ' ' || *line == '\t') { /* Continuation. This won't happen too often as it is not recommended. We use a straightforward implementaion. */ if (!hd->headers) return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); n += strlen (hd->headers->value); p = xtrymalloc (n+1); if (!p) return gpg_error_from_syserror (); strcpy (stpcpy (p, hd->headers->value), line); xfree (hd->headers->value); hd->headers->value = p; return 0; } capitalize_header_name (line); p = strchr (line, ':'); if (!p) return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); *p++ = 0; while (*p == ' ' || *p == '\t') p++; value = p; for (h=hd->headers; h; h = h->next) if ( !strcmp (h->name, line) ) break; if (h) { /* We have already seen a line with that name. Thus we assume it is a comma separated list and merge them. */ p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1); if (!p) return gpg_error_from_syserror (); strcpy (stpcpy (stpcpy (p, h->value), ","), value); xfree (h->value); h->value = p; return 0; } /* Append a new header. */ h = xtrymalloc (sizeof *h + strlen (line)); if (!h) return gpg_error_from_syserror (); strcpy (h->name, line); h->value = xtrymalloc (strlen (value)+1); if (!h->value) { xfree (h); return gpg_error_from_syserror (); } strcpy (h->value, value); h->next = hd->headers; hd->headers = h; return 0; } /* Return the header NAME from the last response. The returned value is valid as along as HD has not been closed and no othe request has been send. If the header was not found, NULL is returned. Name must be canonicalized, that is the first letter of each dash - delimited part must be uppercase and all other letters lowercase. - Note that the context must have been opened with the - HTTP_FLAG_NEED_HEADER. */ + delimited part must be uppercase and all other letters lowercase. */ const char * http_get_header (http_t hd, const char *name) { header_t h; for (h=hd->headers; h; h = h->next) if ( !strcmp (h->name, name) ) return h->value; return NULL; } /* * Parse the response from a server. * Returns: Errorcode and sets some files in the handle */ static gpg_error_t parse_response (http_t hd) { char *line, *p, *p2; size_t maxlen, len; + cookie_t cookie = hd->read_cookie; + const char *s; /* Delete old header lines. */ while (hd->headers) { header_t tmp = hd->headers->next; xfree (hd->headers->value); xfree (hd->headers); hd->headers = tmp; } /* Wait for the status line. */ do { maxlen = MAX_LINELEN; len = my_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); line = hd->buffer; if (!line) return gpg_error_from_syserror (); /* Out of core. */ if (!maxlen) return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */ if (!len) return gpg_error (GPG_ERR_EOF); if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) log_info ("RESP: `%.*s'\n", (int)strlen(line)-(*line&&line[1]?2:0),line); } while (!*line); if ((p = strchr (line, '/'))) *p++ = 0; if (!p || strcmp (line, "HTTP")) return 0; /* Assume http 0.9. */ if ((p2 = strpbrk (p, " \t"))) { *p2++ = 0; p2 += strspn (p2, " \t"); } if (!p2) return 0; /* Also assume http 0.9. */ p = p2; /* TODO: Add HTTP version number check. */ if ((p2 = strpbrk (p, " \t"))) *p2++ = 0; if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1]) || !isdigit ((unsigned int)p[2]) || p[3]) { /* Malformed HTTP status code - assume http 0.9. */ hd->is_http_0_9 = 1; hd->status_code = 200; return 0; } hd->status_code = atoi (p); /* Skip all the header lines and wait for the empty line. */ do { maxlen = MAX_LINELEN; len = my_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); line = hd->buffer; if (!line) return gpg_error_from_syserror (); /* Out of core. */ /* Note, that we can silently ignore truncated lines. */ if (!len) return gpg_error (GPG_ERR_EOF); /* Trim line endings of empty lines. */ if ((*line == '\r' && line[1] == '\n') || *line == '\n') *line = 0; if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) log_info ("RESP: `%.*s'\n", (int)strlen(line)-(*line&&line[1]?2:0),line); - if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line ) + if (*line) { gpg_error_t err = store_header (hd, line); if (err) return err; } } while (len && *line); + cookie->content_length_valid = 0; + if (!(hd->flags & HTTP_FLAG_IGNORE_CL)) + { + s = http_get_header (hd, "Content-Length"); + if (s) + { + cookie->content_length_valid = 1; + cookie->content_length = counter_strtoul (s); + } + } + return 0; } #if 0 static int start_server () { struct sockaddr_in mya; struct sockaddr_in peer; int fd, client; fd_set rfds; int addrlen; int i; if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1) { log_error ("socket() failed: %s\n", strerror (errno)); return -1; } i = 1; if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i))) log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); mya.sin_family = AF_INET; memset (&mya.sin_addr, 0, sizeof (mya.sin_addr)); mya.sin_port = htons (11371); if (bind (fd, (struct sockaddr *) &mya, sizeof (mya))) { log_error ("bind to port 11371 failed: %s\n", strerror (errno)); sock_close (fd); return -1; } if (listen (fd, 5)) { log_error ("listen failed: %s\n", strerror (errno)); sock_close (fd); return -1; } for (;;) { FD_ZERO (&rfds); FD_SET (fd, &rfds); if (select (fd + 1, &rfds, NULL, NULL, NULL) <= 0) continue; /* ignore any errors */ if (!FD_ISSET (fd, &rfds)) continue; addrlen = sizeof peer; client = accept (fd, (struct sockaddr *) &peer, &addrlen); if (client == -1) continue; /* oops */ log_info ("connect from %s\n", inet_ntoa (peer.sin_addr)); fflush (stdout); fflush (stderr); if (!fork ()) { int c; FILE *fp; fp = fdopen (client, "r"); while ((c = getc (fp)) != EOF) putchar (c); fclose (fp); exit (0); } sock_close (client); } return 0; } #endif /* Actually connect to a server. Returns the file descriptor or -1 on error. ERRNO is set on error. */ static int connect_server (const char *server, unsigned short port, unsigned int flags, const char *srvtag) { int sock = -1; int srvcount = 0; int hostfound = 0; int srv, connected; int last_errno = 0; struct srventry *serverlist = NULL; #ifdef HAVE_W32_SYSTEM unsigned long inaddr; #endif /* Not currently using the flags */ (void)flags; #ifdef HAVE_W32_SYSTEM #ifndef HTTP_NO_WSASTARTUP init_sockets (); #endif /* Win32 gethostbyname doesn't handle IP addresses internally, so we try inet_addr first on that platform only. */ inaddr = inet_addr(server); if ( inaddr != INADDR_NONE ) { struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); sock = socket(AF_INET,SOCK_STREAM,0); if ( sock==INVALID_SOCKET ) { log_error("error creating socket: ec=%d\n",(int)WSAGetLastError()); return -1; } addr.sin_family = AF_INET; addr.sin_port = htons(port); memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr)); if (!connect (sock,(struct sockaddr *)&addr,sizeof(addr)) ) return sock; sock_close(sock); return -1; } #endif /*HAVE_W32_SYSTEM*/ #ifdef USE_DNS_SRV /* Do the SRV thing */ if (srvtag) { /* We're using SRV, so append the tags. */ if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME) { char srvname[MAXDNAME]; stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag), "._tcp."), server); srvcount = getsrv (srvname, &serverlist); } } #else + (void)flags; (void)srvtag; #endif /*USE_DNS_SRV*/ if (!serverlist) { /* Either we're not using SRV, or the SRV lookup failed. Make up a fake SRV record. */ serverlist = xtrycalloc (1, sizeof *serverlist); if (!serverlist) return -1; /* Out of core. */ serverlist->port = port; strncpy (serverlist->target, server, MAXDNAME); serverlist->target[MAXDNAME-1] = '\0'; srvcount = 1; } #ifdef HAVE_GETADDRINFO connected = 0; for (srv=0; srv < srvcount && !connected; srv++) { struct addrinfo hints, *res, *ai; char portstr[35]; sprintf (portstr, "%hu", port); memset (&hints, 0, sizeof (hints)); hints.ai_socktype = SOCK_STREAM; if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res)) continue; /* Not found - try next one. */ hostfound = 1; for (ai = res; ai && !connected; ai = ai->ai_next) { if (sock != -1) sock_close (sock); sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) { int save_errno = errno; log_error ("error creating socket: %s\n", strerror (errno)); freeaddrinfo (res); xfree (serverlist); errno = save_errno; return -1; } if (connect (sock, ai->ai_addr, ai->ai_addrlen)) last_errno = errno; else connected = 1; } freeaddrinfo (res); } #else /* !HAVE_GETADDRINFO */ connected = 0; for (srv=0; srv < srvcount && !connected; srv++) { int i; struct hostent *host = NULL; struct sockaddr_in addr; /* Note: This code is not thread-safe. */ memset (&addr, 0, sizeof (addr)); host = gethostbyname (serverlist[srv].target); if (!host) continue; hostfound = 1; if (sock != -1) sock_close (sock); sock = socket (host->h_addrtype, SOCK_STREAM, 0); if (sock == -1) { log_error (_("error creating socket: %s\n"), strerror (errno)); xfree (serverlist); return -1; } addr.sin_family = host->h_addrtype; if (addr.sin_family != AF_INET) { log_error ("unknown address family for `%s'\n", serverlist[srv].target); xfree (serverlist); return -1; } addr.sin_port = htons (serverlist[srv].port); if (host->h_length != 4) { log_error ("illegal address length for `%s'\n", serverlist[srv].target); xfree (serverlist); return -1; } /* Try all A records until one responds. */ for (i = 0; host->h_addr_list[i] && !connected; i++) { memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length); if (connect (sock, (struct sockaddr *) &addr, sizeof (addr))) last_errno = errno; else { connected = 1; break; } } } #endif /* !HAVE_GETADDRINFO */ xfree (serverlist); if (!connected) { #ifdef HAVE_W32_SYSTEM log_error ("can't connect to `%s': %s%sec=%d\n", server, hostfound? "":_("host not found"), hostfound? "":" - ", (int)WSAGetLastError()); #else log_error ("can't connect to `%s': %s\n", server, hostfound? strerror (last_errno):"host not found"); #endif if (sock != -1) sock_close (sock); gpg_err_set_errno (last_errno); return -1; } return sock; } static gpg_error_t write_server (int sock, const char *data, size_t length) { int nleft; + /* FIXME: We would better use pth I/O functions. */ nleft = length; while (nleft > 0) { #ifdef HAVE_W32_SYSTEM int nwritten; nwritten = send (sock, data, nleft, 0); if ( nwritten == SOCKET_ERROR ) { log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ()); return gpg_error (GPG_ERR_NETWORK); } #else /*!HAVE_W32_SYSTEM*/ int nwritten = write (sock, data, nleft); if (nwritten == -1) { if (errno == EINTR) continue; if (errno == EAGAIN) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 50000; select (0, NULL, NULL, NULL, &tv); continue; } log_info ("network write failed: %s\n", strerror (errno)); return gpg_error_from_syserror (); } #endif /*!HAVE_W32_SYSTEM*/ nleft -= nwritten; data += nwritten; } return 0; } #ifdef HTTP_USE_ESTREAM /* Read handler for estream. */ static ssize_t cookie_read (void *cookie, void *buffer, size_t size) { cookie_t c = cookie; int nread; + if (c->content_length_valid) + { + if (!c->content_length) + return 0; /* EOF */ + if (c->content_length < size) + size = c->content_length; + } + #ifdef HTTP_USE_GNUTLS if (c->tls_session) { again: nread = gnutls_record_recv (c->tls_session, buffer, size); if (nread < 0) { if (nread == GNUTLS_E_INTERRUPTED) goto again; if (nread == GNUTLS_E_AGAIN) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 50000; select (0, NULL, NULL, NULL, &tv); goto again; } if (nread == GNUTLS_E_REHANDSHAKE) goto again; /* A client is allowed to just ignore this request. */ log_info ("TLS network read failed: %s\n", gnutls_strerror (nread)); gpg_err_set_errno (EIO); return -1; } } else #endif /*HTTP_USE_GNUTLS*/ { do { -#ifdef HAVE_W32_SYSTEM +#ifdef HAVE_PTH + nread = pth_read (c->fd, buffer, size); +#elif defined(HAVE_W32_SYSTEM) /* Under Windows we need to use recv for a socket. */ nread = recv (c->fd, buffer, size, 0); #else nread = read (c->fd, buffer, size); #endif } while (nread == -1 && errno == EINTR); } + if (c->content_length_valid && nread > 0) + { + if (nread < c->content_length) + c->content_length -= nread; + else + c->content_length = 0; + } + return nread; } /* Write handler for estream. */ static ssize_t cookie_write (void *cookie, const void *buffer, size_t size) { cookie_t c = cookie; int nwritten = 0; #ifdef HTTP_USE_GNUTLS if (c->tls_session) { int nleft = size; while (nleft > 0) { nwritten = gnutls_record_send (c->tls_session, buffer, nleft); if (nwritten <= 0) { if (nwritten == GNUTLS_E_INTERRUPTED) continue; if (nwritten == GNUTLS_E_AGAIN) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 50000; select (0, NULL, NULL, NULL, &tv); continue; } log_info ("TLS network write failed: %s\n", gnutls_strerror (nwritten)); gpg_err_set_errno (EIO); return -1; } nleft -= nwritten; buffer += nwritten; } } else #endif /*HTTP_USE_GNUTLS*/ { if ( write_server (c->fd, buffer, size) ) { gpg_err_set_errno (EIO); nwritten = -1; } else nwritten = size; } return nwritten; } /* Close handler for estream. */ static int cookie_close (void *cookie) { cookie_t c = cookie; if (!c) return 0; #ifdef HTTP_USE_GNUTLS if (c->tls_session && !c->keep_socket) { gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR); } #endif /*HTTP_USE_GNUTLS*/ if (c->fd != -1 && !c->keep_socket) sock_close (c->fd); xfree (c); return 0; } #endif /*HTTP_USE_ESTREAM*/ /**** Test code ****/ #ifdef TEST static gpg_error_t verify_callback (http_t hd, void *tls_context, int reserved) { log_info ("verification of certificates skipped\n"); return 0; } /* static void */ /* my_gnutls_log (int level, const char *text) */ /* { */ /* fprintf (stderr, "gnutls:L%d: %s", level, text); */ /* } */ int main (int argc, char **argv) { int rc; parsed_uri_t uri; uri_tuple_t r; http_t hd; int c; gnutls_session_t tls_session = NULL; #ifdef HTTP_USE_GNUTLS gnutls_certificate_credentials certcred; const int certprio[] = { GNUTLS_CRT_X509, 0 }; #endif /*HTTP_USE_GNUTLS*/ header_t hdr; #ifdef HTTP_USE_ESTREAM es_init (); #endif log_set_prefix ("http-test", 1 | 4); if (argc == 1) { /*start_server (); */ return 0; } if (argc != 2) { fprintf (stderr, "usage: http-test uri\n"); return 1; } argc--; argv++; #ifdef HTTP_USE_GNUTLS rc = gnutls_global_init (); if (rc) log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); rc = gnutls_certificate_allocate_credentials (&certcred); if (rc) log_error ("gnutls_certificate_allocate_credentials failed: %s\n", gnutls_strerror (rc)); /* rc = gnutls_certificate_set_x509_trust_file */ /* (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */ /* if (rc) */ /* log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */ /* gnutls_strerror (rc)); */ rc = gnutls_init (&tls_session, GNUTLS_CLIENT); if (rc) log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc)); rc = gnutls_set_default_priority (tls_session); if (rc) log_error ("gnutls_set_default_priority failed: %s\n", gnutls_strerror (rc)); rc = gnutls_certificate_type_set_priority (tls_session, certprio); if (rc) log_error ("gnutls_certificate_type_set_priority failed: %s\n", gnutls_strerror (rc)); rc = gnutls_credentials_set (tls_session, GNUTLS_CRD_CERTIFICATE, certcred); if (rc) log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc)); /* gnutls_global_set_log_function (my_gnutls_log); */ /* gnutls_global_set_log_level (4); */ http_register_tls_callback (verify_callback); #endif /*HTTP_USE_GNUTLS*/ rc = http_parse_uri (&uri, *argv); if (rc) { log_error ("`%s': %s\n", *argv, gpg_strerror (rc)); http_release_parsed_uri (uri); return 1; } printf ("Scheme: %s\n", uri->scheme); printf ("Host : %s\n", uri->host); printf ("Port : %u\n", uri->port); printf ("Path : %s\n", uri->path); for (r = uri->params; r; r = r->next) { printf ("Params: %s", r->name); if (!r->no_value) { printf ("=%s", r->value); if (strlen (r->value) != r->valuelen) printf (" [real length=%d]", (int) r->valuelen); } putchar ('\n'); } for (r = uri->query; r; r = r->next) { printf ("Query : %s", r->name); if (!r->no_value) { printf ("=%s", r->value); if (strlen (r->value) != r->valuelen) printf (" [real length=%d]", (int) r->valuelen); } putchar ('\n'); } http_release_parsed_uri (uri); uri = NULL; - rc = http_open_document (&hd, *argv, NULL, - HTTP_FLAG_NO_SHUTDOWN | HTTP_FLAG_NEED_HEADER, - NULL, tls_session); + rc = http_open_document (&hd, *argv, NULL, 0, NULL, tls_session); if (rc) { log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc)); return 1; } log_info ("open_http_document succeeded; status=%u\n", http_get_status_code (hd)); for (hdr = hd->headers; hdr; hdr = hdr->next) printf ("HDR: %s: %s\n", hdr->name, hdr->value); switch (http_get_status_code (hd)) { case 200: while ((c = P_ES(getc) (http_get_read_ptr (hd))) != EOF) putchar (c); break; case 301: case 302: printf ("Redirected to `%s'\n", http_get_header (hd, "Location")); break; } http_close (hd, 0); #ifdef HTTP_USE_GNUTLS gnutls_deinit (tls_session); gnutls_certificate_free_credentials (certcred); gnutls_global_deinit (); #endif /*HTTP_USE_GNUTLS*/ return 0; } #endif /*TEST*/ /* Local Variables: compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls" End: */ diff --git a/common/http.h b/common/http.h index e7120f561..b00d87a3f 100644 --- a/common/http.h +++ b/common/http.h @@ -1,116 +1,116 @@ /* http.h - HTTP protocol handler - * Copyright (C) 1999, 2000, 2001, 2003, - * 2006 Free Software Foundation, Inc. + * Copyright (C) 1999, 2000, 2001, 2003, 2006, + * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef GNUPG_COMMON_HTTP_H #define GNUPG_COMMON_HTTP_H #include #ifdef HTTP_USE_ESTREAM #include "../common/estream.h" #endif struct uri_tuple_s { struct uri_tuple_s *next; const char *name; /* A pointer into name. */ char *value; /* A pointer to value (a Nul is always appended). */ size_t valuelen; /* The real length of the value; we need it because the value may contain embedded Nuls. */ int no_value; /* True if no value has been given in the URL. */ }; typedef struct uri_tuple_s *uri_tuple_t; struct parsed_uri_s { /* All these pointers point into BUFFER; most stuff is not escaped. */ char *scheme; /* Pointer to the scheme string (lowercase). */ int use_tls; /* Whether TLS should be used. */ char *auth; /* username/password for basic auth */ char *host; /* Host (converted to lowercase). */ unsigned short port; /* Port (always set if the host is set). */ char *path; /* Path. */ uri_tuple_t params; /* ";xxxxx" */ uri_tuple_t query; /* "?xxx=yyy" */ char buffer[1]; /* Buffer which holds a (modified) copy of the URI. */ }; typedef struct parsed_uri_s *parsed_uri_t; typedef enum { HTTP_REQ_GET = 1, HTTP_REQ_HEAD = 2, HTTP_REQ_POST = 3 } http_req_t; /* We put the flag values into an enum, so that gdb can display them. */ enum { HTTP_FLAG_TRY_PROXY = 1, - HTTP_FLAG_NO_SHUTDOWN = 2, - HTTP_FLAG_LOG_RESP = 4, - HTTP_FLAG_NEED_HEADER = 8 + HTTP_FLAG_SHUTDOWN = 2, + HTTP_FLAG_LOG_RESP = 8, + HTTP_FLAG_IGNORE_CL = 32 }; struct http_context_s; typedef struct http_context_s *http_t; void http_register_tls_callback (gpg_error_t (*cb) (http_t, void *, int)); gpg_error_t http_parse_uri (parsed_uri_t *ret_uri, const char *uri); void http_release_parsed_uri (parsed_uri_t uri); gpg_error_t http_open (http_t *r_hd, http_req_t reqtype, const char *url, const char *auth, unsigned int flags, const char *proxy, void *tls_context, const char *srvtag, strlist_t headers); void http_start_data (http_t hd); gpg_error_t http_wait_response (http_t hd); void http_close (http_t hd, int keep_read_stream); gpg_error_t http_open_document (http_t *r_hd, const char *document, const char *auth, unsigned int flags, const char *proxy, void *tls_context, const char *srvtag, strlist_t headers); #ifdef HTTP_USE_ESTREAM estream_t http_get_read_ptr (http_t hd); estream_t http_get_write_ptr (http_t hd); #else /*!HTTP_USE_ESTREAM*/ FILE *http_get_read_ptr (http_t hd); FILE *http_get_write_ptr (http_t hd); #endif /*!HTTP_USE_ESTREAM*/ unsigned int http_get_status_code (http_t hd); const char *http_get_header (http_t hd, const char *name); char *http_escape_string (const char *string, const char *specials); #endif /*GNUPG_COMMON_HTTP_H*/ diff --git a/common/util.h b/common/util.h index 2735cef3d..fdea333b5 100644 --- a/common/util.h +++ b/common/util.h @@ -1,306 +1,312 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need errno. */ #include /* We need gpg_error_t. */ /* Add error codes available only in newer versions of libgpg-error. */ #ifndef GPG_ERR_MISSING_KEY #define GPG_ERR_MISSING_KEY 181 #endif +#ifndef GPG_ERR_LIMIT_REACHED +#define GPG_ERR_LIMIT_REACHED 183 +#endif /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* Estream replaces most uses of stdio. */ #include "../common/estream.h" #include "../common/estream-printf.h" /* Get all the stuff from jnlib. */ #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/dotlock.h" #include "../common/utf8conv.h" #include "../common/dynload.h" #include "init.h" #include "gettime.h" /* Redefine asprintf by our estream version which uses our own memory allocator.. */ #define asprintf estream_asprintf #define vasprintf estream_vasprintf /* Due to a bug in mingw32's snprintf related to the 'l' modifier and for increased portability we use our snprintf on all systems. */ #define snprintf estream_snprintf /* GCC attributes. */ #if __GNUC__ >= 4 # define GNUPG_GCC_A_SENTINEL(a) __attribute__ ((sentinel(a))) #else # define GNUPG_GCC_A_SENTINEL(a) #endif /* We need this type even if we are not using libreadline and or we did not include libreadline in the current file. */ #ifndef GNUPG_LIBREADLINE_H_INCLUDED typedef char **rl_completion_func_t (const char *, int, int); #endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/ /* 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 xfree_fnc gcry_free #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)) /* Convenience function to return a gpg-error code for memory allocation failures. This function makes sure that an error will be returned even if accidently ERRNO is not set. */ static inline gpg_error_t out_of_core (void) { return gpg_error_from_syserror (); } /*-- signal.c --*/ void gnupg_init_signals (int mode, void (*fast_cleanup)(void)); 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 and b64dec.c --*/ struct b64state { unsigned int flags; int idx; int quad_count; FILE *fp; + estream_t stream; char *title; unsigned char radbuf[4]; u32 crc; int stop_seen:1; int invalid_encoding:1; }; gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); +gpg_error_t b64enc_start_es (struct b64state *state, estream_t 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); gpg_error_t b64dec_start (struct b64state *state, const char *title); gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, size_t *r_nbytes); gpg_error_t b64dec_finish (struct b64state *state); /*-- sexputil.c */ gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen); 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); int hash_algo_from_sigval (const unsigned char *sigval); unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen, const void *e, size_t elen, size_t *r_len); gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen); gpg_error_t get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, int *r_algo); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); int hexcolon2bin (const char *string, void *buffer, size_t length); char *bin2hex (const void *buffer, size_t length, char *stringbuf); char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); const char *hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen); char *hex2str_alloc (const char *hexstring, size_t *r_count); /*-- percent.c --*/ char *percent_plus_escape (const char *string); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); size_t percent_plus_unescape_inplace (char *string, int nulrepl); size_t percent_unescape_inplace (char *string, int nulrepl); /*-- homedir.c --*/ const char *standard_homedir (void); const char *default_homedir (void); const char *gnupg_sysconfdir (void); const char *gnupg_bindir (void); const char *gnupg_libexecdir (void); const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *gnupg_localedir (void); const char *gnupg_cachedir (void); const char *dirmngr_socket_name (void); /* All module names. We also include gpg and gpgsm for the sake for gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 #define GNUPG_MODULE_NAME_GPGSM 7 #define GNUPG_MODULE_NAME_GPG 8 #define GNUPG_MODULE_NAME_CONNECT_AGENT 9 #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 const char *gnupg_module_name (int which); /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); /*-- helpfile.c --*/ char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own logging subsystem. */ void setup_libgcrypt_logging (void); /* Same as estream_asprintf but die on memory failure. */ char *xasprintf (const char *fmt, ...) JNLIB_GCC_A_PRINTF(1,2); /* This is now an alias to estream_asprintf. */ 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_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim); void print_utf8_buffer (estream_t fp, const void *p, size_t n); void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); 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) { (void)fd; return NULL; } #endif /* !HAVE_TTYNAME */ #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () char *_gnupg_getenv (const char *name); /* See sysutils.c */ #define getenv(a) _gnupg_getenv ((a)) char *_gnupg_setenv (const char *name); /* See sysutils.c */ #define setenv(a,b,c) _gnupg_setenv ((a),(b),(c)) int _gnupg_isatty (int fd); #define gnupg_isatty(a) _gnupg_isatty ((a)) #else #define gnupg_isatty(a) isatty ((a)) #endif /*-- 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)) /*-- Forward declaration of the commonly used server control structure. */ /* (We need it here as it is used by some callback prototypes.) */ struct server_control_s; typedef struct server_control_s *ctrl_t; #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog index c6db553de..c95a2f2d6 100644 --- a/dirmngr/ChangeLog +++ b/dirmngr/ChangeLog @@ -1,1356 +1,1379 @@ +2010-07-16 Werner Koch + + * http.c, http.h: Remove. + + * dirmngr-err.h: New. + * dirmngr.h: Include dirmngr-err.h instead of gpg-error.h + + * cdblib.c: Replace assignments to ERRNO by a call to + gpg_err_set_errno. Include dirmngr-err.h. + (cdb_free) [__MINGW32CE__]: Do not use get_osfhandle. + + * dirmngr.c [!HAVE_SIGNAL_H]: Don't include signal.h. + (USE_W32_SERVICE): New. Use this to control the use of the W32 + service system. + +2010-07-06 Werner Koch + + * dirmngr.c (main): Print note on directory name changes. + + Replace almost all uses of stdio by estream. + + * b64dec.c, b64enc.c: Remove. They are duplicated in ../common/. + 2010-06-28 Werner Koch * dirmngr_ldap.c (my_i18n_init): Remove. (main): Call i18n_init instead of above function. * dirmngr-client.c (my_i18n_init): Remove. (main): Call i18n_init instead of above function. * Makefile.am (dirmngr_LDADD): Add ../gl/libgnu. (dirmngr_ldap_LDADD, dirmngr_client_LDADD): Ditto. 2010-06-09 Werner Koch * i18n.h: Remove. * Makefile.am (no-libgcrypt.c): New rule. * exechelp.h: Remove. * exechelp.c: Remove. (dirmngr_release_process): Change callers to use the gnupg func. (dirmngr_wait_process): Likewise. (dirmngr_kill_process): Likewise. This actually implements it for W32. * ldap.c (ldap_wrapper): s/get_dirmngr_ldap_path/gnupg_module_name/. (ldap_wrapper_thread): Use gnupg_wait_process and adjust for changed semantics. (ldap_wrapper): Replace xcalloc by xtrycalloc. Replace spawn mechanism. * server.c (start_command_handler): Remove assuan_set_log_stream. * validate.c: Remove gcrypt.h and ksba.h. * ldapserver.c: s/util.h/dirmngr.h/. * dirmngr.c (sleep) [W32]: Remove macro. (main): s/sleep/gnupg_sleep/. (pid_suffix_callback): Change arg type. (my_gcry_logger): Remove. (fixed_gcry_pth_init): New. (main): Use it. (FD2INT): Remove. 2010-06-08 Werner Koch * misc.h (copy_time): Remove and replace by gnupg_copy_time which allows to set a null date. * misc.c (dump_isotime, get_time, get_isotime, set_time) (check_isotime, add_isotime): Remove and replace all calls by the versions from common/gettime.c. * crlcache.c, misc.c, misc.h: s/dirmngr_isotime_t/gnupg_isotime_t/. * server.c, ldap.c: Reorder include directives. * crlcache.h, misc.h: Remove all include directives. * certcache.c (cmp_simple_canon_sexp): Remove. (compare_serialno): Rewrite using cmp_simple_canon_sexp from common/sexputil.c * error.h: Remove. * dirmngr.c: Remove transitional option "--ignore-ocsp-servic-url". (opts): Use ARGPARSE macros. (i18n_init): Remove. (main): Use GnuPG init functions. * dirmngr.h: Remove duplicated stuff now taken from ../common. * get-path.c, util.h: Remove. * Makefile.am: Adjust to GnuPG system. * estream.c, estream.h, estream-printf.c, estream-printf.h: Remove. 2010-06-07 Werner Koch * OAUTHORS, ONEWS, ChangeLog.1: New. * ChangeLog, Makefile.am, b64dec.c, b64enc.c, cdb.h, cdblib.c * certcache.c, certcache.h, crlcache.c, crlcache.h, crlfetch.c * crlfetch.h, dirmngr-client.c, dirmngr.c, dirmngr.h * dirmngr_ldap.c, error.h, estream-printf.c, estream-printf.h * estream.c, estream.h, exechelp.c, exechelp.h, get-path.c, http.c * http.h, i18n.h, ldap-url.c, ldap-url.h, ldap.c, ldapserver.c * ldapserver.h, misc.c, misc.h, ocsp.c, ocsp.h, server.c, util.h * validate.c, validate.h: Imported from the current SVN of the dirmngr package (only src/). 2010-03-13 Werner Koch * dirmngr.c (int_and_ptr_u): New. (pid_suffix_callback): Trick out compiler. (start_connection_thread): Ditto. (handle_connections): Ditto. 2010-03-09 Werner Koch * dirmngr.c (set_debug): Allow numerical values. 2009-12-15 Werner Koch * dirmngr.c: Add option --ignore-cert-extension. (parse_rereadable_options): Implement. * dirmngr.h (opt): Add IGNORED_CERT_EXTENSIONS. * validate.c (unknown_criticals): Handle ignored extensions. 2009-12-08 Marcus Brinkmann * dirmngr-client.c (start_dirmngr): Convert posix FDs to assuan fds. 2009-11-25 Marcus Brinkmann * server.c (start_command_handler): Use assuan_fd_t and assuan_fdopen on fds. 2009-11-05 Marcus Brinkmann * server.c (start_command_handler): Update use of assuan_init_socket_server. * dirmngr-client.c (start_dirmngr): Update use of assuan_pipe_connect and assuan_socket_connect. 2009-11-04 Werner Koch * server.c (register_commands): Add help arg to assuan_register_command. Change all command comments to strings. 2009-11-02 Marcus Brinkmann * server.c (reset_notify): Take LINE argument, return gpg_error_t. 2009-10-16 Marcus Brinkmann * Makefile.am: (dirmngr_LDADD): Link to $(LIBASSUAN_LIBS) instead of $(LIBASSUAN_PTH_LIBS). * dirmngr.c: Invoke ASSUAN_SYSTEM_PTH_IMPL. (main): Call assuan_set_system_hooks and assuan_sock_init. 2009-09-22 Marcus Brinkmann * dirmngr.c (main): Update to new Assuan interface. * server.c (option_handler, cmd_ldapserver, cmd_isvalid) (cmd_checkcrl, cmd_checkocsp, cmd_lookup, cmd_loadcrl) (cmd_listcrls, cmd_cachecert, cmd_validate): Return gpg_error_t instead int. (register_commands): Likewise for member HANDLER. (start_command_handler): Allocate context with assuan_new before starting server. Release on error. * dirmngr-client.c (main): Update to new Assuan interface. (start_dirmngr): Allocate context with assuan_new before connecting to server. Release on error. 2009-08-12 Werner Koch * dirmngr-client.c (squid_loop_body): Flush stdout. Suggested by Philip Shin. 2009-08-07 Werner Koch * crlfetch.c (my_es_read): Add explicit check for EOF. * http.c (struct http_context_s): Turn IN_DATA and IS_HTTP_0_9 to bit fields. (struct cookie_s): Add CONTENT_LENGTH_VALID and CONTENT_LENGTH. (parse_response): Parse the Content-Length header. (cookie_read): Handle content length. (http_open): Make NEED_HEADER the semi-default. * http.h (HTTP_FLAG_IGNORE_CL): New. 2009-08-04 Werner Koch * ldap.c (ldap_wrapper_thread): Factor some code out to ... (read_log_data): ... new. Close the log fd on error. (ldap_wrapper_thread): Delay cleanup until the log fd is closed. (SAFE_PTH_CLOSE): New. Use it instead of pth_close. 2009-07-31 Werner Koch * server.c (cmd_loadcrl): Add option --url. * dirmngr-client.c (do_loadcrl): Make use of --url. * crlfetch.c (crl_fetch): Remove HTTP_FLAG_NO_SHUTDOWN. Add flag HTTP_FLAG_LOG_RESP with active DBG_LOOKUP. * http.c: Require estream. Remove P_ES macro. (write_server): Remove. (my_read_line): Remove. Replace all callers by es_read_line. (send_request): Use es_asprintf. Always store the cookie. (http_wait_response): Remove the need to dup the socket. USe new shutdown flag. * http.h (HTTP_FLAG_NO_SHUTDOWN): Rename to HTTP_FLAG_SHUTDOWN. * estream.c, estream.h, estream-printf.c, estream-printf.h: Update from current libestream. This is provide es_asprintf. 2009-07-20 Werner Koch * dirmngr.c (pid_suffix_callback): New. (main): Use log_set_pid_suffix_cb. (start_connection_thread): Put the fd into the tls. * ldap.c (ldap_wrapper_thread): Print ldap worker stati. (ldap_wrapper_release_context): Print a debug info. (end_cert_fetch_ldap): Release the reader. Might fix bug#999. 2009-06-17 Werner Koch * util.h: Remove unused dotlock.h. 2009-05-26 Werner Koch * ldap.c (ldap_wrapper): Show reader object in diagnostics. * crlcache.c (crl_cache_reload_crl): Ditto. Change debug messages to regular diagnostics. * dirmngr_ldap.c (print_ldap_entries): Add extra diagnostics. 2009-04-03 Werner Koch * dirmngr.h (struct server_local_s): Move back to ... * server.c (struct server_local_s): ... here. (get_ldapservers_from_ctrl): New. * ldapserver.h (ldapserver_iter_begin): Use it. 2008-10-29 Marcus Brinkmann * estream.c (es_getline): Add explicit cast to silence gcc -W warning. * crlcache.c (finish_sig_check): Likewise. * dirmngr.c (opts): Add missing initializer to silence gcc -W warning. * server.c (register_commands): Likewise. * dirmngr-client.c (opts): Likewise. * dirmngr_ldap.c (opts): Likewise. * dirmngr-client.c (status_cb, inq_cert, data_cb): Change return type to gpg_error_t to silence gcc warning. 2008-10-21 Werner Koch * certcache.c (load_certs_from_dir): Accept ".der" files. * server.c (get_istrusted_from_client): New. * validate.c (validate_cert_chain): Add new optional arg R_TRUST_ANCHOR. Adjust all callers * crlcache.c (crl_cache_entry_s): Add fields USER_TRUST_REQ and CHECK_TRUST_ANCHOR. (release_one_cache_entry): Release CHECK_TRUST_ANCHOR. (list_one_crl_entry): Print info about the new fields. (open_dir, write_dir_line_crl): Support the new U-flag. (crl_parse_insert): Add arg R_TRUST_ANCHOR and set it accordingly. (crl_cache_insert): Store trust anchor in entry object. (cache_isvalid): Ask client for trust is needed. * crlcache.c (open_dir): Replace xcalloc by xtrycalloc. (next_line_from_file): Ditt. Add arg to return the gpg error. Change all callers. (update_dir): Replace sprintf and malloc by estream_asprintf. (crl_cache_insert): Ditto. (crl_cache_isvalid): Replace xmalloc by xtrymalloc. (get_auth_key_id): Ditto. (crl_cache_insert): Ditto. * crlcache.c (start_sig_check): Remove HAVE_GCRY_MD_DEBUG test. * validate.c (check_cert_sig): Ditto. Remove workaround for bug in libgcrypt 1.2. * estream.c, estream.h, estream-printf.c, estream-printf.h: Update from current libestream (svn rev 61). 2008-09-30 Marcus Brinkmann * get-path.c (get_dirmngr_ldap_path): Revert last change. Instead, use dirmngr_libexecdir(). (find_program_at_standard_place): Don't define for now. 2008-09-30 Marcus Brinkmann * get-path.c (dirmngr_cachedir): Make COMP a pointer to const to silence gcc warning. (get_dirmngr_ldap_path): Look for dirmngr_ldap in the installation directory. 2008-08-06 Marcus Brinkmann * dirmngr.c (main): Mark the ldapserverlist-file option as read-only. 2008-07-31 Werner Koch * crlcache.c (start_sig_check) [!HAVE_GCRY_MD_DEBUG]: Use gcry_md_start_debug 2008-06-16 Werner Koch * get-path.c (w32_commondir): New. (dirmngr_sysconfdir): Use it here. (dirmngr_datadir): Ditto. 2008-06-12 Marcus Brinkmann * Makefile.am (dirmngr_SOURCES): Add ldapserver.h and ldapserver.c. * ldapserver.h, ldapserver.c: New files. * ldap.c: Include "ldapserver.h". (url_fetch_ldap): Use iterator to get session servers as well. (attr_fetch_ldap, start_default_fetch_ldap): Likewise. * dirmngr.c: Include "ldapserver.h". (free_ldapservers_list): Removed. Change callers to ldapserver_list_free. (parse_ldapserver_file): Use ldapserver_parse_one. * server.c: Include "ldapserver.h". (cmd_ldapserver): New command. (register_commands): Add new command LDAPSERVER. (reset_notify): New function. (start_command_handler): Register reset notify handler. Deallocate session server list. (lookup_cert_by_pattern): Use iterator to get session servers as well. (struct server_local_s): Move to ... * dirmngr.h (struct server_local_s): ... here. Add new member ldapservers. 2008-06-10 Werner Koch Support PEM encoded CRLs. Fixes bug#927. * crlfetch.c (struct reader_cb_context_s): New. (struct file_reader_map_s): Replace FP by new context. (register_file_reader, get_file_reader): Adjust accordingly. (my_es_read): Detect Base64 encoded CRL and decode if needed. (crl_fetch): Pass new context to the callback. (crl_close_reader): Cleanup the new context. * b64dec.c: New. Taken from GnuPG. * util.h (struct b64state): Add new fields STOP_SEEN and INVALID_ENCODING. 2008-05-26 Marcus Brinkmann * dirmngr.c (main) [HAVE_W32_SYSTEM]: Switch to system configuration on gpgconf related commands, and make all options unchangeable. 2008-03-25 Marcus Brinkmann * dirmngr_ldap.c (print_ldap_entries): Add code alternative for W32 console stdout (unused at this point). 2008-03-21 Marcus Brinkmann * estream.c (ESTREAM_MUTEX_DESTROY): New macro. (es_create, es_destroy): Use it. 2008-02-21 Werner Koch * validate.c (check_cert_sig) [HAVE_GCRY_MD_DEBUG]: Use new debug function if available. * crlcache.c (abort_sig_check): Mark unused arg. * exechelp.c (dirmngr_release_process) [!W32]: Mark unsed arg. * validate.c (is_root_cert): New. Taken from GnuPG. (validate_cert_chain): Use it in place of the simple DN compare. 2008-02-15 Marcus Brinkmann * dirmngr.c (main): Reinitialize assuan log stream if necessary. * crlcache.c (update_dir) [HAVE_W32_SYSTEM]: Remove destination file before rename. (crl_cache_insert) [HAVE_W32_SYSTEM]: Remove destination file before rename. 2008-02-14 Marcus Brinkmann * validate.c (check_cert_policy): Use ksba_free instead of xfree. (validate_cert_chain): Likewise. Free SUBJECT on error. (cert_usage_p): Likewise. * crlcache.c (finish_sig_check): Undo last change. (finish_sig_check): Close md. (abort_sig_check): New function. (crl_parse_insert): Use abort_sig_check to clean up. * crlcache.c (crl_cache_insert): Clean up CDB on error. 2008-02-13 Marcus Brinkmann * crlcache.c (finish_sig_check): Call gcry_md_stop_debug. * exechelp.h (dirmngr_release_process): New prototype. * exechelp.c (dirmngr_release_process): New function. * ldap.c (ldap_wrapper_thread): Release pid. (destroy_wrapper): Likewise. * dirmngr.c (launch_reaper_thread): Destroy tattr. (handle_connections): Likewise. 2008-02-12 Marcus Brinkmann * ldap.c (pth_close) [! HAVE_W32_SYSTEM]: New macro. (struct wrapper_context_s): New member log_ev. (destroy_wrapper): Check FDs for != -1 rather than != 0. Use pth_close instead of close. Free CTX->log_ev. (ldap_wrapper_thread): Rewritten to use pth_wait instead of select. Also use pth_read instead of read and pth_close instead of close. (ldap_wrapper): Initialize CTX->log_ev. (reader_callback): Use pth_close instead of close. * exechelp.c (create_inheritable_pipe) [HAVE_W32_SYSTEM]: Removed. (dirmngr_spawn_process) [HAVE_W32_SYSTEM]: Use pth_pipe instead. * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Include . (main) [HAVE_W32_SYSTEM]: Set mode of stdout to binary. 2008-02-01 Werner Koch * ldap.c: Remove all ldap headers as they are unused. * dirmngr_ldap.c (LDAP_DEPRECATED): New, to have OpenLDAP use the old standard API. 2008-01-10 Werner Koch * dirmngr-client.c: New option --local. (do_lookup): Use it. * server.c (lookup_cert_by_pattern): Implement local lookup. (return_one_cert): New. * certcache.c (hexsn_to_sexp): New. (classify_pattern, get_certs_bypattern): New. * misc.c (unhexify): Allow passing NULL for RESULT. (cert_log_subject): Do not call ksba_free on an unused variable. 2008-01-02 Marcus Brinkmann * Makefile.am (dirmngr_LDADD, dirmngr_ldap_LDADD) (dirmngr_client_LDADD): Add $(LIBICONV). Reported by Michael Nottebrock. 2007-12-11 Werner Koch * server.c (option_handler): New option audit-events. * dirmngr.h (struct server_control_s): Add member AUDIT_EVENTS. 2007-11-26 Marcus Brinkmann * get-path.c (dirmngr_cachedir): Create intermediate directories. (default_socket_name): Use CSIDL_WINDOWS. 2007-11-21 Werner Koch * server.c (lookup_cert_by_pattern): Add args SINGLE and CACHE_ONLY. (cmd_lookup): Add options --single and --cache-only. 2007-11-16 Werner Koch * certcache.c (load_certs_from_dir): Also log the subject DN. * misc.c (cert_log_subject): New. 2007-11-14 Werner Koch * dirmngr-client.c: Replace --lookup-url by --url. (main): Remove extra code for --lookup-url. (do_lookup): Remove LOOKUP_URL arg and use the global option OPT.URL. * server.c (has_leading_option): New. (cmd_lookup): Use it. * crlfetch.c (fetch_cert_by_url): Use GPG_ERR_INV_CERT_OBJ. (fetch_cert_by_url): Use gpg_error_from_syserror. 2007-11-14 Moritz (wk) * dirmngr-client.c: New command: --lookup-url . (do_lookup): New parameter: lookup_url. If TRUE, include "--url" switch in LOOKUP transaction. (enum): New entry: oLookupUrl. (opts): Likewise. (main): Handle oLookupUrl. New variable: cmd_lookup_url, set during option parsing, pass to do_lookup() and substitute some occurences of "cmd_lookup" with "cmd_lookup OR cmd_lookup_url". * crlfetch.c (fetch_cert_by_url): New function, uses url_fetch_ldap() to create a reader object and libksba functions to read a single cert from that reader. * server.c (lookup_cert_by_url, lookup_cert_by_pattern): New functions. (cmd_lookup): Moved almost complete code ... (lookup_cert_by_pattern): ... here. (cmd_lookup): Support new optional argument: --url. Depending on the presence of that switch, call lookup_cert_by_url() or lookup_cert_by_pattern(). (lookup_cert_by_url): Heavily stripped down version of lookup_cert_by_pattern(), using fetch_cert_by_url. 2007-10-24 Marcus Brinkmann * exechelp.c (dirmngr_spawn_process): Fix child handles. 2007-10-05 Marcus Brinkmann * dirmngr.h: Include assuan.h. (start_command_handler): Change type of FD to assuan_fd_t. * dirmngr.c: Do not include w32-afunix.h. (socket_nonce): New global variable. (create_server_socket): Use assuan socket wrappers. Remove W32 specific stuff. Save the server nonce. (check_nonce): New function. (start_connection_thread): Call it. (handle_connections): Change args to assuan_fd_t. * server.c (start_command_handler): Change type of FD to assuan_fd_t. 2007-09-12 Marcus Brinkmann * dirmngr.c (main): Percent escape pathnames in --gpgconf-list output. 2007-08-27 Moritz Schulte * src/Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SOCKETDIR based on $(localstatedir). * src/get-path.c (default_socket_name): Use DIRMNGR_SOCKETDIR instead of hard-coded "/var/run/dirmngr". 2007-08-16 Werner Koch * get-path.c (get_dirmngr_ldap_path): Make PATHNAME const. * dirmngr.c (my_ksba_hash_buffer): Mark unused arg. (dirmngr_init_default_ctrl): Ditto. (my_gcry_logger): Ditto. * dirmngr-client.c (status_cb): Ditto. * dirmngr_ldap.c (catch_alarm): Ditto. * estream-printf.c (pr_bytes_so_far): Ditto. * estream.c (es_func_fd_create): Ditto. (es_func_fp_create): Ditto. (es_write_hexstring): Ditto. * server.c (cmd_listcrls): Ditto. (cmd_cachecert): Ditto. * crlcache.c (cache_isvalid): Ditto. * ocsp.c (do_ocsp_request): Ditto. * ldap.c (ldap_wrapper_thread): Ditto. * http.c (http_register_tls_callback): Ditto. (connect_server): Ditto. (write_server) [!HTTP_USE_ESTREAM]: Don't build. 2007-08-14 Werner Koch * get-path.c (dirmngr_cachedir) [W32]: Use CSIDL_LOCAL_APPDATA. 2007-08-13 Werner Koch * dirmngr.c (handle_connections): Use a timeout in the accept function. Block signals while creating a new thread. (shutdown_pending): Needs to be volatile as also accessed bt the service function. (w32_service_control): Do not use the regular log fucntions here. (handle_tick): New. (main): With system_service in effect use aDaemon as default command. (main) [W32]: Only temporary redefine main for the sake of Emacs's "C-x 4 a". * dirmngr-client.c (main) [W32]: Initialize sockets. (start_dirmngr): Use default_socket_name instead of a constant. * Makefile.am (dirmngr_client_SOURCES): Add get-path.c 2007-08-09 Werner Koch * dirmngr.c (parse_ocsp_signer): New. (parse_rereadable_options): Set opt.ocsp_signer to this. * dirmngr.h (fingerprint_list_t): New. * ocsp.c (ocsp_isvalid, check_signature, validate_responder_cert): Allow for several default ocscp signers. (ocsp_isvalid): Return GPG_ERR_NO_DATA for an unknwon status. * dirmngr-client.c: New option --force-default-responder. * server.c (has_option, skip_options): New. (cmd_checkocsp): Add option --force-default-responder. (cmd_isvalid): Ditto. Also add option --only-ocsp. * ocsp.c (ocsp_isvalid): New arg FORCE_DEFAULT_RESPONDER. * dirmngr.c: New option --ocsp-max-period. * ocsp.c (ocsp_isvalid): Implement it and take care that a missing next_update is to be ignored. * crlfetch.c (my_es_read): New. Use it instead of es_read. * estream.h, estream.c, estream-printf.c: Updated from current libestream SVN. 2007-08-08 Werner Koch * crlcache.c (crl_parse_insert): Hack to allow for a missing nextUpdate. * dirmngr_ldap.c (print_ldap_entries): Strip the extension from the want_attr. * exechelp.c (dirmngr_wait_process): Reworked for clear error semantics. * ldap.c (ldap_wrapper_thread): Adjust for new dirmngr_wait_process semantics. 2007-08-07 Werner Koch * get-path.c (default_socket_name) [!W32]: Fixed syntax error. * ldap.c (X509CACERT, make_url, fetch_next_cert_ldap): Support x509caCert as used by the Bundesnetzagentur. (ldap_wrapper): Do not pass the prgtram name as the first argument. dirmngr_spawn_process takes care of that. 2007-08-04 Marcus Brinkmann * dirmngr.h (opt): Add member system_service. * dirmngr.c (opts) [HAVE_W32_SYSTEM]: New entry for option --service. (DEFAULT_SOCKET_NAME): Removed. (service_handle, service_status, w32_service_control) [HAVE_W32_SYSTEM]: New symbols. (main) [HAVE_W32_SYSTEM]: New entry point for --service. Rename old function to ... (real_main) [HAVE_W32_SYSTEM]: ... this. Use default_socket_name instead of DEFAULT_SOCKET_NAME, and similar for other paths. Allow colons in Windows socket path name, and implement --service option. * util.h (dirmngr_sysconfdir, dirmngr_libexecdir, dirmngr_datadir, dirmngr_cachedir, default_socket_name): New prototypes. * get-path.c (dirmngr_sysconfdir, dirmngr_libexecdir) (dirmngr_datadir, dirmngr_cachedir, default_socket_name): New functions. (DIRSEP_C, DIRSEP_S): New macros. 2007-08-03 Marcus Brinkmann * get-path.c: Really add the file this time. 2007-07-31 Marcus Brinkmann * crlfetch.c: Include "estream.h". (crl_fetch): Use es_read callback instead a file handle. (crl_close_reader): Use es_fclose instead of fclose. (struct file_reader_map_s): Change type of FP to estream_t. (register_file_reader, crl_fetch, crl_close_reader): Likewise. * ocsp.c: Include "estream.h". (read_response): Change type of FP to estream_t. (read_response, do_ocsp_request): Use es_* variants of I/O functions. * http.c: Include . (http_wait_response) [HAVE_W32_SYSTEM]: Use DuplicateHandle. (cookie_read): Use pth_read instead read. (cookie_write): Use pth_write instead write. 2007-07-30 Marcus Brinkmann * ldap-url.c (ldap_str2charray): Fix buglet in ldap_utf8_strchr invocation. 2007-07-27 Marcus Brinkmann * estream.h, estream.c: Update from recent GnuPG. * get-path.c: New file. * Makefile.am (dirmngr_SOURCES): Add get-path.c. * util.h (default_homedir, get_dirmngr_ldap_path): New prototypes. * dirmngr.c (main): Use default_homedir(). * ldap-url.h: Remove japanese white space (sorry!). 2007-07-26 Marcus Brinkmann * ldap.c (pth_yield): Remove macro. * ldap.c (pth_yield) [HAVE_W32_SYSTEM]: Define to Sleep(0). * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Do not include , but , and "ldap-url.h". * ldap.c [HAVE_W32_SYSTEM]: Do not include , but and . * ldap-url.c: Do not include , but , and "ldap-url.h". (LDAP_P): New macro. * ldap-url.h: New file. * Makefile.am (ldap_url): Add ldap-url.h. * Makefile.am (ldap_url): New variable. (dirmngr_ldap_SOURCES): Add $(ldap_url). (dirmngr_ldap_LDADD): Add $(LIBOBJS). * ldap-url.c: New file, excerpted from OpenLDAP. * dirmngr.c (main) [HAVE_W32_SYSTEM]: Avoid the daemonization. * dirmngr_ldap.c: Include "util.h". (main) [HAVE_W32_SYSTEM]: Don't set up alarm. (set_timeout) [HAVE_W32_SYSTEM]: Likewise. * ldap.c [HAVE_W32_SYSTEM]: Add macros for setenv and pth_yield. * no-libgcrypt.h (NO_LIBGCRYPT): Define. * util.h [NO_LIBGCRYPT]: Don't include . 2007-07-23 Marcus Brinkmann * Makefile.am (dirmngr_SOURCES): Add exechelp.h and exechelp.c. * exechelp.h, exechelp.c: New files. * ldap.c: Don't include but "exechelp.h". (destroy_wrapper, ldap_wrapper_thread, ldap_wrapper_connection_cleanup): Use dirmngr_kill_process instead of kill. (ldap_wrapper_thread): Use dirmngr_wait_process instead of waitpid. (ldap_wrapper): Use dirmngr_spawn_process. 2007-07-20 Marcus Brinkmann * certcache.c (cert_cache_lock): Do not initialize statically. (init_cache_lock): New function. (cert_cache_init): Call init_cache_lock. * estream.h, estream.c, estream-printf.h, estream-printf.c: New files. * Makefile.am (dirmngr_SOURCES): Add estream.c, estream.h, estream-printf.c, estream-printf.h. * http.c: Update to latest version from GnuPG. * Makefile.am (cdb_sources) * cdblib.c: Port to windows (backport from tinycdb 0.76). * crlcache.c [HAVE_W32_SYSTEM]: Don't include sys/utsname.h. [MKDIR_TAKES_ONE_ARG]: Define mkdir as a macro for such systems. (update_dir, crl_cache_insert) [HAVE_W32_SYSTEM]: Don't get uname. * server.c (start_command_handler) [HAVE_W32_SYSTEM]: Don't log peer credentials. * dirmngr.c [HAVE_W32_SYSTEM]: Do not include sys/socket.h or sys/un.h, but ../jnlib/w32-afunix.h. (sleep) [HAVE_W32_SYSTEM]: New macro. (main) [HAVE_W32_SYSTEM]: Don't mess with SIGPIPE. Use W32 socket API. (handle_signal) [HAVE_W32_SYSTEM]: Deactivate the bunch of the code. (handle_connections) [HAVE_W32_SYSTEM]: don't handle signals. 2006-11-29 Werner Koch * dirmngr.c (my_strusage): Use macro for the bug report address and the copyright line. * dirmngr-client.c (my_strusage): Ditto. * dirmngr_ldap.c (my_strusage): Ditto. * Makefile.am: Do not link against LIBICONV. 2006-11-19 Werner Koch * dirmngr.c: Include i18n.h. 2006-11-17 Werner Koch * Makefile.am (dirmngr_LDADD): Use LIBASSUAN_PTH_LIBS. 2006-11-16 Werner Koch * server.c (start_command_handler): Replaced assuan_init_connected_socket_server by assuan_init_socket_server_ext. * crlcache.c (update_dir): Put a diagnostic into DIR.txt. (open_dir): Detect invalid and duplicate entries. (update_dir): Fixed search for second field. 2006-10-23 Werner Koch * dirmngr.c (main): New command --gpgconf-test. 2006-09-14 Werner Koch * server.c (start_command_handler): In vebose mode print information about the peer. This may later be used to restrict certain commands. 2006-09-12 Werner Koch * server.c (start_command_handler): Print a more informative hello line. * dirmngr.c: Moved config_filename into the opt struct. 2006-09-11 Werner Koch Changed everything to use Assuan with gpg-error codes. * maperror.c: Removed. * server.c (map_to_assuan_status): Removed. * dirmngr.c (main): Set assuan error source. * dirmngr-client.c (main): Ditto. 2006-09-04 Werner Koch * crlfetch.c (crl_fetch): Implement HTTP redirection. * ocsp.c (do_ocsp_request): Ditto. New HTTP code version taken from gnupg svn release 4236. * http.c (http_get_header): New. (capitalize_header_name, store_header): New. (parse_response): Store headers away. (send_request): Return GPG_ERR_NOT_FOUND if connect_server failed. * http.h: New flag HTTP_FLAG_NEED_HEADER. 2006-09-01 Werner Koch * crlfetch.c (register_file_reader, get_file_reader): New. (crl_fetch): Register the file pointer for HTTP. (crl_close_reader): And release it. * http.c, http.h: Updated from GnuPG SVN trunk. Changed all users to adopt the new API. * dirmngr.h: Moved inclusion of jnlib header to ... * util.h: .. here. This is required becuase http.c includes only a file util.h but makes use of log_foo. Include gcrypt.h so that gcry_malloc et al are declared. 2006-08-31 Werner Koch * ocsp.c (check_signature): Make use of the responder id. 2006-08-30 Werner Koch * validate.c (check_cert_sig): Workaround for rimemd160. (allowed_ca): Always allow trusted CAs. * dirmngr.h (cert_ref_t): New. (struct server_control_s): Add field OCSP_CERTS. * server.c (start_command_handler): Release new field * ocsp.c (release_ctrl_ocsp_certs): New. (check_signature): Store certificates in OCSP_CERTS. * certcache.c (find_issuing_cert): Reset error if cert was found by subject. (put_cert): Add new arg FPR_BUFFER. Changed callers. (cache_cert_silent): New. * dirmngr.c (parse_rereadable_options): New options --ocsp-max-clock-skew and --ocsp-current-period. * ocsp.c (ocsp_isvalid): Use them here. * ocsp.c (validate_responder_cert): New optional arg signer_cert. (check_signature_core): Ditto. (check_signature): Use the default signer certificate here. 2006-06-27 Werner Koch * dirmngr-client.c (inq_cert): Take care of SENDCERT_SKI. 2006-06-26 Werner Koch * crlcache.c (lock_db_file): Count open files when needed. (find_entry): Fixed deleted case. 2006-06-23 Werner Koch * misc.c (cert_log_name): New. * certcache.c (load_certs_from_dir): Also print certificate name. (find_cert_bysn): Release ISSDN. * validate.h: New VALIDATE_MODE_CERT. * server.c (cmd_validate): Use it here so that no policy checks are done. Try to validated a cached copy of the target. * validate.c (validate_cert_chain): Implement a validation cache. (check_revocations): Print more diagnostics. Actually use the loop variable and not the head of the list. (validate_cert_chain): Do not check revocations of CRL issuer certificates in plain CRL check mode. * ocsp.c (ocsp_isvalid): Make sure it is reset for a status of revoked. 2006-06-22 Werner Koch * validate.c (cert_use_crl_p): New. (cert_usage_p): Add a mode 6 for CRL signing. (validate_cert_chain): Check that the certificate may be used for CRL signing. Print a note when not running as system daemon. (validate_cert_chain): Reduce the maximum depth from 50 to 10. * certcache.c (find_cert_bysn): Minor restructuring (find_cert_bysubject): Ditto. Use get_cert_local when called without KEYID. * crlcache.c (get_crlissuer_cert_bysn): Removed. (get_crlissuer_cert): Removed. (crl_parse_insert): Use find_cert_bysubject and find_cert_bysn instead of the removed functions. 2006-06-19 Werner Koch * certcache.c (compare_serialno): Silly me. Using 0 as true is that hard; tsss. Fixed call cases except for the only working one which are both numbers of the same length. 2006-05-15 Werner Koch * crlfetch.c (crl_fetch): Use no-shutdown flag for HTTP. This seems to be required for "IBM_HTTP_Server/2.0.47.1 Apache/2.0.47 (Unix)". * http.c (parse_tuple): Set flag to to indicate no value. (build_rel_path): Take care of it. * crlcache.c (crl_cache_reload_crl): Also iterate over all names within a DP. 2005-09-28 Marcus Brinkmann * Makefile.am (dirmngr_LDADD): Add @LIBINTL@ and @LIBICONV@. (dirmngr_ldap_LDADD): Likewise. (dirmngr_client_LDADD): Likewise. 2005-09-12 Werner Koch * dirmngr.c: Fixed description to match the one in gpgconf. 2005-06-15 Werner Koch * server.c (cmd_lookup): Take care of NO_DATA which might get returned also by start_cert_fetch(). 2005-04-20 Werner Koch * ldap.c (ldap_wrapper_wait_connections): Set a shutdown flag. (ldap_wrapper_thread): Handle shutdown in a special way. 2005-04-19 Werner Koch * server.c (get_cert_local, get_issuing_cert_local) (get_cert_local_ski): Bail out if called without a local context. 2005-04-18 Werner Koch * certcache.c (find_issuing_cert): Fixed last resort method which should be finding by subject and not by issuer. Try to locate it also using the keyIdentifier method. Improve error reporting. (cmp_simple_canon_sexp): New. (find_cert_bysubject): New. (find_cert_bysn): Ask back to the caller before trying an extarnl lookup. * server.c (get_cert_local_ski): New. * crlcache.c (crl_parse_insert): Also try to locate issuer certificate using the keyIdentifier. Improved error reporting. 2005-04-14 Werner Koch * ldap.c (start_cert_fetch_ldap): Really return ERR. 2005-03-17 Werner Koch * http.c (parse_response): Changed MAXLEN and LEN to size_t to match the requirement of read_line. * http.h (http_context_s): Ditto for BUFFER_SIZE. 2005-03-15 Werner Koch * ldap.c: Included time.h. Reported by Bernhard Herzog. 2005-03-09 Werner Koch * dirmngr.c: Add a note to the help listing check the man page for other options. 2005-02-01 Werner Koch * crlcache.c (crl_parse_insert): Renamed a few variables and changed diagnostic strings for clarity. (get_issuer_cert): Renamed to get_crlissuer_cert. Try to locate the certificate from the cache using the subject name. Use new fetch function. (get_crlissuer_cert_bysn): New. (crl_parse_insert): Use it here. * crlfetch.c (ca_cert_fetch): Changed interface. (fetch_next_ksba_cert): New. * ldap.c (run_ldap_wrapper): Add arg MULTI_MODE. Changed all callers. (start_default_fetch_ldap): New * certcache.c (get_cert_bysubject): New. (clean_cache_slot, put_cert): Store the subject DN if available. (MAX_EXTRA_CACHED_CERTS): Increase limit of cachable certificates to 1000. (find_cert_bysn): Loop until a certificate with a matching S/N has been found. * dirmngr.c (main): Add honor-http-proxy to the gpgconf list. 2005-01-31 Werner Koch * ldap.c: Started to work on support for userSMIMECertificates. * dirmngr.c (main): Make sure to always pass a server control structure to the caching functions. Reported by Neil Dunbar. 2005-01-05 Werner Koch * dirmngr-client.c (read_pem_certificate): Skip trailing percent escaped linefeeds. 2005-01-03 Werner Koch * dirmngr-client.c (read_pem_certificate): New. (read_certificate): Divert to it depending on pem option. (squid_loop_body): New. (main): New options --pem and --squid-mode. 2004-12-17 Werner Koch * dirmngr.c (launch_ripper_thread): Renamed to launch_reaper_thread. (shutdown_reaper): New. Use it for --server and --daemon. * ldap.c (ldap_wrapper_wait_connections): New. 2004-12-17 Werner Koch * Makefile.am (dirmngr_ldap_LDADD): Adjusted for new LDAP checks. 2004-12-16 Werner Koch * ldap.c (ldap_wrapper): Peek on the output to detect empty output early. 2004-12-15 Werner Koch * ldap.c (ldap_wrapper): Print a diagnostic after forking for the ldap wrapper. * certcache.h (find_cert_bysn): Add this prototype. * crlcache.c (start_sig_check): Write CRL hash debug file. (finish_sig_check): Dump the signer's certificate. (crl_parse_insert): Try to get the issuing cert by authKeyId. Moved certificate retrieval after item processing. 2004-12-13 Werner Koch * dirmngr_ldap.c (catch_alarm, set_timeout): new. (main): Install alarm handler. Add new option --only-search-timeout. (print_ldap_entries, fetch_ldap): Use set_timeout (); * dirmngr.h: Make LDAPTIMEOUT a simple unsigned int. Change all initializations. * ldap.c (start_cert_fetch_ldap, run_ldap_wrapper): Pass timeout option to the wrapper. (INACTIVITY_TIMEOUT): Depend on LDAPTIMEOUT. (run_ldap_wrapper): Add arg IGNORE_TIMEOUT. (ldap_wrapper_thread): Check for special timeout exit code. * dirmngr.c: Workaround a typo in gpgconf for ignore-ocsp-service-url. 2004-12-10 Werner Koch * ldap.c (url_fetch_ldap): Use TMP and not a HOST which is always NULL. * misc.c (host_and_port_from_url): Fixed bad encoding detection. 2004-12-03 Werner Koch * crlcache.c (crl_cache_load): Re-implement it. * dirmngr-client.c: New command --load-crl (do_loadcrl): New. * dirmngr.c (parse_rereadable_options, main): Make --allow-ocsp, --ocsp-responder, --ocsp-signer and --max-replies re-readable. * ocsp.c (check_signature): try to get the cert from the cache first. (ocsp_isvalid): Print the next and this update times on time conflict. * certcache.c (load_certs_from_dir): Print the fingerprint for trusted certificates. (get_cert_byhexfpr): New. * misc.c (get_fingerprint_hexstring_colon): New. 2004-12-01 Werner Koch * Makefile.am (dirmngr_LDADD): Don't use LDAP_LIBS. * validate.c (validate_cert_chain): Fixed test; as written in the comment we want to do this only in daemon mode. For clarity reworked by using a linked list of certificates and include root and tragte certificate. (check_revocations): Likewise. Introduced a recursion sentinel. 2004-11-30 Werner Koch * crlfetch.c (ca_cert_fetch, crl_fetch_default): Do not use the binary prefix as this will be handled in the driver. * dirmngr_ldap.c: New option --log-with-pid. (fetch_ldap): Handle LDAP_NO_SUCH_OBJECT. * ldap.c (run_ldap_wrapper, start_cert_fetch_ldap): Use new log option. 2004-11-25 Werner Koch * Makefile.am (dirmngr_ldap_CFLAGS): Added GPG_ERROR_CFLAGS. Noted by Bernhard Herzog. 2004-11-24 Werner Koch * ldap.c (ldap_wrapper): Fixed default name of the ldap wrapper. * b64enc.c (b64enc_start, b64enc_finish): Use standard strdup/free to manage memory. * dirmngr.c: New options --ignore-http-dp, --ignore-ldap-dp and --ignore-ocsp-service-url. * crlcache.c (crl_cache_reload_crl): Implement them. * ocsp.c (ocsp_isvalid): Ditto. 2004-11-23 Werner Koch * ldap.c (ldap_wrapper_thread, reader_callback, ldap_wrapper): Keep a timestamp and terminate the wrapper after some time of inactivity. * dirmngr-client.c (do_lookup): New. (main): New option --lookup. (data_cb): New. * b64enc.c: New. Taken from GnuPG 1.9. * no-libgcrypt.c (gcry_strdup): Added. * ocsp.c (ocsp_isvalid): New arg CERT and lookup the issuer certificate using the standard methods. * server.c (cmd_lookup): Truncation is now also an indication for error. (cmd_checkocsp): Implemented. * dirmngr_ldap.c (fetch_ldap): Write an error marker for a truncated search. * ldap.c (add_server_to_servers): Reactivated. (url_fetch_ldap): Call it here and try all configured servers in case of a a failed lookup. (fetch_next_cert_ldap): Detect the truncation error flag. * misc.c (host_and_port_from_url, remove_percent_escapes): New. 2004-11-22 Werner Koch * dirmngr_ldap.c (main): New option --proxy. * ocsp.c (do_ocsp_request): Take care of opt.disable_http. * crlfetch.c (crl_fetch): Honor the --honor-http-proxy variable. (crl_fetch): Take care of opt.disable_http and disable_ldap. (crl_fetch_default, ca_cert_fetch, start_cert_fetch): * ldap.c (run_ldap_wrapper): New arg PROXY. (url_fetch_ldap, attr_fetch_ldap, start_cert_fetch_ldap): Pass it. * http.c (http_open_document): Add arg PROXY. (http_open): Ditto. (send_request): Ditto and implement it as an override. * ocsp.c (validate_responder_cert): Use validate_cert_chain. * Makefile.am (AM_CPPFLAGS): Add macros for a few system directories. * dirmngr.h (opt): New members homedir_data, homedir_cache, ldap_wrapper_program, system_daemon, honor_http_proxy, http_proxy, ldap_proxy, only_ldap_proxy, disable_ldap, disable_http. * dirmngr.c (main): Initialize new opt members HOMEDIR_DATA and HOMEDIR_CACHE. (parse_rereadable_options): New options --ldap-wrapper-program, --http-wrapper-program, --disable-ldap, --disable-http, --honor-http-proxy, --http-proxy, --ldap-proxy, --only-ldap-proxy. (reread_configuration): New. * ldap.c (ldap_wrapper): Use the correct name for the wrapper. * crlcache.c (DBDIR_D): Make it depend on opt.SYSTEM_DAEMON. (cleanup_cache_dir, open_dir, update_dir, make_db_file_name) (crl_cache_insert, create_directory_if_needed): Use opt.HOMEDIR_CACHE * validate.c (check_revocations): New. * crlcache.c (crl_cache_isvalid): Factored most code out to (cache_isvalid): .. new. (crl_cache_cert_isvalid): New. * server.c (cmd_checkcrl): Cleaned up by using this new function. (reload_crl): Moved to .. * crlcache.c (crl_cache_reload_crl): .. here and made global. * certcache.c (cert_compute_fpr): Renamed from computer_fpr and made global. (find_cert_bysn): Try to lookup missing certs. (cert_cache_init): Intialize using opt.HOMEDIR_DATA. 2004-11-19 Werner Koch * dirmngr-client.c (status_cb): New. Use it in very verbose mode. * server.c (start_command_handler): Malloc the control structure and properly release it. Removed the primary_connection hack. Cleanup running wrappers. (dirmngr_status): Return an error code. (dirmngr_tick): Return an error code and detect a cancellation. Use wall time and not CPU time. * validate.c (validate_cert_chain): Add CTRL arg and changed callers. * crlcache.c (crl_cache_isvalid): * crlfetch.c (ca_cert_fetch, start_cert_fetch, crl_fetch_default) (crl_fetch): Ditto. * ldap.c (ldap_wrapper, run_ldap_wrapper, url_fetch_ldap) (attr_fetch_ldap, start_cert_fetch_ldap): Ditto. (ldap_wrapper_release_context): Reset the stored CTRL. (reader_callback): Periodically call dirmngr_tick. (ldap_wrapper_release_context): Print an error message for read errors. (ldap_wrapper_connection_cleanup): New. 2004-11-18 Werner Koch * dirmngr.c (main): Do not cd / if not running detached. * dirmngr-client.c: New options --cache-cert and --validate. (do_cache, do_validate): New. * server.c (cmd_cachecert, cmd_validate): New. * crlcache.c (get_issuer_cert): Make use of the certificate cache. (crl_parse_insert): Validate the issuer certificate. * dirmngr.c (handle_signal): Reinitialize the certificate cache on a HUP. (struct opts): Add --homedir to enable the already implemented code. (handle_signal): Print stats on SIGUSR1. * certcache.c (clean_cache_slot, cert_cache_init) (cert_cache_deinit): New. (acquire_cache_read_lock, acquire_cache_write_lock) (release_cache_lock): New. Use them where needed. (put_cert): Renamed from put_loaded_cert. (cache_cert): New. (cert_cache_print_stats): New. (compare_serialno): Fixed. 2004-11-16 Werner Koch * Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SYSCONFDIR and DIRMNGR_LIBEXECDIR. * misc.c (dump_isotime, dump_string, dump_cert): New. Taken from gnupg 1.9. (dump_serial): New. 2004-11-15 Werner Koch * validate.c: New. Based on gnupg's certchain.c * ldap.c (get_cert_ldap): Removed. (read_buffer): New. (start_cert_fetch_ldap, fetch_next_cert_ldap) (end_cert_fetch_ldap): Rewritten to make use of the ldap wrapper. 2004-11-12 Werner Koch * http.c (insert_escapes): Print the percent sign too. * dirmngr-client.c (inq_cert): Ignore "SENDCERT" and "SENDISSUERCERT". * server.c (do_get_cert_local): Limit the length of a retruned certificate. Return NULL without an error if an empry value has been received. * crlfetch.c (ca_cert_fetch): Use the ksba_reader_object. (setup_funopen, fun_reader, fun_closer): Removed. * crlcache.c (get_issuer_cert): Adjust accordingly. * ldap.c (attr_fetch_ldap_internal, attr_fetch_fun_closer) (attr_fetch_fun_reader, url_fetch_ldap_internal) (get_attr_from_result_ldap): Removed. (destroy_wrapper, print_log_line, ldap_wrapper_thread) (ldap_wrapper_release_context, reader_callback, ldap_wrapper) (run_ldap_wrapper): New. (url_fetch_ldap): Make use of the new ldap wrapper and return a ksba reader object instead of a stdio stream. (attr_fetch_ldap): Ditto. (make_url, escape4url): New. 2004-11-11 Werner Koch * dirmngr.c (launch_ripper_thread): New. (main): Start it wheere appropriate. Always ignore SIGPIPE. (start_connection_thread): Maintain a connection count. (handle_signal, handle_connections): Use it here instead of the thread count. * crlcache.c (crl_cache_insert): Changed to use ksba reader object. Changed all callers to pass this argument. 2004-11-08 Werner Koch * dirmngr_ldap.c: New. * crlcache.c (crl_cache_init): Don't return a cache object but keep it module local. We only need one. (crl_cache_deinit): Don't take cache object but work on existing one. (get_current_cache): New. (crl_cache_insert, crl_cache_list, crl_cache_load): Use the global cache object and removed the cache arg. Changed all callers. * dirmngr-client.c: New option --ping. * dirmngr.c (main): New option --daemon. Initialize PTH. (handle_connections, start_connection_thread): New. (handle_signal): New. (parse_rereadable_options): New. Changed main to make use of it. (set_debug): Don't bail out on invalid debug levels. (main): Init the crl_chache for server and daemon mode. * server.c (start_command_handler): New arg FD. Changed callers. 2004-11-06 Werner Koch * server.c (map_assuan_err): Factored out to .. * maperror.c: .. new file. * util.h: Add prototype 2004-11-05 Werner Koch * no-libgcrypt.c: New, used as helper for dirmngr-client which does not need libgcrypt proper but jnlib references the memory functions. Taken from gnupg 1.9.12. * dirmngr.h: Factored i18n and xmalloc code out to .. * i18n.h, util.h: .. New. * dirmngr-client.c: New. Some code taken from gnupg 1.9.12. * Makefile.am (bin_PROGRAMS) Add dirmngr-client. 2004-11-04 Werner Koch * src/server.c (get_fingerprint_from_line, cmd_checkcrl) (cmd_checkocsp): New. (register_commands): Register new commands. (inquire_cert_and_load_crl): Factored most code out to .. (reload_crl): .. new function. * src/certcache.h, src/certcache.c: New. * src/Makefile.am (dirmngr_SOURCES): Add new files. 2004-11-04 Werner Koch Please note that earlier entries are found in the top level ChangeLog. [Update after merge with GnuPG: see ./ChangeLog.1] Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010 g10 Code GmbH This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without modifications, as long as this notice is preserved. This 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/dirmngr/Makefile.am b/dirmngr/Makefile.am index 7fbff02ff..537bdf035 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -1,65 +1,65 @@ # Makefile.am - dirmngr # Copyright (C) 2002 Klarälvdalens Datakonsult AB # Copyright (C) 2004, 2007, 2010 g10 Code GmbH # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GnuPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . ## Process this file with automake to produce Makefile.in EXTRA_DIST = OAUTHORS ONEWS ChangeLog.1 bin_PROGRAMS = dirmngr dirmngr-client libexec_PROGRAMS = dirmngr_ldap AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common include $(top_srcdir)/am/cmacros.am AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) \ $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(PTH_CFLAGS) BUILT_SOURCES = no-libgcrypt.c noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ ldapserver.h ldapserver.c certcache.c certcache.h \ - b64dec.c cdb.h cdblib.c ldap.c http.c http.h misc.c \ + cdb.h cdblib.c ldap.c misc.c dirmngr-err.h \ ocsp.c ocsp.h validate.c validate.h dirmngr_LDADD = $(libcommonpth) ../gl/libgnu.a $(DNSLIBS) $(LIBASSUAN_LIBS) \ $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(PTH_LIBS) $(LIBINTL) $(LIBICONV) if HAVE_W32_SYSTEM ldap_url = ldap-url.h ldap-url.c else ldap_url = endif dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url) no-libgcrypt.c dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) dirmngr_ldap_LDFLAGS = dirmngr_ldap_LDADD = $(libcommon) ../gl/libgnu.a $(DNSLIBS) \ $(GPG_ERROR_LIBS) $(LDAPLIBS) $(LIBINTL) $(LIBICONV) -dirmngr_client_SOURCES = dirmngr-client.c b64enc.c no-libgcrypt.c +dirmngr_client_SOURCES = dirmngr-client.c no-libgcrypt.c dirmngr_client_LDADD = $(libcommon) ../gl/libgnu.a $(LIBASSUAN_LIBS) \ $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) no-libgcrypt.c : $(top_srcdir)/tools/no-libgcrypt.c cat $(top_srcdir)/tools/no-libgcrypt.c > no-libgcrypt.c diff --git a/dirmngr/b64dec.c b/dirmngr/b64dec.c deleted file mode 100644 index af223aef2..000000000 --- a/dirmngr/b64dec.c +++ /dev/null @@ -1,217 +0,0 @@ -/* b64dec.c - Simple Base64 decoder. - * Copyright (C) 2008 Free Software Foundation, Inc. - * - * This file is part of GnuPG. - * - * GnuPG is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * GnuPG is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#include -#include -#include -#include -#include -#include - -#include "i18n.h" -#include "util.h" - - -/* The reverse base-64 list used for base-64 decoding. */ -static unsigned char const asctobin[128] = - { - 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 - }; - -enum decoder_states - { - s_init, s_idle, s_lfseen, s_begin, - s_b64_0, s_b64_1, s_b64_2, s_b64_3, - s_waitendtitle, s_waitend - }; - - - -/* Initialize the context for the base64 decoder. If TITLE is NULL a - plain base64 decoding is done. If it is the empty string the - decoder will skip everything until a "-----BEGIN " line has been - seen, decoding ends at a "----END " line. - - Not yet implemented: If TITLE is either "PGP" or begins with "PGP " - the PGP armor lines are skipped as well. */ -gpg_error_t -b64dec_start (struct b64state *state, const char *title) -{ - memset (state, 0, sizeof *state); - if (title) - { - if (!strncmp (title, "PGP", 3) && (!title[3] || title[3] == ' ')) - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); - - state->title = xtrystrdup (title); - if (!state->title) - return gpg_error_from_syserror (); - state->idx = s_init; - } - else - state->idx = s_b64_0; - return 0; -} - - -/* Do in-place decoding of base-64 data of LENGTH in BUFFER. Stores the - new length of the buffer at R_NBYTES. */ -gpg_error_t -b64dec_proc (struct b64state *state, void *buffer, size_t length, - size_t *r_nbytes) -{ - enum decoder_states ds = state->idx; - unsigned char val = state->radbuf[0]; - int pos = state->quad_count; - char *d, *s; - - if (state->stop_seen) - { - *r_nbytes = 0; - return gpg_error (GPG_ERR_EOF); - } - - for (s=d=buffer; length && !state->stop_seen; length--, s++) - { - switch (ds) - { - case s_idle: - if (*s == '\n') - { - ds = s_lfseen; - pos = 0; - } - break; - case s_init: - ds = s_lfseen; - case s_lfseen: - if (*s != "-----BEGIN "[pos]) - ds = s_idle; - else if (pos == 10) - ds = s_begin; - else - pos++; - break; - case s_begin: - if (*s == '\n') - ds = s_b64_0; - break; - case s_b64_0: - case s_b64_1: - case s_b64_2: - case s_b64_3: - { - int c; - - if (*s == '-' && state->title) - { - /* Not a valid Base64 character: assume end - header. */ - ds = s_waitend; - } - else if (*s == '=') - { - /* Pad character: stop */ - if (ds == s_b64_1) - *d++ = val; - ds = state->title? s_waitendtitle : s_waitend; - } - else if (*s == '\n' || *s == ' ' || *s == '\r' || *s == '\t') - ; /* Skip white spaces. */ - else if ( (*s & 0x80) - || (c = asctobin[*(unsigned char *)s]) == 255) - { - /* Skip invalid encodings. */ - state->invalid_encoding = 1; - } - else if (ds == s_b64_0) - { - val = c << 2; - ds = s_b64_1; - } - else if (ds == s_b64_1) - { - val |= (c>>4)&3; - *d++ = val; - val = (c<<4)&0xf0; - ds = s_b64_2; - } - else if (ds == s_b64_2) - { - val |= (c>>2)&15; - *d++ = val; - val = (c<<6)&0xc0; - ds = s_b64_3; - } - else - { - val |= c&0x3f; - *d++ = val; - ds = s_b64_0; - } - } - break; - case s_waitendtitle: - if (*s == '-') - ds = s_waitend; - break; - case s_waitend: - if ( *s == '\n') - state->stop_seen = 1; - break; - default: - BUG(); - } - } - - - state->idx = ds; - state->radbuf[0] = val; - state->quad_count = pos; - *r_nbytes = (d -(char*) buffer); - return 0; -} - - -/* This function needs to be called before releasing the decoder - state. It may return an error code in case an encoding error has - been found during decoding. */ -gpg_error_t -b64dec_finish (struct b64state *state) -{ - xfree (state->title); - state->title = NULL; - return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0; -} - diff --git a/dirmngr/b64enc.c b/dirmngr/b64enc.c deleted file mode 100644 index 4429a8e75..000000000 --- a/dirmngr/b64enc.c +++ /dev/null @@ -1,213 +0,0 @@ -/* 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 - */ - -#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 = strdup (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) - { - free (state->title); - state->title = NULL; - } - state->fp = NULL; - return err; -} - diff --git a/dirmngr/cdblib.c b/dirmngr/cdblib.c index de60fe926..16d53aed8 100644 --- a/dirmngr/cdblib.c +++ b/dirmngr/cdblib.c @@ -1,925 +1,935 @@ /* cdblib.c - all CDB library functions. * * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru. * Public domain. * * Taken from tinycdb-0.73 and merged into one file for easier * inclusion into Dirmngr. By Werner Koch 2003-12-12. */ /* A cdb database is a single file used to map `keys' to `values', having records of (key,value) pairs. File consists of 3 parts: toc (table of contents), data and index (hash tables). Toc has fixed length of 2048 bytes, containing 256 pointers to hash tables inside index sections. Every pointer consists of position of a hash table in bytes from the beginning of a file, and a size of a hash table in entries, both are 4-bytes (32 bits) unsigned integers in little-endian form. Hash table length may have zero length, meaning that corresponding hash table is empty. Right after toc section, data section follows without any alingment. It consists of series of records, each is a key length, value (data) length, key and value. Again, key and value length are 4-byte unsigned integers. Each next record follows previous without any special alignment. After data section, index (hash tables) section follows. It should be looked to in conjunction with toc section, where each of max 256 hash tables are defined. Index section consists of series of hash tables, with starting position and length defined in toc section. Every hash table is a sequence of records each holds two numbers: key's hash value and record position inside data section (bytes from the beginning of a file to first byte of key length starting data record). If record position is zero, then this is an empty hash table slot, pointed to nowhere. CDB hash function is hv = ((hv << 5) + hv) ^ c for every single c byte of a key, starting with hv = 5381. Toc section indexed by (hv % 256), i.e. hash value modulo 256 (number of entries in toc section). In order to find a record, one should: first, compute the hash value (hv) of a key. Second, look to hash table number hv modulo 256. If it is empty, then there is no such key exists. If it is not empty, then third, loop by slots inside that hash table, starting from slot with number hv divided by 256 modulo length of that table, or ((hv / 256) % htlen), searching for this hv in hash table. Stop search on empty slot (if record position is zero) or when all slots was probed (note cyclic search, jumping from end to beginning of a table). When hash value in question is found in hash table, look to key of corresponding record, comparing it with key in question. If them of the same length and equals to each other, then record is found, overwise, repeat with next hash table slot. Note that there may be several records with the same key. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef _WIN32 # include #else # include # ifndef MAP_FAILED # define MAP_FAILED ((void*)-1) # endif #endif #include + +#include "dirmngr-err.h" #include "cdb.h" #ifndef EPROTO # define EPROTO EINVAL #endif #ifndef SEEK_SET # define SEEK_SET 0 #endif struct cdb_rec { cdbi_t hval; cdbi_t rpos; }; struct cdb_rl { struct cdb_rl *next; cdbi_t cnt; struct cdb_rec rec[254]; }; static int make_find(struct cdb_make *cdbmp, const void *key, cdbi_t klen, cdbi_t hval, struct cdb_rl **rlp); static int make_write(struct cdb_make *cdbmp, const char *ptr, cdbi_t len); /* Initializes structure given by CDBP pointer and associates it with the open file descriptor FD. Allocate memory for the structure itself if needed and file open operation should be done by application. File FD should be opened at least read-only, and should be seekable. Routine returns 0 on success or negative value on error. */ int cdb_init(struct cdb *cdbp, int fd) { struct stat st; unsigned char *mem; unsigned fsize; #ifdef _WIN32 HANDLE hFile, hMapping; #endif /* get file size */ if (fstat(fd, &st) < 0) return -1; /* trivial sanity check: at least toc should be here */ if (st.st_size < 2048) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } fsize = (unsigned)(st.st_size & 0xffffffffu); /* memory-map file */ #ifdef _WIN32 +# ifdef __MINGW32CE__ + hFile = fd; +# else hFile = (HANDLE) _get_osfhandle(fd); +#endif if (hFile == (HANDLE) -1) return -1; hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (!hMapping) return -1; mem = (unsigned char *)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); if (!mem) return -1; #else mem = (unsigned char*)mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) return -1; #endif /* _WIN32 */ cdbp->cdb_fd = fd; cdbp->cdb_fsize = st.st_size; cdbp->cdb_mem = mem; #if 0 /* XXX don't know well about madvise syscall -- is it legal to set different options for parts of one mmap() region? There is also posix_madvise() exist, with POSIX_MADV_RANDOM etc... */ #ifdef MADV_RANDOM /* set madvise() parameters. Ignore errors for now if system doesn't support it */ madvise(mem, 2048, MADV_WILLNEED); madvise(mem + 2048, cdbp->cdb_fsize - 2048, MADV_RANDOM); #endif #endif cdbp->cdb_vpos = cdbp->cdb_vlen = 0; return 0; } /* Frees the internal resources held by structure. Note that this routine does not close the file. */ void cdb_free(struct cdb *cdbp) { if (cdbp->cdb_mem) { #ifdef _WIN32 HANDLE hFile, hMapping; #endif #ifdef _WIN32 +#ifdef __MINGW32CE__ + hFile = cdbp->cdb_fd; +#else hFile = (HANDLE) _get_osfhandle(cdbp->cdb_fd); +#endif hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); UnmapViewOfFile((void*) cdbp->cdb_mem); CloseHandle(hMapping); #else munmap((void*)cdbp->cdb_mem, cdbp->cdb_fsize); #endif /* _WIN32 */ cdbp->cdb_mem = NULL; } cdbp->cdb_fsize = 0; } /* Read data from cdb file, starting at position pos of length len, placing result to buf. This routine may be used to get actual value found by cdb_find() or other routines that returns position and length of a data. Returns 0 on success or negative value on error. */ int cdb_read(const struct cdb *cdbp, void *buf, unsigned len, cdbi_t pos) { if (pos > cdbp->cdb_fsize || cdbp->cdb_fsize - pos < len) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } memcpy(buf, cdbp->cdb_mem + pos, len); return 0; } /* Attempts to find a key given by (key,klen) parameters. If key exists in database, routine returns 1 and places position and length of value associated with this key to internal fields inside cdbp structure, to be accessible by cdb_datapos() and cdb_datalen(). If key is not in database, routines returns 0. On error, negative value is returned. Note that using cdb_find() it is possible to lookup only first record with a given key. */ int cdb_find(struct cdb *cdbp, const void *key, cdbi_t klen) { const unsigned char *htp; /* hash table pointer */ const unsigned char *htab; /* hash table */ const unsigned char *htend; /* end of hash table */ cdbi_t httodo; /* ht bytes left to look */ cdbi_t pos, n; cdbi_t hval; if (klen > cdbp->cdb_fsize) /* if key size is larger than file */ return 0; hval = cdb_hash(key, klen); /* find (pos,n) hash table to use */ /* first 2048 bytes (toc) are always available */ /* (hval % 256) * 8 */ htp = cdbp->cdb_mem + ((hval << 3) & 2047); /* index in toc (256x8) */ n = cdb_unpack(htp + 4); /* table size */ if (!n) /* empty table */ return 0; /* not found */ httodo = n << 3; /* bytes of htab to lookup */ pos = cdb_unpack(htp); /* htab position */ if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */ || pos > cdbp->cdb_fsize /* htab start within file ? */ || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */ { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } htab = cdbp->cdb_mem + pos; /* htab pointer */ htend = htab + httodo; /* after end of htab */ /* htab starting position: rest of hval modulo htsize, 8bytes per elt */ htp = htab + (((hval >> 8) % n) << 3); for(;;) { pos = cdb_unpack(htp + 4); /* record position */ if (!pos) return 0; if (cdb_unpack(htp) == hval) { if (pos > cdbp->cdb_fsize - 8) { /* key+val lengths */ - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } if (cdb_unpack(cdbp->cdb_mem + pos) == klen) { if (cdbp->cdb_fsize - klen < pos + 8) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } if (memcmp(key, cdbp->cdb_mem + pos + 8, klen) == 0) { n = cdb_unpack(cdbp->cdb_mem + pos + 4); pos += 8 + klen; if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } cdbp->cdb_vpos = pos; cdbp->cdb_vlen = n; return 1; } } } httodo -= 8; if (!httodo) return 0; if ((htp += 8) >= htend) htp = htab; } } /* Sequential-find routines that used separate structure. It is possible to have many than one record with the same key in a database, and these routines allows to enumerate all them. cdb_findinit() initializes search structure pointed to by cdbfp. It will return negative value on error or 0 on success. cdb_find­ next() attempts to find next matching key, setting value position and length in cdbfp structure. It will return positive value if given key was found, 0 if there is no more such key(s), or negative value on error. To access value position and length after successeful call to cdb_findnext() (when it returned positive result), use cdb_datapos() and cdb_datalen() macros with cdbp pointer. It is error to use cdb_findnext() after it returned 0 or error condition. These routines is a bit slower than cdb_find(). Setting KEY to NULL will start a sequential search through the entire DB. */ int cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp, const void *key, cdbi_t klen) { cdbi_t n, pos; cdbfp->cdb_cdbp = cdbp; cdbfp->cdb_key = key; cdbfp->cdb_klen = klen; cdbfp->cdb_hval = key? cdb_hash(key, klen) : 0; if (key) { cdbfp->cdb_htp = cdbp->cdb_mem + ((cdbfp->cdb_hval << 3) & 2047); n = cdb_unpack(cdbfp->cdb_htp + 4); cdbfp->cdb_httodo = n << 3; /* Set to size of hash table. */ if (!n) return 0; /* The hash table is empry. */ pos = cdb_unpack(cdbfp->cdb_htp); if (n > (cdbp->cdb_fsize >> 3) || pos > cdbp->cdb_fsize || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } cdbfp->cdb_htab = cdbp->cdb_mem + pos; cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo; cdbfp->cdb_htp = cdbfp->cdb_htab + (((cdbfp->cdb_hval >> 8) % n) << 3); } else /* Walk over all entries. */ { cdbfp->cdb_hval = 0; /* Force stepping in findnext. */ cdbfp->cdb_htp = cdbfp->cdb_htend = cdbp->cdb_mem; } return 0; } /* See cdb_findinit. */ int cdb_findnext(struct cdb_find *cdbfp) { cdbi_t pos, n; struct cdb *cdbp = cdbfp->cdb_cdbp; if (cdbfp->cdb_key) { while(cdbfp->cdb_httodo) { pos = cdb_unpack(cdbfp->cdb_htp + 4); if (!pos) return 0; n = cdb_unpack(cdbfp->cdb_htp) == cdbfp->cdb_hval; if ((cdbfp->cdb_htp += 8) >= cdbfp->cdb_htend) cdbfp->cdb_htp = cdbfp->cdb_htab; cdbfp->cdb_httodo -= 8; if (n) { if (pos > cdbp->cdb_fsize - 8) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } if (cdb_unpack(cdbp->cdb_mem + pos) == cdbfp->cdb_klen) { if (cdbp->cdb_fsize - cdbfp->cdb_klen < pos + 8) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } if (memcmp(cdbfp->cdb_key, cdbp->cdb_mem + pos + 8, cdbfp->cdb_klen) == 0) { n = cdb_unpack(cdbp->cdb_mem + pos + 4); pos += 8 + cdbfp->cdb_klen; if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } cdbp->cdb_vpos = pos; cdbp->cdb_vlen = n; return 1; } } } } } else /* Walk over all entries. */ { do { while (cdbfp->cdb_htp >= cdbfp->cdb_htend) { if (cdbfp->cdb_hval > 255) return 0; /* No more items. */ cdbfp->cdb_htp = cdbp->cdb_mem + cdbfp->cdb_hval * 8; cdbfp->cdb_hval++; /* Advance for next round. */ pos = cdb_unpack (cdbfp->cdb_htp); /* Offset of table. */ n = cdb_unpack (cdbfp->cdb_htp + 4); /* Number of entries. */ cdbfp->cdb_httodo = n * 8; /* Size of table. */ if (n > (cdbp->cdb_fsize / 8) || pos > cdbp->cdb_fsize || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } cdbfp->cdb_htab = cdbp->cdb_mem + pos; cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo; cdbfp->cdb_htp = cdbfp->cdb_htab; } pos = cdb_unpack (cdbfp->cdb_htp + 4); /* Offset of record. */ cdbfp->cdb_htp += 8; } while (!pos); if (pos > cdbp->cdb_fsize - 8) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } cdbp->cdb_kpos = pos + 8; cdbp->cdb_klen = cdb_unpack(cdbp->cdb_mem + pos); cdbp->cdb_vpos = pos + 8 + cdbp->cdb_klen; cdbp->cdb_vlen = cdb_unpack(cdbp->cdb_mem + pos + 4); n = 8 + cdbp->cdb_klen + cdbp->cdb_vlen; if ( pos > cdbp->cdb_fsize || pos > cdbp->cdb_fsize - n) { - errno = EPROTO; + gpg_err_set_errno (EPROTO); return -1; } return 1; /* Found. */ } return 0; } /* Read a chunk from file, ignoring interrupts (EINTR) */ int cdb_bread(int fd, void *buf, int len) { int l; while(len > 0) { do l = read(fd, buf, len); while(l < 0 && errno == EINTR); if (l <= 0) { if (!l) - errno = EIO; + gpg_err_set_errno (EIO); return -1; } buf = (char*)buf + l; len -= l; } return 0; } /* Find a given key in cdb file, seek a file pointer to it's value and place data length to *dlenp. */ int cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp) { cdbi_t htstart; /* hash table start position */ cdbi_t htsize; /* number of elements in a hash table */ cdbi_t httodo; /* hash table elements left to look */ cdbi_t hti; /* hash table index */ cdbi_t pos; /* position in a file */ cdbi_t hval; /* key's hash value */ unsigned char rbuf[64]; /* read buffer */ int needseek = 1; /* if we should seek to a hash slot */ hval = cdb_hash(key, klen); pos = (hval & 0xff) << 3; /* position in TOC */ /* read the hash table parameters */ if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) return -1; if ((htsize = cdb_unpack(rbuf + 4)) == 0) return 0; hti = (hval >> 8) % htsize; /* start position in hash table */ httodo = htsize; htstart = cdb_unpack(rbuf); for(;;) { if (needseek && lseek(fd, htstart + (hti << 3), SEEK_SET) < 0) return -1; if (cdb_bread(fd, rbuf, 8) < 0) return -1; if ((pos = cdb_unpack(rbuf + 4)) == 0) /* not found */ return 0; if (cdb_unpack(rbuf) != hval) /* hash value not matched */ needseek = 0; else { /* hash value matched */ if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) return -1; if (cdb_unpack(rbuf) == klen) { /* key length matches */ /* read the key from file and compare with wanted */ cdbi_t l = klen, c; const char *k = (const char*)key; if (*dlenp) *dlenp = cdb_unpack(rbuf + 4); /* save value length */ for(;;) { if (!l) /* the whole key read and matches, return */ return 1; c = l > sizeof(rbuf) ? sizeof(rbuf) : l; if (cdb_bread(fd, rbuf, c) < 0) return -1; if (memcmp(rbuf, k, c) != 0) /* no, it differs, stop here */ break; k += c; l -= c; } } needseek = 1; /* we're looked to other place, should seek back */ } if (!--httodo) return 0; if (++hti == htsize) { hti = htstart; needseek = 1; } } } cdbi_t cdb_unpack(const unsigned char buf[4]) { cdbi_t n = buf[3]; n <<= 8; n |= buf[2]; n <<= 8; n |= buf[1]; n <<= 8; n |= buf[0]; return n; } /* Add record with key (KEY,KLEN) and value (VAL,VLEN) to a database. Returns 0 on success or negative value on error. Note that this routine does not checks if given key already exists, but cdb_find() will not see second record with the same key. It is not possible to continue building a database if cdb_make_add() returned an error indicator. */ int cdb_make_add(struct cdb_make *cdbmp, const void *key, cdbi_t klen, const void *val, cdbi_t vlen) { unsigned char rlen[8]; cdbi_t hval; struct cdb_rl *rl; if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) { - errno = ENOMEM; + gpg_err_set_errno (ENOMEM); return -1; } hval = cdb_hash(key, klen); rl = cdbmp->cdb_rec[hval&255]; if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) { rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl)); if (!rl) { - errno = ENOMEM; + gpg_err_set_errno (ENOMEM); return -1; } rl->cnt = 0; rl->next = cdbmp->cdb_rec[hval&255]; cdbmp->cdb_rec[hval&255] = rl; } rl->rec[rl->cnt].hval = hval; rl->rec[rl->cnt].rpos = cdbmp->cdb_dpos; ++rl->cnt; ++cdbmp->cdb_rcnt; cdb_pack(klen, rlen); cdb_pack(vlen, rlen + 4); if (make_write(cdbmp, rlen, 8) < 0 || make_write(cdbmp, key, klen) < 0 || make_write(cdbmp, val, vlen) < 0) return -1; return 0; } int cdb_make_put(struct cdb_make *cdbmp, const void *key, cdbi_t klen, const void *val, cdbi_t vlen, int flags) { unsigned char rlen[8]; cdbi_t hval = cdb_hash(key, klen); struct cdb_rl *rl; int c, r; switch(flags) { case CDB_PUT_REPLACE: case CDB_PUT_INSERT: case CDB_PUT_WARN: c = make_find(cdbmp, key, klen, hval, &rl); if (c < 0) return -1; if (c) { if (flags == CDB_PUT_INSERT) { - errno = EEXIST; + gpg_err_set_errno (EEXIST); return 1; } else if (flags == CDB_PUT_REPLACE) { --c; r = 1; break; } else r = 1; } /* fall */ case CDB_PUT_ADD: rl = cdbmp->cdb_rec[hval&255]; if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) { rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl)); if (!rl) { - errno = ENOMEM; + gpg_err_set_errno (ENOMEM); return -1; } rl->cnt = 0; rl->next = cdbmp->cdb_rec[hval&255]; cdbmp->cdb_rec[hval&255] = rl; } c = rl->cnt; r = 0; break; default: - errno = EINVAL; + gpg_err_set_errno (EINVAL); return -1; } if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) { - errno = ENOMEM; + gpg_err_set_errno (ENOMEM); return -1; } rl->rec[c].hval = hval; rl->rec[c].rpos = cdbmp->cdb_dpos; if (c == rl->cnt) { ++rl->cnt; ++cdbmp->cdb_rcnt; } cdb_pack(klen, rlen); cdb_pack(vlen, rlen + 4); if (make_write(cdbmp, rlen, 8) < 0 || make_write(cdbmp, key, klen) < 0 || make_write(cdbmp, val, vlen) < 0) return -1; return r; } static int match(int fd, cdbi_t pos, const char *key, cdbi_t klen) { unsigned char buf[64]; /*XXX cdb_buf may be used here instead */ if (lseek(fd, pos, SEEK_SET) < 0 || read(fd, buf, 8) != 8) return -1; if (cdb_unpack(buf) != klen) return 0; while(klen > sizeof(buf)) { if (read(fd, buf, sizeof(buf)) != sizeof(buf)) return -1; if (memcmp(buf, key, sizeof(buf)) != 0) return 0; key += sizeof(buf); klen -= sizeof(buf); } if (klen) { if (read(fd, buf, klen) != klen) return -1; if (memcmp(buf, key, klen) != 0) return 0; } return 1; } static int make_find (struct cdb_make *cdbmp, const void *key, cdbi_t klen, cdbi_t hval, struct cdb_rl **rlp) { struct cdb_rl *rl = cdbmp->cdb_rec[hval&255]; int r, i; int seeked = 0; while(rl) { for(i = rl->cnt - 1; i >= 0; --i) { /* search backward */ if (rl->rec[i].hval != hval) continue; /*XXX this explicit flush may be unnecessary having * smarter match() that looks to cdb_buf too, but * most of a time here spent in finding hash values * (above), not keys */ if (cdbmp->cdb_bpos != cdbmp->cdb_buf) { if (write(cdbmp->cdb_fd, cdbmp->cdb_buf, cdbmp->cdb_bpos - cdbmp->cdb_buf) < 0) return -1; cdbmp->cdb_bpos = cdbmp->cdb_buf; } seeked = 1; r = match(cdbmp->cdb_fd, rl->rec[i].rpos, key, klen); if (!r) continue; if (r < 0) return -1; if (lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0) return -1; if (rlp) *rlp = rl; return i + 1; } rl = rl->next; } if (seeked && lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0) return -1; return 0; } int cdb_make_exists(struct cdb_make *cdbmp, const void *key, cdbi_t klen) { return make_find(cdbmp, key, klen, cdb_hash(key, klen), NULL); } void cdb_pack(cdbi_t num, unsigned char buf[4]) { buf[0] = num & 255; num >>= 8; buf[1] = num & 255; num >>= 8; buf[2] = num & 255; buf[3] = num >> 8; } /* Initializes structure to create a database. File FD should be opened read-write and should be seekable. Returns 0 on success or negative value on error. */ int cdb_make_start(struct cdb_make *cdbmp, int fd) { memset (cdbmp, 0, sizeof *cdbmp); cdbmp->cdb_fd = fd; cdbmp->cdb_dpos = 2048; cdbmp->cdb_bpos = cdbmp->cdb_buf + 2048; return 0; } static int ewrite(int fd, const char *buf, int len) { while(len) { int l = write(fd, buf, len); if (l < 0 && errno != EINTR) return -1; if (l > 0) { len -= l; buf += l; } } return 0; } static int make_write(struct cdb_make *cdbmp, const char *ptr, cdbi_t len) { cdbi_t l = sizeof(cdbmp->cdb_buf) - (cdbmp->cdb_bpos - cdbmp->cdb_buf); cdbmp->cdb_dpos += len; if (len > l) { memcpy(cdbmp->cdb_bpos, ptr, l); if (ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, sizeof(cdbmp->cdb_buf)) < 0) return -1; ptr += l; len -= l; l = len / sizeof(cdbmp->cdb_buf); if (l) { l *= sizeof(cdbmp->cdb_buf); if (ewrite(cdbmp->cdb_fd, ptr, l) < 0) return -1; ptr += l; len -= l; } cdbmp->cdb_bpos = cdbmp->cdb_buf; } if (len) { memcpy(cdbmp->cdb_bpos, ptr, len); cdbmp->cdb_bpos += len; } return 0; } static int cdb_make_finish_internal(struct cdb_make *cdbmp) { cdbi_t hcnt[256]; /* hash table counts */ cdbi_t hpos[256]; /* hash table positions */ struct cdb_rec *htab; unsigned char *p; struct cdb_rl *rl; cdbi_t hsize; unsigned t, i; if (((0xffffffff - cdbmp->cdb_dpos) >> 3) < cdbmp->cdb_rcnt) { - errno = ENOMEM; + gpg_err_set_errno (ENOMEM); return -1; } /* count htab sizes and reorder reclists */ hsize = 0; for (t = 0; t < 256; ++t) { struct cdb_rl *rlt = NULL; i = 0; rl = cdbmp->cdb_rec[t]; while(rl) { struct cdb_rl *rln = rl->next; rl->next = rlt; rlt = rl; i += rl->cnt; rl = rln; } cdbmp->cdb_rec[t] = rlt; if (hsize < (hcnt[t] = i << 1)) hsize = hcnt[t]; } /* allocate memory to hold max htable */ htab = (struct cdb_rec*)malloc((hsize + 2) * sizeof(struct cdb_rec)); if (!htab) { - errno = ENOENT; + gpg_err_set_errno (ENOENT); return -1; } p = (unsigned char *)htab; htab += 2; /* build hash tables */ for (t = 0; t < 256; ++t) { cdbi_t len, hi; hpos[t] = cdbmp->cdb_dpos; if ((len = hcnt[t]) == 0) continue; for (i = 0; i < len; ++i) htab[i].hval = htab[i].rpos = 0; for (rl = cdbmp->cdb_rec[t]; rl; rl = rl->next) for (i = 0; i < rl->cnt; ++i) { hi = (rl->rec[i].hval >> 8) % len; while(htab[hi].rpos) if (++hi == len) hi = 0; htab[hi] = rl->rec[i]; } for (i = 0; i < len; ++i) { cdb_pack(htab[i].hval, p + (i << 3)); cdb_pack(htab[i].rpos, p + (i << 3) + 4); } if (make_write(cdbmp, p, len << 3) < 0) { free(p); return -1; } } free(p); if (cdbmp->cdb_bpos != cdbmp->cdb_buf && ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, cdbmp->cdb_bpos - cdbmp->cdb_buf) != 0) return -1; p = cdbmp->cdb_buf; for (t = 0; t < 256; ++t) { cdb_pack(hpos[t], p + (t << 3)); cdb_pack(hcnt[t], p + (t << 3) + 4); } if (lseek(cdbmp->cdb_fd, 0, 0) != 0 || ewrite(cdbmp->cdb_fd, p, 2048) != 0) return -1; return 0; } static void cdb_make_free(struct cdb_make *cdbmp) { unsigned t; for(t = 0; t < 256; ++t) { struct cdb_rl *rl = cdbmp->cdb_rec[t]; while(rl) { struct cdb_rl *tm = rl; rl = rl->next; free(tm); } } } /* Finalizes database file, constructing all needed indexes, and frees memory structures. It does not close the file descriptor. Returns 0 on success or a negative value on error. */ int cdb_make_finish(struct cdb_make *cdbmp) { int r = cdb_make_finish_internal(cdbmp); cdb_make_free(cdbmp); return r; } cdbi_t cdb_hash(const void *buf, cdbi_t len) { register const unsigned char *p = (const unsigned char *)buf; register const unsigned char *end = p + len; register cdbi_t hash = 5381; /* start value */ while (p < end) hash = (hash + (hash << 5)) ^ *p++; return hash; } diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c index c40bb17d0..46d2ac365 100644 --- a/dirmngr/certcache.c +++ b/dirmngr/certcache.c @@ -1,1384 +1,1384 @@ /* certcache.c - Certificate caching * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "crlfetch.h" #include "certcache.h" #define MAX_EXTRA_CACHED_CERTS 1000 /* Constants used to classify search patterns. */ enum pattern_class { PATTERN_UNKNOWN = 0, PATTERN_EMAIL, PATTERN_EMAIL_SUBSTR, PATTERN_FINGERPRINT16, PATTERN_FINGERPRINT20, PATTERN_SHORT_KEYID, PATTERN_LONG_KEYID, PATTERN_SUBJECT, PATTERN_SERIALNO, PATTERN_SERIALNO_ISSUER, PATTERN_ISSUER, PATTERN_SUBSTR }; /* A certificate cache item. This consists of a the KSBA cert object and some meta data for easier lookup. We use a hash table to keep track of all items and use the (randomly distributed) first byte of the fingerprint directly as the hash which makes it pretty easy. */ struct cert_item_s { struct cert_item_s *next; /* Next item with the same hash value. */ ksba_cert_t cert; /* The KSBA cert object or NULL is this is not a valid item. */ unsigned char fpr[20]; /* The fingerprint of this object. */ char *issuer_dn; /* The malloced issuer DN. */ ksba_sexp_t sn; /* The malloced serial number */ char *subject_dn; /* The malloced subject DN - maybe NULL. */ struct { unsigned int loaded:1; /* It has been explicitly loaded. */ unsigned int trusted:1; /* This is a trusted root certificate. */ } flags; }; typedef struct cert_item_s *cert_item_t; /* The actual cert cache consisting of 256 slots for items indexed by the first byte of the fingerprint. */ static cert_item_t cert_cache[256]; /* This is the global cache_lock variable. In general looking is not needed but it would take extra efforts to make sure that no indirect use of pth functions is done, so we simply lock it always. Note: We can't use static initialization, as that is not available through w32-pth. */ static pth_rwlock_t cert_cache_lock; /* Flag to track whether the cache has been initialized. */ static int initialization_done; /* Total number of certificates loaded during initialization and cached during operation. */ static unsigned int total_loaded_certificates; static unsigned int total_extra_certificates; /* Helper to do the cache locking. */ static void init_cache_lock (void) { if (!pth_rwlock_init (&cert_cache_lock)) log_fatal (_("can't initialize certificate cache lock: %s\n"), strerror (errno)); } static void acquire_cache_read_lock (void) { if (!pth_rwlock_acquire (&cert_cache_lock, PTH_RWLOCK_RD, FALSE, NULL)) log_fatal (_("can't acquire read lock on the certificate cache: %s\n"), strerror (errno)); } static void acquire_cache_write_lock (void) { if (!pth_rwlock_acquire (&cert_cache_lock, PTH_RWLOCK_RW, FALSE, NULL)) log_fatal (_("can't acquire write lock on the certificate cache: %s\n"), strerror (errno)); } static void release_cache_lock (void) { if (!pth_rwlock_release (&cert_cache_lock)) log_fatal (_("can't release lock on the certificate cache: %s\n"), strerror (errno)); } /* Return false if both serial numbers match. Can't be used for sorting. */ static int compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 ) { unsigned char *a = serial1; unsigned char *b = serial2; return cmp_simple_canon_sexp (a, b); } /* Return a malloced canonical S-Expression with the serialnumber converted from the hex string HEXSN. Return NULL on memory error. */ ksba_sexp_t hexsn_to_sexp (const char *hexsn) { char *buffer, *p; size_t len; char numbuf[40]; len = unhexify (NULL, hexsn); snprintf (numbuf, sizeof numbuf, "(%u:", (unsigned int)len); buffer = xtrymalloc (strlen (numbuf) + len + 2 ); if (!buffer) return NULL; p = stpcpy (buffer, numbuf); len = unhexify (p, hexsn); p[len] = ')'; p[len+1] = 0; return buffer; } /* Compute the fingerprint of the certificate CERT and put it into the 20 bytes large buffer DIGEST. Return address of this buffer. */ unsigned char * cert_compute_fpr (ksba_cert_t cert, unsigned char *digest) { gpg_error_t err; gcry_md_hd_t md; err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err)); err = ksba_cert_hash (cert, 0, HASH_FNC, md); if (err) { log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err)); memset (digest, 0xff, 20); /* Use a dummy value. */ } else { gcry_md_final (md); memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20); } gcry_md_close (md); return digest; } /* Cleanup one slot. This releases all resourses but keeps the actual slot in the cache marked for reuse. */ static void clean_cache_slot (cert_item_t ci) { ksba_cert_t cert; if (!ci->cert) return; /* Already cleaned. */ ksba_free (ci->sn); ci->sn = NULL; ksba_free (ci->issuer_dn); ci->issuer_dn = NULL; ksba_free (ci->subject_dn); ci->subject_dn = NULL; cert = ci->cert; ci->cert = NULL; ksba_cert_release (cert); } /* Put the certificate CERT into the cache. It is assumed that the cache is locked while this function is called. If FPR_BUFFER is not NULL the fingerprint of the certificate will be stored there. FPR_BUFFER neds to point to a buffer of at least 20 bytes. The fingerprint will be stored on success or when the function returns gpg_err_code(GPG_ERR_DUP_VALUE). */ static gpg_error_t put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer) { unsigned char help_fpr_buffer[20], *fpr; cert_item_t ci; fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer; /* If we already reached the caching limit, drop a couple of certs from the cache. Our dropping strategy is simple: We keep a static index counter and use this to start looking for certificates, then we drop 5 percent of the oldest certificates starting at that index. For a large cache this is a fair way of removing items. An LRU strategy would be better of course. Because we append new entries to the head of the list and we want to remove old ones first, we need to do this from the tail. The implementation is not very efficient but compared to the long time it takes to retrieve a certifciate from an external resource it seems to be reasonable. */ if (!is_loaded && total_extra_certificates >= MAX_EXTRA_CACHED_CERTS) { static int idx; cert_item_t ci_mark; int i; unsigned int drop_count; drop_count = MAX_EXTRA_CACHED_CERTS / 20; if (drop_count < 2) drop_count = 2; log_info (_("dropping %u certificates from the cache\n"), drop_count); assert (idx < 256); for (i=idx; drop_count; i = ((i+1)%256)) { ci_mark = NULL; for (ci = cert_cache[i]; ci; ci = ci->next) if (ci->cert && !ci->flags.loaded) ci_mark = ci; if (ci_mark) { clean_cache_slot (ci_mark); drop_count--; total_extra_certificates--; } } if (i==idx) idx++; else idx = i; idx %= 256; } cert_compute_fpr (cert, fpr); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) return gpg_error (GPG_ERR_DUP_VALUE); /* Try to reuse an existing entry. */ for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (!ci->cert) break; if (!ci) { /* No: Create a new entry. */ ci = xtrycalloc (1, sizeof *ci); if (!ci) return gpg_error_from_errno (errno); ci->next = cert_cache[*fpr]; cert_cache[*fpr] = ci; } else memset (&ci->flags, 0, sizeof ci->flags); ksba_cert_ref (cert); ci->cert = cert; memcpy (ci->fpr, fpr, 20); ci->sn = ksba_cert_get_serial (cert); ci->issuer_dn = ksba_cert_get_issuer (cert, 0); if (!ci->issuer_dn || !ci->sn) { clean_cache_slot (ci); return gpg_error (GPG_ERR_INV_CERT_OBJ); } ci->subject_dn = ksba_cert_get_subject (cert, 0); ci->flags.loaded = !!is_loaded; ci->flags.trusted = !!is_trusted; if (is_loaded) total_loaded_certificates++; else total_extra_certificates++; return 0; } /* Load certificates from the directory DIRNAME. All certificates matching the pattern "*.crt" or "*.der" are loaded. We assume that certificates are DER encoded and not PEM encapsulated. The cache should be in a locked state when calling this fucntion. */ static gpg_error_t load_certs_from_dir (const char *dirname, int are_trusted) { gpg_error_t err; DIR *dir; struct dirent *ep; char *p; size_t n; - FILE *fp; + estream_t fp; ksba_reader_t reader; ksba_cert_t cert; char *fname = NULL; dir = opendir (dirname); if (!dir) { if (opt.system_daemon) log_info (_("can't access directory `%s': %s\n"), dirname, strerror (errno)); return 0; /* We do not consider this a severe error. */ } while ( (ep=readdir (dir)) ) { p = ep->d_name; if (*p == '.' || !*p) continue; /* Skip any hidden files and invalid entries. */ n = strlen (p); if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der"))) continue; /* Not the desired "*.crt" or "*.der" pattern. */ xfree (fname); fname = make_filename (dirname, p, NULL); - fp = fopen (fname, "rb"); + fp = es_fopen (fname, "rb"); if (!fp) { log_error (_("can't open `%s': %s\n"), fname, strerror (errno)); continue; } err = ksba_reader_new (&reader); if (!err) err = ksba_reader_set_file (reader, fp); if (err) { log_error (_("can't setup KSBA reader: %s\n"), gpg_strerror (err)); ksba_reader_release (reader); - fclose (fp); + es_fclose (fp); continue; } err = ksba_cert_new (&cert); if (!err) err = ksba_cert_read_der (cert, reader); ksba_reader_release (reader); - fclose (fp); + es_fclose (fp); if (err) { log_error (_("can't parse certificate `%s': %s\n"), fname, gpg_strerror (err)); ksba_cert_release (cert); continue; } err = put_cert (cert, 1, are_trusted, NULL); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate `%s' already cached\n"), fname); else if (!err) { if (are_trusted) log_info (_("trusted certificate `%s' loaded\n"), fname); else log_info (_("certificate `%s' loaded\n"), fname); if (opt.verbose) { p = get_fingerprint_hexstring_colon (cert); log_info (_(" SHA1 fingerprint = %s\n"), p); xfree (p); cert_log_name (_(" issuer ="), cert); cert_log_subject (_(" subject ="), cert); } } else log_error (_("error loading certificate `%s': %s\n"), fname, gpg_strerror (err)); ksba_cert_release (cert); } xfree (fname); closedir (dir); return 0; } /* Initialize the certificate cache if not yet done. */ void cert_cache_init (void) { char *dname; if (initialization_done) return; init_cache_lock (); acquire_cache_write_lock (); dname = make_filename (opt.homedir, "trusted-certs", NULL); load_certs_from_dir (dname, 1); xfree (dname); dname = make_filename (opt.homedir_data, "extra-certs", NULL); load_certs_from_dir (dname, 0); xfree (dname); initialization_done = 1; release_cache_lock (); cert_cache_print_stats (); } /* Deinitialize the certificate cache. With FULL set to true even the unused certificate slots are released. */ void cert_cache_deinit (int full) { cert_item_t ci, ci2; int i; if (!initialization_done) return; acquire_cache_write_lock (); for (i=0; i < 256; i++) for (ci=cert_cache[i]; ci; ci = ci->next) clean_cache_slot (ci); if (full) { for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci2) { ci2 = ci->next; xfree (ci); } cert_cache[i] = NULL; } } total_loaded_certificates = 0; total_extra_certificates = 0; initialization_done = 0; release_cache_lock (); } /* Print some statistics to the log file. */ void cert_cache_print_stats (void) { log_info (_("permanently loaded certificates: %u\n"), total_loaded_certificates); log_info (_(" runtime cached certificates: %u\n"), total_extra_certificates); } /* Put CERT into the certificate cache. */ gpg_error_t cache_cert (ksba_cert_t cert) { gpg_error_t err; acquire_cache_write_lock (); err = put_cert (cert, 0, 0, NULL); release_cache_lock (); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate already cached\n")); else if (!err) log_info (_("certificate cached\n")); else log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return err; } /* Put CERT into the certificate cache and store the fingerprint of the certificate into FPR_BUFFER. If the certificate is already in the cache do not print a warning; just store the fingerprint. FPR_BUFFER needs to be at least 20 bytes. */ gpg_error_t cache_cert_silent (ksba_cert_t cert, void *fpr_buffer) { gpg_error_t err; acquire_cache_write_lock (); err = put_cert (cert, 0, 0, fpr_buffer); release_cache_lock (); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) err = 0; if (err) log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return err; } /* Return a certificate object for the given fingerprint. FPR is expected to be a 20 byte binary SHA-1 fingerprint. If no matching certificate is available in the cache NULL is returned. The caller must release a returned certificate. Note that although we are using reference counting the caller should not just compare the pointers to check for identical certificates. */ ksba_cert_t get_cert_byfpr (const unsigned char *fpr) { cert_item_t ci; acquire_cache_read_lock (); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } release_cache_lock (); return NULL; } /* Return a certificate object for the given fingerprint. STRING is expected to be a SHA-1 fingerprint in standard hex notation with or without colons. If no matching certificate is available in the cache NULL is returned. The caller must release a returned certificate. Note that although we are using reference counting the caller should not just compare the pointers to check for identical certificates. */ ksba_cert_t get_cert_byhexfpr (const char *string) { unsigned char fpr[20]; const char *s; int i; if (strchr (string, ':')) { for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);) { if (s[2] && s[2] != ':') break; /* Invalid string. */ fpr[i++] = xtoi_2 (s); s += 2; if (i!= 20 && *s == ':') s++; } } else { for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 ) fpr[i++] = xtoi_2 (s); } if (i!=20 || *s) { log_error (_("invalid SHA1 fingerprint string `%s'\n"), string); return NULL; } return get_cert_byfpr (fpr); } /* Return the certificate matching ISSUER_DN and SERIALNO. */ ksba_cert_t get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno) { /* Simple and inefficient implementation. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn) && !compare_serialno (ci->sn, serialno)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return the certificate matching ISSUER_DN. SEQ should initially be set to 0 and bumped up to get the next issuer with that DN. */ ksba_cert_t get_cert_byissuer (const char *issuer_dn, unsigned int seq) { /* Simple and very inefficient implementation and API. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)) if (!seq--) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return the certificate matching SUBJECT_DN. SEQ should initially be set to 0 and bumped up to get the next subject with that DN. */ ksba_cert_t get_cert_bysubject (const char *subject_dn, unsigned int seq) { /* Simple and very inefficient implementation and API. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && ci->subject_dn && !strcmp (ci->subject_dn, subject_dn)) if (!seq--) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return a value decribing the the class of PATTERN. The offset of the actual string to be used for the comparison is stored at R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */ static enum pattern_class classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset) { enum pattern_class result = PATTERN_UNKNOWN; const char *s; int hexprefix = 0; int hexlength; int mode = 0; *r_offset = *r_sn_offset = 0; /* Skip leading spaces. */ for(s = pattern; *s && spacep (s); s++ ) ; switch (*s) { case 0: /* Empty string is an error. */ result = PATTERN_UNKNOWN; break; case '.': /* An email address, compare from end. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; case '<': /* An email address. */ result = PATTERN_EMAIL; s++; break; case '@': /* Part of an email address. */ result = PATTERN_EMAIL_SUBSTR; s++; break; case '=': /* Exact compare. */ result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */ break; case '*': /* Case insensitive substring search. */ mode = PATTERN_SUBSTR; s++; break; case '+': /* Compare individual words. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; case '/': /* Subject's DN. */ s++; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_SUBJECT; break; case '#': /* Serial number or issuer DN. */ { const char *si; s++; if ( *s == '/') { /* An issuer's DN is indicated by "#/" */ s++; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_ISSUER; } else { /* Serialnumber + optional issuer ID. */ for (si=s; *si && *si != '/'; si++) if (!strchr("01234567890abcdefABCDEF", *si)) break; if (*si && *si != '/') result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */ else { *r_sn_offset = s - pattern; if (!*si) result = PATTERN_SERIALNO; else { s = si+1; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_SERIALNO_ISSUER; } } } } break; case ':': /* Unified fingerprint. */ { const char *se, *si; int i; se = strchr (++s, ':'); if (!se) result = PATTERN_UNKNOWN; else { for (i=0, si=s; si < se; si++, i++ ) if (!strchr("01234567890abcdefABCDEF", *si)) break; if ( si < se ) result = PATTERN_UNKNOWN; /* Invalid digit. */ else if (i == 32) result = PATTERN_FINGERPRINT16; else if (i == 40) result = PATTERN_FINGERPRINT20; else result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */ } } break; case '&': /* Keygrip. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; default: if (s[0] == '0' && s[1] == 'x') { hexprefix = 1; s += 2; } hexlength = strspn(s, "0123456789abcdefABCDEF"); /* Check if a hexadecimal number is terminated by EOS or blank. */ if (hexlength && s[hexlength] && !spacep (s+hexlength)) { /* If the "0x" prefix is used a correct termination is required. */ if (hexprefix) { result = PATTERN_UNKNOWN; break; /* switch */ } hexlength = 0; /* Not a hex number. */ } if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0')) { if (hexlength == 9) s++; result = PATTERN_SHORT_KEYID; } else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0')) { if (hexlength == 17) s++; result = PATTERN_LONG_KEYID; } else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0')) { if (hexlength == 33) s++; result = PATTERN_FINGERPRINT16; } else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0')) { if (hexlength == 41) s++; result = PATTERN_FINGERPRINT20; } else if (!hexprefix) { /* The fingerprints used with X.509 are often delimited by colons, so we try to single this case out. */ result = PATTERN_UNKNOWN; hexlength = strspn (s, ":0123456789abcdefABCDEF"); if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) { int i, c; for (i=0; i < 20; i++, s += 3) { c = hextobyte(s); if (c == -1 || (i < 19 && s[2] != ':')) break; } if (i == 20) result = PATTERN_FINGERPRINT20; } if (result == PATTERN_UNKNOWN) /* Default to substring match. */ { result = PATTERN_SUBSTR; } } else /* A hex number with a prefix but with a wrong length. */ result = PATTERN_UNKNOWN; } if (result != PATTERN_UNKNOWN) *r_offset = s - pattern; return result; } /* Given PATTERN, which is a string as used by GnuPG to specify a certificate, return all matching certificates by calling the supplied function RETFNC. */ gpg_error_t get_certs_bypattern (const char *pattern, gpg_error_t (*retfnc)(void*,ksba_cert_t), void *retfnc_data) { gpg_error_t err = GPG_ERR_BUG; enum pattern_class class; size_t offset, sn_offset; const char *hexserialno; ksba_sexp_t serialno = NULL; ksba_cert_t cert = NULL; unsigned int seq; if (!pattern || !retfnc) return gpg_error (GPG_ERR_INV_ARG); class = classify_pattern (pattern, &offset, &sn_offset); hexserialno = pattern + sn_offset; pattern += offset; switch (class) { case PATTERN_UNKNOWN: err = gpg_error (GPG_ERR_INV_NAME); break; case PATTERN_FINGERPRINT20: cert = get_cert_byhexfpr (pattern); err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_SERIALNO_ISSUER: serialno = hexsn_to_sexp (hexserialno); if (!serialno) err = gpg_error_from_syserror (); else { cert = get_cert_bysn (pattern, serialno); err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); } break; case PATTERN_ISSUER: for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++) { err = retfnc (retfnc_data, cert); ksba_cert_release (cert); cert = NULL; } if (!err && !seq) err = gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_SUBJECT: for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++) { err = retfnc (retfnc_data, cert); ksba_cert_release (cert); cert = NULL; } if (!err && !seq) err = gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_EMAIL: case PATTERN_EMAIL_SUBSTR: case PATTERN_FINGERPRINT16: case PATTERN_SHORT_KEYID: case PATTERN_LONG_KEYID: case PATTERN_SUBSTR: case PATTERN_SERIALNO: /* Not supported. */ err = gpg_error (GPG_ERR_INV_NAME); } if (!err && cert) err = retfnc (retfnc_data, cert); ksba_cert_release (cert); xfree (serialno); return err; } /* Return the certificate matching ISSUER_DN and SERIALNO; if it is not already in the cache, try to find it from other resources. */ ksba_cert_t find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno) { gpg_error_t err; ksba_cert_t cert; cert_fetch_context_t context = NULL; char *hexsn, *buf; /* First check whether it has already been cached. */ cert = get_cert_bysn (issuer_dn, serialno); if (cert) return cert; /* Ask back to the service requester to return the certificate. This is because we can assume that he already used the certificate while checking for the CRL. */ hexsn = serial_hex (serialno); if (!hexsn) { log_error ("serial_hex() failed\n"); return NULL; } buf = xtrymalloc (1 + strlen (hexsn) + 1 + strlen (issuer_dn) + 1); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexsn); return NULL; } strcpy (stpcpy (stpcpy (stpcpy (buf, "#"), hexsn),"/"), issuer_dn); xfree (hexsn); cert = get_cert_local (ctrl, buf); xfree (buf); if (cert) { cache_cert (cert); return cert; /* Done. */ } if (DBG_LOOKUP) log_debug ("find_cert_bysn: certificate not returned by caller" " - doing lookup\n"); /* Retrieve the certificate from external resources. */ while (!cert) { ksba_sexp_t sn; char *issdn; if (!context) { err = ca_cert_fetch (ctrl, &context, issuer_dn); if (err) { log_error (_("error fetching certificate by S/N: %s\n"), gpg_strerror (err)); break; } } err = fetch_next_ksba_cert (context, &cert); if (err) { log_error (_("error fetching certificate by S/N: %s\n"), gpg_strerror (err) ); break; } issdn = ksba_cert_get_issuer (cert, 0); if (strcmp (issuer_dn, issdn)) { log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n"); ksba_cert_release (cert); cert = NULL; ksba_free (issdn); break; } sn = ksba_cert_get_serial (cert); if (DBG_LOOKUP) { log_debug (" considering certificate (#"); dump_serial (sn); log_printf ("/"); dump_string (issdn); log_printf (")\n"); } if (!compare_serialno (serialno, sn)) { ksba_free (sn); ksba_free (issdn); cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } ksba_free (sn); ksba_free (issdn); ksba_cert_release (cert); cert = NULL; } end_cert_fetch (context); return cert; } /* Return the certificate matching SUBJECT_DN and (if not NULL) KEYID. If it is not already in the cache, try to find it from other resources. Note, that the external search does not work for user certificates because the LDAP lookup is on the caCertificate attribute. For our purposes this is just fine. */ ksba_cert_t find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid) { gpg_error_t err; int seq; ksba_cert_t cert = NULL; cert_fetch_context_t context = NULL; ksba_sexp_t subj; /* If we have certificates from an OCSP request we first try to use them. This is because these certificates will really be the required ones and thus even in the case that they can't be uniquely located by the following code we can use them. This is for example required by Telesec certificates where a keyId is used but the issuer certificate comes without a subject keyId! */ if (ctrl->ocsp_certs) { cert_item_t ci; cert_ref_t cr; int i; /* For efficiency reasons we won't use get_cert_bysubject here. */ acquire_cache_read_lock (); for (i=0; i < 256; i++) for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && ci->subject_dn && !strcmp (ci->subject_dn, subject_dn)) for (cr=ctrl->ocsp_certs; cr; cr = cr->next) if (!memcmp (ci->fpr, cr->fpr, 20)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; /* We use this certificate. */ } release_cache_lock (); if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n"); } /* First we check whether the certificate is cached. */ for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++) { if (!keyid) break; /* No keyid requested, so return the first one found. */ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj) && !cmp_simple_canon_sexp (keyid, subj)) { xfree (subj); break; /* Found matching cert. */ } xfree (subj); ksba_cert_release (cert); } if (cert) return cert; /* Done. */ if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not in cache\n"); /* Ask back to the service requester to return the certificate. This is because we can assume that he already used the certificate while checking for the CRL. */ if (keyid) cert = get_cert_local_ski (ctrl, subject_dn, keyid); else { /* In contrast to get_cert_local_ski, get_cert_local uses any passed pattern, so we need to make sure that an exact subject search is done. */ char *buf; buf = xtrymalloc (1 + strlen (subject_dn) + 1); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); return NULL; } strcpy (stpcpy (buf, "/"), subject_dn); cert = get_cert_local (ctrl, buf); xfree (buf); } if (cert) { cache_cert (cert); return cert; /* Done. */ } if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not returned by caller" " - doing lookup\n"); /* Locate the certificate using external resources. */ while (!cert) { char *subjdn; if (!context) { err = ca_cert_fetch (ctrl, &context, subject_dn); if (err) { log_error (_("error fetching certificate by subject: %s\n"), gpg_strerror (err)); break; } } err = fetch_next_ksba_cert (context, &cert); if (err) { log_error (_("error fetching certificate by subject: %s\n"), gpg_strerror (err) ); break; } subjdn = ksba_cert_get_subject (cert, 0); if (strcmp (subject_dn, subjdn)) { log_info ("find_cert_bysubject: subject DN does not match\n"); ksba_cert_release (cert); cert = NULL; ksba_free (subjdn); continue; } if (DBG_LOOKUP) { log_debug (" considering certificate (/"); dump_string (subjdn); log_printf (")\n"); } ksba_free (subjdn); /* If no key ID has been provided, we return the first match. */ if (!keyid) { cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } /* With the key ID given we need to compare it. */ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) { if (!cmp_simple_canon_sexp (keyid, subj)) { ksba_free (subj); cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } } ksba_free (subj); ksba_cert_release (cert); cert = NULL; } end_cert_fetch (context); return cert; } /* Return 0 if the certificate is a trusted certificate. Returns GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in case of systems errors. */ gpg_error_t is_trusted_cert (ksba_cert_t cert) { unsigned char fpr[20]; cert_item_t ci; cert_compute_fpr (cert, fpr); acquire_cache_read_lock (); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) { if (ci->flags.trusted) { release_cache_lock (); return 0; /* Yes, it is trusted. */ } break; } release_cache_lock (); return gpg_error (GPG_ERR_NOT_TRUSTED); } /* Given the certificate CERT locate the issuer for this certificate and return it at R_CERT. Returns 0 on success or GPG_ERR_NOT_FOUND. */ gpg_error_t find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert) { gpg_error_t err; char *issuer_dn; ksba_cert_t issuer_cert = NULL; ksba_name_t authid; ksba_sexp_t authidno; ksba_sexp_t keyid; *r_cert = NULL; issuer_dn = ksba_cert_get_issuer (cert, 0); if (!issuer_dn) { log_error (_("no issuer found in certificate\n")); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* First we need to check whether we can return that certificate using the authorithyKeyIdentifier. */ err = ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno); if (err) { log_info (_("error getting authorityKeyIdentifier: %s\n"), gpg_strerror (err)); } else { const char *s = ksba_name_enum (authid, 0); if (s && *authidno) { issuer_cert = find_cert_bysn (ctrl, s, authidno); } if (!issuer_cert && keyid) { /* Not found by issuer+s/n. Now that we have an AKI keyIdentifier look for a certificate with a matching SKI. */ issuer_cert = find_cert_bysubject (ctrl, issuer_dn, 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 (!issuer_cert) { log_info ("issuer certificate "); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } if (authidno) { log_printf ("(#"); dump_serial (authidno); log_printf ("/"); dump_string (s); log_printf (") "); } log_printf ("not found using authorityKeyIdentifier\n"); } ksba_name_release (authid); xfree (authidno); xfree (keyid); } /* If this did not work, try just with the issuer's name and assume that there is only one such certificate. We only look into our cache then. */ if (err || !issuer_cert) { issuer_cert = get_cert_bysubject (issuer_dn, 0); if (issuer_cert) err = 0; } leave: if (!err && !issuer_cert) err = gpg_error (GPG_ERR_NOT_FOUND); xfree (issuer_dn); if (err) ksba_cert_release (issuer_cert); else *r_cert = issuer_cert; return err; } diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index 9ec5414fa..aeb6304b0 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1,2544 +1,2545 @@ /* crlcache.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2008 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* 1. To keep track of the CRLs actually cached and to store the meta information of the CRLs a simple record oriented text file is used. Fields in the file are colon (':') separated and values containing colons or linefeeds are percent escaped (e.g. a colon itself is represented as "%3A"). The first field is a record type identifier, so that the file is useful to keep track of other meta data too. The name of the file is "DIR.txt". 1.1. Comment record Field 1: Constant beginning with "#". Other fields are not defined and such a record is simply skipped during processing. 1.2. Version record Field 1: Constant "v" Field 2: Version number of this file. Must be 1. This record must be the first non-comment record record and there shall only exist one record of this type. 1.3. CRL cache record Field 1: Constant "c", "u" or "i". A "c" or "u" indicate a valid cache entry, however "u" requires that a user root certificate check needs to be done. An "i" indicates an invalid Cache entry which should not be used but still exists so that it can be updated at NEXT_UPDATE. Field 2: Hexadecimal encoded SHA-1 hash of the issuer DN using uppercase letters. Field 3: Issuer DN in RFC-2253 notation. Field 4: URL used to retrieve the corresponding CRL. Field 5: 15 character ISO timestamp with THIS_UPDATE. Field 6: 15 character ISO timestamp with NEXT_UPDATE. Field 7: Hexadecimal encoded MD-5 hash of the DB file to detect accidental modified (i.e. deleted and created) cache files. Field 8: optional CRL number as a hex string. Field 9: AuthorityKeyID.issuer, each Name separated by 0x01 Field 10: AuthorityKeyID.serial Field 11: Hex fingerprint of trust anchor if field 1 is 'u'. 2. Layout of the standard CRL Cache DB file: We use records of variable length with this structure n bytes Serialnumber (binary) used as key thus there is no need to store the length explicitly with DB2. 1 byte Reason for revocation (currently the KSBA reason flags are used) 15 bytes ISO date of revocation (e.g. 19980815T142000) Note that there is no terminating 0 stored. The filename used is the hexadecimal (using uppercase letters) SHA-1 hash value of the issuer DN prefixed with a "crl-" and suffixed with a ".db". Thus the length of the filename is 47. */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #endif #ifdef MKDIR_TAKES_ONE_ARG #undef mkdir #define mkdir(a,b) mkdir(a) #endif #include "dirmngr.h" #include "validate.h" #include "certcache.h" #include "crlcache.h" #include "crlfetch.h" #include "misc.h" #include "cdb.h" #include "estream-printf.h" /* Change this whenever the format changes */ #define DBDIR_D (opt.system_daemon? "crls.d" : "dirmngr-cache.d") #define DBDIRFILE "DIR.txt" #define DBDIRVERSION 1 /* The number of DB files we may have open at one time. We need to limit this because there is no guarantee that the number of issuers has a upper limit. We are currently using mmap, so it is a good idea anyway to limit the number of opened cache files. */ #define MAX_OPEN_DB_FILES 5 static const char oidstr_crlNumber[] = "2.5.29.20"; static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; /* Definition of one cached item. */ struct crl_cache_entry_s { struct crl_cache_entry_s *next; int deleted; /* True if marked for deletion. */ int mark; /* Internally used by update_dir. */ unsigned int lineno;/* A 0 indicates a new entry. */ char *release_ptr; /* The actual allocated memory. */ char *url; /* Points into RELEASE_PTR. */ char *issuer; /* Ditto. */ char *issuer_hash; /* Ditto. */ char *dbfile_hash; /* MD5 sum of the cache file, points into RELEASE_PTR.*/ int invalid; /* Can't use this CRL. */ int user_trust_req; /* User supplied root certificate required. */ char *check_trust_anchor; /* Malloced fingerprint. */ ksba_isotime_t this_update; ksba_isotime_t next_update; ksba_isotime_t last_refresh; /* Use for the force_crl_refresh feature. */ char *crl_number; char *authority_issuer; char *authority_serialno; struct cdb *cdb; /* The cache file handle or NULL if not open. */ unsigned int cdb_use_count; /* Current use count. */ unsigned int cdb_lru_count; /* Used for LRU purposes. */ int dbfile_checked; /* Set to true if the dbfile_hash value has been checked one. */ }; /* Definition of the entire cache object. */ struct crl_cache_s { crl_cache_entry_t entries; }; typedef struct crl_cache_s *crl_cache_t; /* Prototypes. */ static crl_cache_entry_t find_entry (crl_cache_entry_t first, const char *issuer_hash); /* The currently loaded cache object. This isi usually initialized right at startup. */ static crl_cache_t current_cache; /* Return the current cache object or bail out if it is has not yet been initialized. */ static crl_cache_t get_current_cache (void) { if (!current_cache) log_fatal ("CRL cache has not yet been initialized\n"); return current_cache; } /* Create ae directory if it does not yet exists. Returns on success, or -1 on error. */ static int create_directory_if_needed (const char *name) { DIR *dir; char *fname; fname = make_filename (opt.homedir_cache, name, NULL); dir = opendir (fname); if (!dir) { log_info (_("creating directory `%s'\n"), fname); if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR) ) { int save_errno = errno; log_error (_("error creating directory `%s': %s\n"), fname, strerror (errno)); xfree (fname); - errno = save_errno; + gpg_err_set_errno (save_errno); return -1; } } else closedir (dir); xfree (fname); return 0; } /* Remove all files from the cache directory. If FORCE is not true, some sanity checks on the filenames are done. Return 0 if everything went fine. */ static int cleanup_cache_dir (int force) { char *dname = make_filename (opt.homedir_cache, DBDIR_D, NULL); DIR *dir; struct dirent *de; int problem = 0; if (!force) { /* Very minor sanity checks. */ if (!strcmp (dname, "~/") || !strcmp (dname, "/" )) { log_error (_("ignoring database dir `%s'\n"), dname); xfree (dname); return -1; } } dir = opendir (dname); if (!dir) { log_error (_("error reading directory `%s': %s\n"), dname, strerror (errno)); xfree (dname); return -1; } while ((de = readdir (dir))) { if (strcmp (de->d_name, "." ) && strcmp (de->d_name, "..")) { char *cdbname = make_filename (dname, de->d_name, NULL); int okay; struct stat sbuf; if (force) okay = 1; else okay = (!stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode)); if (okay) { log_info (_("removing cache file `%s'\n"), cdbname); if (unlink (cdbname)) { log_error ("failed to remove `%s': %s\n", cdbname, strerror (errno)); problem = -1; } } else log_info (_("not removing file `%s'\n"), cdbname); xfree (cdbname); } } xfree (dname); closedir (dir); return problem; } /* Read the next line from the file FP and return the line in an malloced buffer. Return NULL on error or EOF. There is no limitation os the line length. The trailing linefeed has been removed, the function will read the last line of a file, even if that is not terminated by a LF. */ static char * -next_line_from_file (FILE *fp, gpg_error_t *r_err) +next_line_from_file (estream_t fp, gpg_error_t *r_err) { char buf[300]; char *largebuf = NULL; size_t buflen; size_t len = 0; unsigned char *p; int c; char *tmpbuf; *r_err = 0; p = buf; buflen = sizeof buf - 1; - while ((c=getc (fp)) != EOF && c != '\n') + while ((c=es_getc (fp)) != EOF && c != '\n') { if (len >= buflen) { if (!largebuf) { buflen += 1024; largebuf = xtrymalloc ( buflen + 1 ); if (!largebuf) { *r_err = gpg_error_from_syserror (); return NULL; } memcpy (largebuf, buf, len); } else { buflen += 1024; tmpbuf = xtryrealloc (largebuf, buflen + 1); if (!tmpbuf) { *r_err = gpg_error_from_syserror (); xfree (largebuf); return NULL; } largebuf = tmpbuf; } p = largebuf; } p[len++] = c; } if (c == EOF && !len) return NULL; p[len] = 0; if (largebuf) tmpbuf = xtryrealloc (largebuf, len+1); else tmpbuf = xtrystrdup (buf); if (!tmpbuf) { *r_err = gpg_error_from_syserror (); xfree (largebuf); } return tmpbuf; } /* Release one cache entry. */ static void release_one_cache_entry (crl_cache_entry_t entry) { if (entry) { if (entry->cdb) { int fd = cdb_fileno (entry->cdb); cdb_free (entry->cdb); xfree (entry->cdb); if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); } xfree (entry->release_ptr); xfree (entry->check_trust_anchor); xfree (entry); } } /* Release the CACHE object. */ static void release_cache (crl_cache_t cache) { crl_cache_entry_t entry, entry2; if (!cache) return; for (entry = cache->entries; entry; entry = entry2) { entry2 = entry->next; release_one_cache_entry (entry); } cache->entries = NULL; xfree (cache); } /* Open the dir file FNAME or create a new one if it does not yet exist. */ -static FILE * +static estream_t open_dir_file (const char *fname) { - FILE *fp; + estream_t fp; - fp = fopen (fname, "r"); + fp = es_fopen (fname, "r"); if (!fp) { log_error (_("failed to open cache dir file `%s': %s\n"), fname, strerror (errno)); /* Make sure that the directory exists, try to create if otherwise. */ if (create_directory_if_needed (NULL) || create_directory_if_needed (DBDIR_D)) return NULL; - fp = fopen (fname, "w"); + fp = es_fopen (fname, "w"); if (!fp) { log_error (_("error creating new cache dir file `%s': %s\n"), fname, strerror (errno)); return NULL; } - fprintf (fp, "v:%d:\n", DBDIRVERSION); - if (ferror (fp)) + es_fprintf (fp, "v:%d:\n", DBDIRVERSION); + if (es_ferror (fp)) { log_error (_("error writing new cache dir file `%s': %s\n"), fname, strerror (errno)); - fclose (fp); + es_fclose (fp); return NULL; } - if (fclose (fp)) + if (es_fclose (fp)) { log_error (_("error closing new cache dir file `%s': %s\n"), fname, strerror (errno)); return NULL; } log_info (_("new cache dir file `%s' created\n"), fname); - fp = fopen (fname, "r"); + fp = es_fopen (fname, "r"); if (!fp) { log_error (_("failed to re-open cache dir file `%s': %s\n"), fname, strerror (errno)); return NULL; } } return fp; } /* Helper for open_dir. */ static gpg_error_t -check_dir_version (FILE **fpadr, const char *fname, +check_dir_version (estream_t *fpadr, const char *fname, unsigned int *lineno, int cleanup_on_mismatch) { char *line; gpg_error_t lineerr = 0; - FILE *fp = *fpadr; + estream_t fp = *fpadr; int created = 0; retry: while ((line = next_line_from_file (fp, &lineerr))) { ++*lineno; if (*line == 'v' && line[1] == ':') break; else if (*line != '#') { log_error (_("first record of `%s' is not the version\n"), fname); xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } xfree (line); } if (lineerr) return lineerr; if (strtol (line+2, NULL, 10) != DBDIRVERSION) { if (!created && cleanup_on_mismatch) { log_error (_("old version of cache directory - cleaning up\n")); - fclose (fp); + es_fclose (fp); *fpadr = NULL; if (!cleanup_cache_dir (1)) { *lineno = 0; fp = *fpadr = open_dir_file (fname); if (!fp) { xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } created = 1; goto retry; } } log_error (_("old version of cache directory - giving up\n")); xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } xfree (line); return 0; } /* Open the dir file and read in all available information. Store that in a newly allocated cache object and return that if everything worked out fine. Create the cache directory and the dir if it does not yet exist. Remove all files in that directory if the version does not match. */ static gpg_error_t open_dir (crl_cache_t *r_cache) { crl_cache_t cache; char *fname; char *line = NULL; gpg_error_t lineerr = 0; - FILE *fp; + estream_t fp; crl_cache_entry_t entry, *entrytail; unsigned int lineno; gpg_error_t err = 0; int anyerr = 0; cache = xtrycalloc (1, sizeof *cache); if (!cache) return gpg_error_from_syserror (); fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); lineno = 0; fp = open_dir_file (fname); if (!fp) { err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } err = check_dir_version (&fp, fname, &lineno, 1); if (err) goto leave; /* Read in all supported entries from the dir file. */ cache->entries = NULL; entrytail = &cache->entries; xfree (line); while ((line = next_line_from_file (fp, &lineerr))) { int fieldno; char *p, *endp; lineno++; if ( *line == 'c' || *line == 'u' || *line == 'i' ) { entry = xtrycalloc (1, sizeof *entry); if (!entry) { err = gpg_error_from_syserror (); goto leave; } entry->lineno = lineno; entry->release_ptr = line; if (*line == 'i') { entry->invalid = atoi (line+1); if (entry->invalid < 1) entry->invalid = 1; } else if (*line == 'u') entry->user_trust_req = 1; for (fieldno=1, p = line; p; p = endp, fieldno++) { endp = strchr (p, ':'); if (endp) *endp++ = '\0'; switch (fieldno) { case 1: /* record type */ break; case 2: entry->issuer_hash = p; break; case 3: entry->issuer = unpercent_string (p); break; case 4: entry->url = unpercent_string (p); break; case 5: strncpy (entry->this_update, p, 15); break; case 6: strncpy (entry->next_update, p, 15); break; case 7: entry->dbfile_hash = p; break; case 8: if (*p) entry->crl_number = p; break; case 9: if (*p) entry->authority_issuer = unpercent_string (p); break; case 10: if (*p) entry->authority_serialno = unpercent_string (p); break; case 11: if (*p) entry->check_trust_anchor = xtrystrdup (p); break; default: if (*p) log_info (_("extra field detected in crl record of " "`%s' line %u\n"), fname, lineno); break; } } if (!entry->issuer_hash) { log_info (_("invalid line detected in `%s' line %u\n"), fname, lineno); xfree (entry); entry = NULL; } else if (find_entry (cache->entries, entry->issuer_hash)) { /* Fixme: The duplicate checking used is not very effective for large numbers of issuers. */ log_info (_("duplicate entry detected in `%s' line %u\n"), fname, lineno); xfree (entry); entry = NULL; } else { line = NULL; *entrytail = entry; entrytail = &entry->next; } } else if (*line == '#') ; else log_info (_("unsupported record type in `%s' line %u skipped\n"), fname, lineno); if (line) xfree (line); } if (lineerr) { err = lineerr; log_error (_("error reading `%s': %s\n"), fname, gpg_strerror (err)); goto leave; } - if (ferror (fp)) + if (es_ferror (fp)) { log_error (_("error reading `%s': %s\n"), fname, strerror (errno)); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } /* Now do some basic checks on the data. */ for (entry = cache->entries; entry; entry = entry->next) { assert (entry->lineno); if (strlen (entry->issuer_hash) != 40) { anyerr++; log_error (_("invalid issuer hash in `%s' line %u\n"), fname, entry->lineno); } else if ( !*entry->issuer ) { anyerr++; log_error (_("no issuer DN in `%s' line %u\n"), fname, entry->lineno); } else if ( check_isotime (entry->this_update) || check_isotime (entry->next_update)) { anyerr++; log_error (_("invalid timestamp in `%s' line %u\n"), fname, entry->lineno); } /* Checks not leading to an immediate fail. */ if (strlen (entry->dbfile_hash) != 32) log_info (_("WARNING: invalid cache file hash in `%s' line %u\n"), fname, entry->lineno); } if (anyerr) { log_error (_("detected errors in cache dir file\n")); log_info (_("please check the reason and manually delete that file\n")); err = gpg_error (GPG_ERR_CONFIGURATION); } leave: - if (fp) - fclose (fp); + es_fclose (fp); xfree (line); xfree (fname); if (err) { release_cache (cache); cache = NULL; } *r_cache = cache; return err; } static void -write_percented_string (const char *s, FILE *fp) +write_percented_string (const char *s, estream_t fp) { for (; *s; s++) if (*s == ':') - fputs ("%3A", fp); + es_fputs ("%3A", fp); else if (*s == '\n') - fputs ("%0A", fp); + es_fputs ("%0A", fp); else if (*s == '\r') - fputs ("%0D", fp); + es_fputs ("%0D", fp); else - putc (*s, fp); + es_putc (*s, fp); } static void -write_dir_line_crl (FILE *fp, crl_cache_entry_t e) +write_dir_line_crl (estream_t fp, crl_cache_entry_t e) { if (e->invalid) - fprintf (fp, "i%d", e->invalid); + es_fprintf (fp, "i%d", e->invalid); else if (e->user_trust_req) - putc ('u', fp); + es_putc ('u', fp); else - putc ('c', fp); - putc (':', fp); - fputs (e->issuer_hash, fp); - putc (':', fp); + es_putc ('c', fp); + es_putc (':', fp); + es_fputs (e->issuer_hash, fp); + es_putc (':', fp); write_percented_string (e->issuer, fp); - putc (':', fp); + es_putc (':', fp); write_percented_string (e->url, fp); - putc (':', fp); - fwrite (e->this_update, 15, 1, fp); - putc (':', fp); - fwrite (e->next_update, 15, 1, fp); - putc (':', fp); - fputs (e->dbfile_hash, fp); - putc (':', fp); + es_putc (':', fp); + es_fwrite (e->this_update, 15, 1, fp); + es_putc (':', fp); + es_fwrite (e->next_update, 15, 1, fp); + es_putc (':', fp); + es_fputs (e->dbfile_hash, fp); + es_putc (':', fp); if (e->crl_number) - fputs (e->crl_number, fp); - putc (':', fp); + es_fputs (e->crl_number, fp); + es_putc (':', fp); if (e->authority_issuer) write_percented_string (e->authority_issuer, fp); - putc (':', fp); + es_putc (':', fp); if (e->authority_serialno) - fputs (e->authority_serialno, fp); - putc (':', fp); + es_fputs (e->authority_serialno, fp); + es_putc (':', fp); if (e->check_trust_anchor && e->user_trust_req) - fputs (e->check_trust_anchor, fp); - putc ('\n', fp); + es_fputs (e->check_trust_anchor, fp); + es_putc ('\n', fp); } /* Update the current dir file using the cache. */ static gpg_error_t update_dir (crl_cache_t cache) { char *fname = NULL; char *tmpfname = NULL; char *line = NULL; gpg_error_t lineerr = 0; - FILE *fp, *fpout = NULL; + estream_t fp; + estream_t fpout = NULL; crl_cache_entry_t e; unsigned int lineno; gpg_error_t err = 0; fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); /* Fixme: Take an update file lock here. */ for (e= cache->entries; e; e = e->next) e->mark = 1; lineno = 0; - fp = fopen (fname, "r"); + fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_errno (errno); log_error (_("failed to open cache dir file `%s': %s\n"), fname, strerror (errno)); goto leave; } err = check_dir_version (&fp, fname, &lineno, 0); if (err) goto leave; - rewind (fp); + es_rewind (fp); lineno = 0; /* Create a temporary DIR file. */ { char *tmpbuf, *p; const char *nodename; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; #endif #ifdef HAVE_W32_SYSTEM nodename = "unknown"; #else if (uname (&utsbuf)) nodename = "unknown"; else nodename = utsbuf.nodename; #endif estream_asprintf (&tmpbuf, "DIR-tmp-%s-%u-%p.txt.tmp", nodename, (unsigned int)getpid (), &tmpbuf); if (!tmpbuf) { err = gpg_error_from_errno (errno); log_error (_("failed to create temporary cache dir file `%s': %s\n"), tmpfname, strerror (errno)); goto leave; } for (p=tmpbuf; *p; p++) if (*p == '/') *p = '.'; tmpfname = make_filename (opt.homedir_cache, DBDIR_D, tmpbuf, NULL); xfree (tmpbuf); } - fpout = fopen (tmpfname, "w"); + fpout = es_fopen (tmpfname, "w"); if (!fpout) { err = gpg_error_from_errno (errno); log_error (_("failed to create temporary cache dir file `%s': %s\n"), tmpfname, strerror (errno)); goto leave; } while ((line = next_line_from_file (fp, &lineerr))) { lineno++; if (*line == 'c' || *line == 'u' || *line == 'i') { /* Extract the issuer hash field. */ char *fieldp, *endp; fieldp = strchr (line, ':'); endp = fieldp? strchr (++fieldp, ':') : NULL; if (endp) { /* There should be no percent within the issuer hash field, thus we can compare it pretty easily. */ *endp = 0; e = find_entry ( cache->entries, fieldp); *endp = ':'; /* Restore orginal line. */ if (e && e->deleted) { /* Marked for deletion, so don't write it. */ e->mark = 0; } else if (e) { /* Yep, this is valid entry we know about; write it out */ write_dir_line_crl (fpout, e); e->mark = 0; } else { /* We ignore entries we don't have in our cache because they may have been added in the meantime by other instances of dirmngr. */ - fprintf (fpout, "# Next line added by " - "another process; our pid is %lu\n", - (unsigned long)getpid ()); - fputs (line, fpout); - putc ('\n', fpout); + es_fprintf (fpout, "# Next line added by " + "another process; our pid is %lu\n", + (unsigned long)getpid ()); + es_fputs (line, fpout); + es_putc ('\n', fpout); } } else { - fputs ("# Invalid line detected: ", fpout); - fputs (line, fpout); - putc ('\n', fpout); + es_fputs ("# Invalid line detected: ", fpout); + es_fputs (line, fpout); + es_putc ('\n', fpout); } } else { /* Write out all non CRL lines as they are. */ - fputs (line, fpout); - putc ('\n', fpout); + es_fputs (line, fpout); + es_putc ('\n', fpout); } xfree (line); } - if (!ferror (fp) && !ferror (fpout) && !lineerr) + if (!es_ferror (fp) && !ferror (es_fpout) && !lineerr) { /* Write out the remaining entries. */ for (e= cache->entries; e; e = e->next) if (e->mark) { if (!e->deleted) write_dir_line_crl (fpout, e); e->mark = 0; } } if (lineerr) { err = lineerr; log_error (_("error reading `%s': %s\n"), fname, gpg_strerror (err)); goto leave; } - if (ferror (fp)) + if (es_ferror (fp)) { err = gpg_error_from_errno (errno); log_error (_("error reading `%s': %s\n"), fname, strerror (errno)); } - if (ferror (fpout)) + if (es_ferror (fpout)) { err = gpg_error_from_errno (errno); log_error (_("error writing `%s': %s\n"), tmpfname, strerror (errno)); } if (err) goto leave; /* Rename the files. */ - fclose (fp); + es_fclose (fp); fp = NULL; - if (fclose (fpout)) + if (es_fclose (fpout)) { err = gpg_error_from_errno (errno); log_error (_("error closing `%s': %s\n"), tmpfname, strerror (errno)); goto leave; } fpout = NULL; #ifdef HAVE_W32_SYSTEM /* No atomic mv on W32 systems. */ unlink (fname); #endif if (rename (tmpfname, fname)) { err = gpg_error_from_errno (errno); log_error (_("error renaming `%s' to `%s': %s\n"), tmpfname, fname, strerror (errno)); goto leave; } leave: /* Fixme: Relinquish update lock. */ xfree (line); - if (fp) - fclose (fp); + es_fclose (fp); xfree (fname); if (fpout) { - fclose (fpout); + es_fclose (fpout); if (err && tmpfname) - remove (tmpfname); + gnupg_remove (tmpfname); } xfree (tmpfname); return err; } /* Create the filename for the cache file from the 40 byte ISSUER_HASH string. Caller must release the return string. */ static char * make_db_file_name (const char *issuer_hash) { char bname[50]; assert (strlen (issuer_hash) == 40); memcpy (bname, "crl-", 4); memcpy (bname + 4, issuer_hash, 40); strcpy (bname + 44, ".db"); return make_filename (opt.homedir_cache, DBDIR_D, bname, NULL); } /* Hash the file FNAME and return the MD5 digest in MD5BUFFER. The caller must allocate MD%buffer wityh at least 16 bytes. Returns 0 on success. */ static int hash_dbfile (const char *fname, unsigned char *md5buffer) { - FILE *fp; + estream_t fp; char *buffer; size_t n; gcry_md_hd_t md5; gpg_err_code_t err; buffer = xtrymalloc (65536); - fp = buffer? fopen (fname, "rb") : NULL; + fp = buffer? es_fopen (fname, "rb") : NULL; if (!fp) { log_error (_("can't hash `%s': %s\n"), fname, strerror (errno)); xfree (buffer); return -1; } err = gcry_md_open (&md5, GCRY_MD_MD5, 0); if (err) { log_error (_("error setting up MD5 hash context: %s\n"), gpg_strerror (err)); xfree (buffer); - fclose (fp); + es_fclose (fp); return -1; } /* We better hash some information about the cache file layout in. */ sprintf (buffer, "%.100s/%.100s:%d", DBDIR_D, DBDIRFILE, DBDIRVERSION); gcry_md_write (md5, buffer, strlen (buffer)); for (;;) { - n = fread (buffer, 1, 65536, fp); - if (n < 65536 && ferror (fp)) + n = es_fread (buffer, 1, 65536, fp); + if (n < 65536 && es_ferror (fp)) { log_error (_("error hashing `%s': %s\n"), fname, strerror (errno)); xfree (buffer); - fclose (fp); + es_fclose (fp); gcry_md_close (md5); return -1; } if (!n) break; gcry_md_write (md5, buffer, n); } - fclose (fp); + es_fclose (fp); xfree (buffer); gcry_md_final (md5); memcpy (md5buffer, gcry_md_read (md5, GCRY_MD_MD5), 16); gcry_md_close (md5); return 0; } /* Compare the file FNAME against the dexified MD5 hash MD5HASH and return 0 if they match. */ static int check_dbfile (const char *fname, const char *md5hexvalue) { unsigned char buffer1[16], buffer2[16]; if (strlen (md5hexvalue) != 32) { log_error (_("invalid formatted checksum for `%s'\n"), fname); return -1; } unhexify (buffer1, md5hexvalue); if (hash_dbfile (fname, buffer2)) return -1; return memcmp (buffer1, buffer2, 16); } /* Open the cache file for ENTRY. This function implements a caching strategy and might close unused cache files. It is required to use unlock_db_file after using the file. */ static struct cdb * lock_db_file (crl_cache_t cache, crl_cache_entry_t entry) { char *fname; int fd; int open_count; crl_cache_entry_t e; if (entry->cdb) { entry->cdb_use_count++; return entry->cdb; } for (open_count = 0, e = cache->entries; e; e = e->next) { if (e->cdb) open_count++; /* log_debug ("CACHE: cdb=%p use_count=%u lru_count=%u\n", */ /* e->cdb,e->cdb_use_count,e->cdb_lru_count); */ } /* If there are too many file open, find the least recent used DB file and close it. Note that for Pth thread safeness we need to use a loop here. */ while (open_count >= MAX_OPEN_DB_FILES ) { crl_cache_entry_t last_e = NULL; unsigned int last_lru = (unsigned int)(-1); for (e = cache->entries; e; e = e->next) if (e->cdb && !e->cdb_use_count && e->cdb_lru_count < last_lru) { last_lru = e->cdb_lru_count; last_e = e; } if (!last_e) { log_error (_("too many open cache files; can't open anymore\n")); return NULL; } /* log_debug ("CACHE: closing file at cdb=%p\n", last_e->cdb); */ fd = cdb_fileno (last_e->cdb); cdb_free (last_e->cdb); xfree (last_e->cdb); last_e->cdb = NULL; if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); open_count--; } fname = make_db_file_name (entry->issuer_hash); if (opt.verbose) log_info (_("opening cache file `%s'\n"), fname ); if (!entry->dbfile_checked) { if (!check_dbfile (fname, entry->dbfile_hash)) entry->dbfile_checked = 1; /* Note, in case of an error we don't print an error here but let require the caller to do that check. */ } entry->cdb = xtrycalloc (1, sizeof *entry->cdb); if (!entry->cdb) { xfree (fname); return NULL; } fd = open (fname, O_RDONLY); if (fd == -1) { log_error (_("error opening cache file `%s': %s\n"), fname, strerror (errno)); xfree (entry->cdb); entry->cdb = NULL; xfree (fname); return NULL; } if (cdb_init (entry->cdb, fd)) { log_error (_("error initializing cache file `%s' for reading: %s\n"), fname, strerror (errno)); xfree (entry->cdb); entry->cdb = NULL; close (fd); xfree (fname); return NULL; } xfree (fname); entry->cdb_use_count = 1; entry->cdb_lru_count = 0; return entry->cdb; } /* Unlock a cache file, so that it can be reused. */ static void unlock_db_file (crl_cache_t cache, crl_cache_entry_t entry) { if (!entry->cdb) log_error (_("calling unlock_db_file on a closed file\n")); else if (!entry->cdb_use_count) log_error (_("calling unlock_db_file on an unlocked file\n")); else { entry->cdb_use_count--; entry->cdb_lru_count++; } /* If the entry was marked for deletion in the meantime do it now. We do this for the sake of Pth thread safeness. */ if (!entry->cdb_use_count && entry->deleted) { crl_cache_entry_t eprev, enext; enext = entry->next; for (eprev = cache->entries; eprev && eprev->next != entry; eprev = eprev->next) ; assert (eprev); if (eprev == cache->entries) cache->entries = enext; else eprev->next = enext; } } /* Find ISSUER_HASH in our cache FIRST. This may be used to enumerate the linked list we use to keep the CRLs of an issuer. */ static crl_cache_entry_t find_entry (crl_cache_entry_t first, const char *issuer_hash) { while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash))) first = first->next; return first; } /* Create a new CRL cache. This fucntion is usually called only once. never fail. */ void crl_cache_init(void) { crl_cache_t cache = NULL; gpg_error_t err; if (current_cache) { log_error ("crl cache has already been initialized - not doing twice\n"); return; } err = open_dir (&cache); if (err) log_fatal (_("failed to create a new cache object: %s\n"), gpg_strerror (err)); current_cache = cache; } /* Remove the cache information and all its resources. Note that we still keep the cache on disk. */ void crl_cache_deinit (void) { if (current_cache) { release_cache (current_cache); current_cache = NULL; } } /* Delete the cache from disk. Return 0 on success.*/ int crl_cache_flush (void) { int rc; rc = cleanup_cache_dir (0)? -1 : 0; return rc; } /* Check whether the certificate identified by ISSUER_HASH and SN/SNLEN is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ static crl_cache_result_t cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const unsigned char *sn, size_t snlen, int force_refresh) { crl_cache_t cache = get_current_cache (); crl_cache_result_t retval; struct cdb *cdb; int rc; crl_cache_entry_t entry; gnupg_isotime_t current_time; size_t n; (void)ctrl; entry = find_entry (cache->entries, issuer_hash); if (!entry) { log_info (_("no CRL available for issuer id %s\n"), issuer_hash ); return CRL_CACHE_DONTKNOW; } gnupg_get_isotime (current_time); if (strcmp (entry->next_update, current_time) < 0 ) { log_info (_("cached CRL for issuer id %s too old; update required\n"), issuer_hash); return CRL_CACHE_DONTKNOW; } if (force_refresh) { gnupg_isotime_t tmptime; if (*entry->last_refresh) { gnupg_copy_time (tmptime, entry->last_refresh); add_seconds_to_isotime (tmptime, 30 * 60); if (strcmp (tmptime, current_time) < 0 ) { log_info (_("force-crl-refresh active and %d minutes passed for" " issuer id %s; update required\n"), 30, issuer_hash); return CRL_CACHE_DONTKNOW; } } else { log_info (_("force-crl-refresh active for" " issuer id %s; update required\n"), issuer_hash); return CRL_CACHE_DONTKNOW; } } if (entry->invalid) { log_info (_("available CRL for issuer ID %s can't be used\n"), issuer_hash); return CRL_CACHE_CANTUSE; } cdb = lock_db_file (cache, entry); if (!cdb) return CRL_CACHE_DONTKNOW; /* Hmmm, not the best error code. */ if (!entry->dbfile_checked) { log_error (_("cached CRL for issuer id %s tampered; we need to update\n") , issuer_hash); unlock_db_file (cache, entry); return CRL_CACHE_DONTKNOW; } rc = cdb_find (cdb, sn, snlen); if (rc == 1) { n = cdb_datalen (cdb); if (n != 16) { log_error (_("WARNING: invalid cache record length for S/N ")); log_printhex ("", sn, snlen); } else if (opt.verbose) { unsigned char record[16]; char *tmp = hexify_data (sn, snlen); if (cdb_read (cdb, record, n, cdb_datapos (cdb))) log_error (_("problem reading cache record for S/N %s: %s\n"), tmp, strerror (errno)); else log_info (_("S/N %s is not valid; reason=%02X date=%.15s\n"), tmp, *record, record+1); xfree (tmp); } retval = CRL_CACHE_INVALID; } else if (!rc) { if (opt.verbose) { char *serialno = hexify_data (sn, snlen); log_info (_("S/N %s is valid, it is not listed in the CRL\n"), serialno ); xfree (serialno); } retval = CRL_CACHE_VALID; } else { log_error (_("error getting data from cache file: %s\n"), strerror (errno)); retval = CRL_CACHE_DONTKNOW; } if (entry->user_trust_req && (retval == CRL_CACHE_VALID || retval == CRL_CACHE_INVALID)) { if (!entry->check_trust_anchor) { log_error ("inconsistent data on user trust check\n"); retval = CRL_CACHE_CANTUSE; } else if (get_istrusted_from_client (ctrl, entry->check_trust_anchor)) { if (opt.verbose) log_info ("no system trust and client does not trust either\n"); retval = CRL_CACHE_CANTUSE; } else { /* Okay, the CRL is considered valid by the client and thus we can return the result as is. */ } } unlock_db_file (cache, entry); return retval; } /* Check whether the certificate identified by ISSUER_HASH and SERIALNO is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *serialno, int force_refresh) { crl_cache_result_t result; unsigned char snbuf_buffer[50]; unsigned char *snbuf; size_t n; n = strlen (serialno)/2+1; if (n < sizeof snbuf_buffer - 1) snbuf = snbuf_buffer; else { snbuf = xtrymalloc (n); if (!snbuf) return CRL_CACHE_DONTKNOW; } n = unhexify (snbuf, serialno); result = cache_isvalid (ctrl, issuer_hash, snbuf, n, force_refresh); if (snbuf != snbuf_buffer) xfree (snbuf); return result; } /* Check whether the certificate CERT is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, int force_refresh) { gpg_error_t err; crl_cache_result_t result; unsigned char issuerhash[20]; char issuerhash_hex[41]; ksba_sexp_t serial; unsigned char *sn; size_t snlen; char *endp, *tmp; int i; /* Compute the hash value of the issuer name. */ tmp = ksba_cert_get_issuer (cert, 0); if (!tmp) { log_error ("oops: issuer missing in certificate\n"); return gpg_error (GPG_ERR_INV_CERT_OBJ); } gcry_md_hash_buffer (GCRY_MD_SHA1, issuerhash, tmp, strlen (tmp)); xfree (tmp); for (i=0,tmp=issuerhash_hex; i < 20; i++, tmp += 2) sprintf (tmp, "%02X", issuerhash[i]); /* Get the serial number. */ serial = ksba_cert_get_serial (cert); if (!serial) { log_error ("oops: S/N missing in certificate\n"); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn = serial; if (*sn != '(') { log_error ("oops: invalid S/N\n"); xfree (serial); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn++; snlen = strtoul (sn, &endp, 10); sn = endp; if (*sn != ':') { log_error ("oops: invalid S/N\n"); xfree (serial); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn++; /* Check the cache. */ result = cache_isvalid (ctrl, issuerhash_hex, sn, snlen, force_refresh); switch (result) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("cache_isvalid returned invalid status code %d\n", result); } xfree (serial); return err; } /* Prepare a hash context for the signature verification. Input is the CRL and the output is the hash context MD as well as the uses algorithm identifier ALGO. */ static gpg_error_t start_sig_check (ksba_crl_t crl, gcry_md_hd_t *md, int *algo) { gpg_error_t err; const char *algoid; algoid = ksba_crl_get_digest_algo (crl); *algo = gcry_md_map_name (algoid); if (!*algo) { log_error (_("unknown hash algorithm `%s'\n"), algoid? algoid:"?"); return gpg_error (GPG_ERR_DIGEST_ALGO); } err = gcry_md_open (md, *algo, 0); if (err) { log_error (_("gcry_md_open for algorithm %d failed: %s\n"), *algo, gcry_strerror (err)); return err; } if (DBG_HASHING) gcry_md_debug (*md, "hash.cert"); ksba_crl_set_hash_function (crl, HASH_FNC, *md); return 0; } /* Finish a hash context and verify the signature. This function should return 0 on a good signature, GPG_ERR_BAD_SIGNATURE if the signature does not verify or any other error code. CRL is the CRL object we are working on, MD the hash context and ISSUER_CERT the certificate of the CRL issuer. This function closes MD. */ static gpg_error_t finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo, ksba_cert_t issuer_cert) { gpg_error_t err; ksba_sexp_t sigval = NULL, pubkey = NULL; const char *s; char algoname[50]; size_t n; gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; unsigned int i; /* This also stops debugging on the MD. */ gcry_md_final (md); /* Get and convert the signature value. */ sigval = ksba_crl_get_sig_val (crl); n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); if (!n) { log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&s_sig, NULL, sigval, n); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Get and convert the public key for the issuer certificate. */ if (DBG_X509) dump_cert ("crl_issuer_cert", issuer_cert); pubkey = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (pubkey, 0, NULL, NULL); if (!n) { log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&s_pkey, NULL, pubkey, n); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Create an S-expression with the actual hash value. */ s = gcry_md_algo_name (algo); for (i = 0; *s && i < sizeof(algoname) - 1; s++, i++) algoname[i] = ascii_tolower (*s); algoname[i] = 0; err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", algoname, gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo)); if (err) { log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Pass this on to the signature verification. */ err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err)); leave: xfree (sigval); xfree (pubkey); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); gcry_md_close (md); return err; } /* Call this to match a start_sig_check that can not be completed normally. */ static void abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md) { (void)crl; gcry_md_close (md); } /* Workhorse of the CRL loading machinery. The CRL is read using the CRL object and stored in the data base file DB with the name FNAME (only used for printing error messages). That DB should be a temporary one and not the actual one. If the function fails the caller should delete this temporary database file. CTRL is required to retrieve certificates using the general dirmngr callback service. R_CRLISSUER returns an allocated string with the crl-issuer DN, THIS_UPDATE and NEXT_UPDATE are filled with the corresponding data from the CRL. Note that these values might get set even if the CRL processing fails at a later step; thus the caller should free *R_ISSUER even if the function returns with an error. R_TRUST_ANCHOR is set on exit to NULL or a string with the hexified fingerprint of the root certificate, if checking this certificate for trustiness is required. */ static int crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, struct cdb_make *cdb, const char *fname, char **r_crlissuer, ksba_isotime_t thisupdate, ksba_isotime_t nextupdate, char **r_trust_anchor) { gpg_error_t err; ksba_stop_reason_t stopreason; ksba_cert_t crlissuer_cert = NULL; gcry_md_hd_t md = NULL; int algo = 0; size_t n; (void)fname; *r_crlissuer = NULL; *thisupdate = *nextupdate = 0; *r_trust_anchor = NULL; /* Start of the KSBA parser loop. */ do { err = ksba_crl_parse (crl, &stopreason); if (err) { log_error (_("ksba_crl_parse failed: %s\n"), gpg_strerror (err) ); goto failure; } switch (stopreason) { case KSBA_SR_BEGIN_ITEMS: { if (start_sig_check (crl, &md, &algo )) goto failure; err = ksba_crl_get_update_times (crl, thisupdate, nextupdate); if (err) { log_error (_("error getting update times of CRL: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); goto failure; } if (opt.verbose || !*nextupdate) log_info (_("update times of this CRL: this=%s next=%s\n"), thisupdate, nextupdate); if (!*nextupdate) { log_info (_("nextUpdate not given; " "assuming a validity period of one day\n")); gnupg_copy_time (nextupdate, thisupdate); add_seconds_to_isotime (nextupdate, 86400); } } break; case KSBA_SR_GOT_ITEM: { ksba_sexp_t serial; const unsigned char *p; ksba_isotime_t rdate; ksba_crl_reason_t reason; int rc; unsigned char record[1+15]; err = ksba_crl_get_item (crl, &serial, rdate, &reason); if (err) { log_error (_("error getting CRL item: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); ksba_free (serial); goto failure; } p = serial_to_buffer (serial, &n); if (!p) BUG (); record[0] = (reason & 0xff); memcpy (record+1, rdate, 15); rc = cdb_make_add (cdb, p, n, record, 1+15); if (rc) { err = gpg_error_from_errno (errno); log_error (_("error inserting item into " "temporary cache file: %s\n"), strerror (errno)); goto failure; } ksba_free (serial); } break; case KSBA_SR_END_ITEMS: break; case KSBA_SR_READY: { char *crlissuer; ksba_name_t authid; ksba_sexp_t authidsn; ksba_sexp_t keyid; /* We need to look for the issuer only after having read all items. The issuer itselfs comes before the items but the optional authorityKeyIdentifier comes after the items. */ err = ksba_crl_get_issuer (crl, &crlissuer); if( err ) { log_error (_("no CRL issuer found in CRL: %s\n"), gpg_strerror (err) ); err = gpg_error (GPG_ERR_INV_CRL); goto failure; } /* Note: This should be released by ksba_free, not xfree. May need a memory reallocation dance. */ *r_crlissuer = crlissuer; /* (Do it here so we don't need to free it later) */ if (!ksba_crl_get_auth_key_id (crl, &keyid, &authid, &authidsn)) { const char *s; if (opt.verbose) log_info (_("locating CRL issuer certificate by " "authorityKeyIdentifier\n")); s = ksba_name_enum (authid, 0); if (s && *authidsn) crlissuer_cert = find_cert_bysn (ctrl, s, authidsn); if (!crlissuer_cert && keyid) crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, keyid); if (!crlissuer_cert) { log_info ("CRL issuer certificate "); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } if (authidsn) { log_printf ("(#"); dump_serial (authidsn); log_printf ("/"); dump_string (s); log_printf (") "); } log_printf ("not found\n"); } ksba_name_release (authid); xfree (authidsn); xfree (keyid); } else crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, NULL); err = 0; if (!crlissuer_cert) { err = gpg_error (GPG_ERR_MISSING_CERT); goto failure; } err = finish_sig_check (crl, md, algo, crlissuer_cert); if (err) { log_error (_("CRL signature verification failed: %s\n"), gpg_strerror (err)); goto failure; } md = NULL; err = validate_cert_chain (ctrl, crlissuer_cert, NULL, VALIDATE_MODE_CRL_RECURSIVE, r_trust_anchor); if (err) { log_error (_("error checking validity of CRL " "issuer certificate: %s\n"), gpg_strerror (err)); goto failure; } } break; default: log_debug ("crl_parse_insert: unknown stop reason\n"); err = gpg_error (GPG_ERR_BUG); goto failure; } } while (stopreason != KSBA_SR_READY); assert (!err); failure: if (md) abort_sig_check (crl, md); ksba_cert_release (crlissuer_cert); return err; } /* Return the crlNumber extension as an allocated hex string or NULL if there is none. */ static char * get_crl_number (ksba_crl_t crl) { gpg_error_t err; ksba_sexp_t number; char *string; err = ksba_crl_get_crl_number (crl, &number); if (err) return NULL; string = serial_hex (number); ksba_free (number); return string; } /* Return the authorityKeyIdentifier or NULL if it is not available. The issuer name may consists of several parts - they are delimted by 0x01. */ static char * get_auth_key_id (ksba_crl_t crl, char **serialno) { gpg_error_t err; ksba_name_t name; ksba_sexp_t sn; int idx; const char *s; char *string; size_t length; *serialno = NULL; err = ksba_crl_get_auth_key_id (crl, NULL, &name, &sn); if (err) return NULL; *serialno = serial_hex (sn); ksba_free (sn); if (!name) return xstrdup (""); length = 0; for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); length += strlen (p?p:s) + 1; xfree (p); } string = xtrymalloc (length+1); if (string) { *string = 0; for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); if (*string) strcat (string, "\x01"); strcat (string, p?p:s); xfree (p); } } ksba_name_release (name); return string; } /* Insert the CRL retrieved using URL into the cache specified by CACHE. The CRL itself will be read from the stream FP and is expected in binary format. */ gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) { crl_cache_t cache = get_current_cache (); gpg_error_t err, err2; ksba_crl_t crl; char *fname = NULL; char *newfname = NULL; struct cdb_make cdb; int fd_cdb = -1; char *issuer = NULL; char *issuer_hash = NULL; ksba_isotime_t thisupdate, nextupdate; crl_cache_entry_t entry = NULL; crl_cache_entry_t e; gnupg_isotime_t current_time; char *checksum = NULL; int invalidate_crl = 0; int idx; const char *oid; int critical; char *trust_anchor = NULL; /* FIXME: We should acquire a mutex for the URL, so that we don't simultaneously enter the same CRL twice. However this needs to be interweaved with the checking function.*/ err2 = 0; err = ksba_crl_new (&crl); if (err) { log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err)); goto leave; } err = ksba_crl_set_reader (crl, reader); if ( err ) { log_error (_("ksba_crl_set_reader failed: %s\n"), gpg_strerror (err)); goto leave; } /* Create a temporary cache file to load the CRL into. */ { char *tmpfname, *p; const char *nodename; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; #endif #ifdef HAVE_W32_SYSTEM nodename = "unknown"; #else if (uname (&utsbuf)) nodename = "unknown"; else nodename = utsbuf.nodename; #endif estream_asprintf (&tmpfname, "crl-tmp-%s-%u-%p.db.tmp", nodename, (unsigned int)getpid (), &tmpfname); if (!tmpfname) { err = gpg_error_from_syserror (); goto leave; } for (p=tmpfname; *p; p++) if (*p == '/') *p = '.'; fname = make_filename (opt.homedir_cache, DBDIR_D, tmpfname, NULL); xfree (tmpfname); - if (!remove (fname)) + if (!gnupg_remove (fname)) log_info (_("removed stale temporary cache file `%s'\n"), fname); else if (errno != ENOENT) { err = gpg_error_from_syserror (); log_error (_("problem removing stale temporary cache file `%s': %s\n"), fname, gpg_strerror (err)); goto leave; } } fd_cdb = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd_cdb == -1) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary cache file `%s': %s\n"), fname, strerror (errno)); goto leave; } cdb_make_start(&cdb, fd_cdb); err = crl_parse_insert (ctrl, crl, &cdb, fname, &issuer, thisupdate, nextupdate, &trust_anchor); if (err) { log_error (_("crl_parse_insert failed: %s\n"), gpg_strerror (err)); /* Error in cleanup ignored. */ cdb_make_finish (&cdb); goto leave; } /* Finish the database. */ if (cdb_make_finish (&cdb)) { err = gpg_error_from_errno (errno); log_error (_("error finishing temporary cache file `%s': %s\n"), fname, strerror (errno)); goto leave; } if (close (fd_cdb)) { err = gpg_error_from_errno (errno); log_error (_("error closing temporary cache file `%s': %s\n"), fname, strerror (errno)); goto leave; } fd_cdb = -1; /* Create a checksum. */ { unsigned char md5buf[16]; if (hash_dbfile (fname, md5buf)) { err = gpg_error (GPG_ERR_CHECKSUM); goto leave; } checksum = hexify_data (md5buf, 16); } /* Check whether that new CRL is still not expired. */ gnupg_get_isotime (current_time); if (strcmp (nextupdate, current_time) < 0 ) { if (opt.force) log_info (_("WARNING: new CRL still too old; it expired on %s " "- loading anyway\n"), nextupdate); else { log_error (_("new CRL still too old; it expired on %s\n"), nextupdate); if (!err2) err2 = gpg_error (GPG_ERR_CRL_TOO_OLD); invalidate_crl |= 1; } } /* Check for unknown critical extensions. */ for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical, NULL, NULL)); idx++) { if (!critical || !strcmp (oid, oidstr_authorityKeyIdentifier) || !strcmp (oid, oidstr_crlNumber) ) continue; log_error (_("unknown critical CRL extension %s\n"), oid); if (!err2) err2 = gpg_error (GPG_ERR_INV_CRL); invalidate_crl |= 2; } if (gpg_err_code (err) == GPG_ERR_EOF || gpg_err_code (err) == GPG_ERR_NO_DATA ) err = 0; if (err) { log_error (_("error reading CRL extensions: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); } /* Create an hex encoded SHA-1 hash of the issuer DN to be used as the key for the cache. */ issuer_hash = hashify_data (issuer, strlen (issuer)); /* Create an ENTRY. */ entry = xtrycalloc (1, sizeof *entry); if (!entry) { err = gpg_error_from_syserror (); goto leave; } entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1 + strlen (issuer) + 1 + strlen (url) + 1 + strlen (checksum) + 1); if (!entry->release_ptr) { err = gpg_error_from_syserror (); xfree (entry); entry = NULL; goto leave; } entry->issuer_hash = entry->release_ptr; entry->issuer = stpcpy (entry->issuer_hash, issuer_hash) + 1; entry->url = stpcpy (entry->issuer, issuer) + 1; entry->dbfile_hash = stpcpy (entry->url, url) + 1; strcpy (entry->dbfile_hash, checksum); gnupg_copy_time (entry->this_update, thisupdate); gnupg_copy_time (entry->next_update, nextupdate); gnupg_copy_time (entry->last_refresh, current_time); entry->crl_number = get_crl_number (crl); entry->authority_issuer = get_auth_key_id (crl, &entry->authority_serialno); entry->invalid = invalidate_crl; entry->user_trust_req = !!trust_anchor; entry->check_trust_anchor = trust_anchor; trust_anchor = NULL; /* Check whether we already have an entry for this issuer and mark it as deleted. We better use a loop, just in case duplicates got somehow into the list. */ for (e = cache->entries; (e=find_entry (e, entry->issuer_hash)); e = e->next) e->deleted = 1; /* Rename the temporary DB to the real name. */ newfname = make_db_file_name (entry->issuer_hash); if (opt.verbose) log_info (_("creating cache file `%s'\n"), newfname); #ifdef HAVE_W32_SYSTEM unlink (newfname); #endif if (rename (fname, newfname)) { err = gpg_error_from_syserror (); log_error (_("problem renaming `%s' to `%s': %s\n"), fname, newfname, gpg_strerror (err)); goto leave; } xfree (fname); fname = NULL; /*(let the cleanup code not try to remove it)*/ /* Link the new entry in. */ entry->next = cache->entries; cache->entries = entry; entry = NULL; err = update_dir (cache); if (err) { log_error (_("updating the DIR file failed - " "cache entry will get lost with the next program start\n")); err = 0; /* Keep on running. */ } leave: release_one_cache_entry (entry); if (fd_cdb != -1) close (fd_cdb); if (fname) { - remove (fname); + gnupg_remove (fname); xfree (fname); } xfree (newfname); ksba_crl_release (crl); xfree (issuer); xfree (issuer_hash); xfree (checksum); xfree (trust_anchor); return err ? err : err2; } /* Print one cached entry E in a human readable format to stream FP. Return 0 on success. */ static gpg_error_t -list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, FILE *fp) +list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp) { struct cdb_find cdbfp; struct cdb *cdb; int rc; int warn = 0; const unsigned char *s; - fputs ("--------------------------------------------------------\n", fp ); - fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); - fprintf (fp, " Issuer:\t%s\n", e->issuer ); - fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash ); - fprintf (fp, " This Update:\t%s\n", e->this_update ); - fprintf (fp, " Next Update:\t%s\n", e->next_update ); - fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none"); - fprintf (fp, " AuthKeyId :\t%s\n", - e->authority_serialno? e->authority_serialno:"none"); + es_fputs ("--------------------------------------------------------\n", fp ); + es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); + es_fprintf (fp, " Issuer:\t%s\n", e->issuer ); + es_fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash ); + es_fprintf (fp, " This Update:\t%s\n", e->this_update ); + es_fprintf (fp, " Next Update:\t%s\n", e->next_update ); + es_fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none"); + es_fprintf (fp, " AuthKeyId :\t%s\n", + e->authority_serialno? e->authority_serialno:"none"); if (e->authority_serialno && e->authority_issuer) { - fputs (" \t", fp); + es_fputs (" \t", fp); for (s=e->authority_issuer; *s; s++) if (*s == '\x01') - fputs ("\n \t", fp); + es_fputs ("\n \t", fp); else - putc (*s, fp); - putc ('\n', fp); + es_putc (*s, fp); + es_putc ('\n', fp); } - fprintf (fp, " Trust Check:\t%s\n", - !e->user_trust_req? "[system]" : - e->check_trust_anchor? e->check_trust_anchor:"[missing]"); + es_fprintf (fp, " Trust Check:\t%s\n", + !e->user_trust_req? "[system]" : + e->check_trust_anchor? e->check_trust_anchor:"[missing]"); if ((e->invalid & 1)) - fprintf (fp, _(" ERROR: The CRL will not be used because it was still too old after an update!\n")); + es_fprintf (fp, _(" ERROR: The CRL will not be used " + "because it was still too old after an update!\n")); if ((e->invalid & 2)) - fprintf (fp, _(" ERROR: The CRL will not be used due to an unknown critical extension!\n")); + es_fprintf (fp, _(" ERROR: The CRL will not be used " + "due to an unknown critical extension!\n")); if ((e->invalid & ~3)) - fprintf (fp, _(" ERROR: The CRL will not be used\n")); + es_fprintf (fp, _(" ERROR: The CRL will not be used\n")); cdb = lock_db_file (cache, e); if (!cdb) return gpg_error (GPG_ERR_GENERAL); if (!e->dbfile_checked) - fprintf (fp, _(" ERROR: This cached CRL may has been tampered with!\n")); + es_fprintf (fp, _(" ERROR: This cached CRL may has been tampered with!\n")); - putc ('\n', fp); + es_putc ('\n', fp); rc = cdb_findinit (&cdbfp, cdb, NULL, 0); while (!rc && (rc=cdb_findnext (&cdbfp)) > 0 ) { unsigned char keyrecord[256]; unsigned char record[16]; int reason; int any = 0; cdbi_t n; cdbi_t i; rc = 0; n = cdb_datalen (cdb); if (n != 16) { log_error (_(" WARNING: invalid cache record length\n")); warn = 1; continue; } if (cdb_read (cdb, record, n, cdb_datapos (cdb))) { log_error (_("problem reading cache record: %s\n"), strerror (errno)); warn = 1; continue; } n = cdb_keylen (cdb); if (n > sizeof keyrecord) n = sizeof keyrecord; if (cdb_read (cdb, keyrecord, n, cdb_keypos (cdb))) { log_error (_("problem reading cache key: %s\n"), strerror (errno)); warn = 1; continue; } reason = *record; - fputs (" ", fp); + es_fputs (" ", fp); for (i = 0; i < n; i++) - fprintf (fp, "%02X", keyrecord[i]); - fputs (":\t reasons( ", fp); + es_fprintf (fp, "%02X", keyrecord[i]); + es_fputs (":\t reasons( ", fp); if (reason & KSBA_CRLREASON_UNSPECIFIED) - fputs( "unspecified ", fp ), any = 1; + es_fputs( "unspecified ", fp ), any = 1; if (reason & KSBA_CRLREASON_KEY_COMPROMISE ) - fputs( "key_compromise ", fp ), any = 1; + es_fputs( "key_compromise ", fp ), any = 1; if (reason & KSBA_CRLREASON_CA_COMPROMISE ) - fputs( "ca_compromise ", fp ), any = 1; + es_fputs( "ca_compromise ", fp ), any = 1; if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED ) - fputs( "affiliation_changed ", fp ), any = 1; + es_fputs( "affiliation_changed ", fp ), any = 1; if (reason & KSBA_CRLREASON_SUPERSEDED ) - fputs( "superseeded", fp ), any = 1; + es_fputs( "superseeded", fp ), any = 1; if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION ) - fputs( "cessation_of_operation", fp ), any = 1; + es_fputs( "cessation_of_operation", fp ), any = 1; if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD ) - fputs( "certificate_hold", fp ), any = 1; + es_fputs( "certificate_hold", fp ), any = 1; if (reason && !any) - fputs( "other", fp ); + es_fputs( "other", fp ); - fprintf (fp, ") rdate: %.15s\n", record+1); + es_fprintf (fp, ") rdate: %.15s\n", record+1); } if (rc) log_error (_("error reading cache entry from db: %s\n"), strerror (rc)); unlock_db_file (cache, e); - fprintf (fp, _("End CRL dump\n") ); - putc ('\n', fp); + es_fprintf (fp, _("End CRL dump\n") ); + es_putc ('\n', fp); return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0; } /* Print the contents of the CRL CACHE in a human readable format to stream FP. */ gpg_error_t -crl_cache_list (FILE *fp) +crl_cache_list (estream_t fp) { crl_cache_t cache = get_current_cache (); crl_cache_entry_t entry; gpg_error_t err = 0; for (entry = cache->entries; entry && !entry->deleted && !err; entry = entry->next ) err = list_one_crl_entry (cache, entry, fp); return err; } /* Load the CRL containing the file named FILENAME into our CRL cache. */ gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename) { gpg_error_t err; - FILE *fp; + estream_t fp; ksba_reader_t reader; - fp = fopen (filename, "r"); + fp = es_fopen (filename, "r"); if (!fp) { err = gpg_error_from_errno (errno); log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); return err; } err = ksba_reader_new (&reader); if (!err) err = ksba_reader_set_file (reader, fp); if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); ksba_reader_release (reader); return err; } err = crl_cache_insert (ctrl, filename, reader); ksba_reader_release (reader); - fclose (fp); + es_fclose (fp); return err; } /* Locate the corresponding CRL for the certificate CERT, read and verify the CRL and store it in the cache. */ gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; ksba_reader_t reader = NULL; char *issuer = NULL; ksba_name_t distpoint = NULL; ksba_name_t issuername = NULL; char *distpoint_uri = NULL; char *issuername_uri = NULL; int any_dist_point = 0; int seq; /* Loop over all distribution points, get the CRLs and put them into the cache. */ if (opt.verbose) log_info ("checking distribution points\n"); seq = 0; while ( !(err = ksba_cert_get_crl_dist_point (cert, seq++, &distpoint, &issuername, NULL ))) { int name_seq; gpg_error_t last_err = 0; if (!distpoint && !issuername) { if (opt.verbose) log_info ("no issuer name and no distribution point\n"); break; /* Not allowed; i.e. an invalid certificate. We give up here and hope that the default method returns a suitable CRL. */ } xfree (issuername_uri); issuername_uri = NULL; /* Get the URIs. We do this in a loop to iterate over all names in the crlDP. */ for (name_seq=0; ksba_name_enum (distpoint, name_seq); name_seq++) { xfree (distpoint_uri); distpoint_uri = NULL; distpoint_uri = ksba_name_get_uri (distpoint, name_seq); if (!distpoint_uri) continue; if (!strncmp (distpoint_uri, "ldap:", 5) || !strncmp (distpoint_uri, "ldaps:", 6)) { if (opt.ignore_ldap_dp) continue; } else if (!strncmp (distpoint_uri, "http:", 5) || !strncmp (distpoint_uri, "https:", 6)) { if (opt.ignore_http_dp) continue; } else continue; /* Skip unknown schemes. */ any_dist_point = 1; if (opt.verbose) log_info ("fetching CRL from `%s'\n", distpoint_uri); err = crl_fetch (ctrl, distpoint_uri, &reader); if (err) { log_error (_("crl_fetch via DP failed: %s\n"), gpg_strerror (err)); last_err = err; continue; /* with the next name. */ } if (opt.verbose) log_info ("inserting CRL (reader %p)\n", reader); err = crl_cache_insert (ctrl, distpoint_uri, reader); if (err) { log_error (_("crl_cache_insert via DP failed: %s\n"), gpg_strerror (err)); last_err = err; continue; /* with the next name. */ } last_err = 0; break; /* Ready. */ } if (last_err) { err = last_err; goto leave; } ksba_name_release (distpoint); distpoint = NULL; /* We don't do anything with issuername_uri yet but we keep the code for documentation. */ issuername_uri = ksba_name_get_uri (issuername, 0); ksba_name_release (issuername); issuername = NULL; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* If we did not found any distpoint, try something reasonable. */ if (!any_dist_point ) { if (opt.verbose) log_info ("no distribution point - trying issuer name\n"); if (reader) { crl_close_reader (reader); reader = NULL; } issuer = ksba_cert_get_issuer (cert, 0); if (!issuer) { log_error ("oops: issuer missing in certificate\n"); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } if (opt.verbose) log_info ("fetching CRL from default location\n"); err = crl_fetch_default (ctrl, issuer, &reader); if (err) { log_error ("crl_fetch via issuer failed: %s\n", gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("inserting CRL (reader %p)\n", reader); err = crl_cache_insert (ctrl, "default location(s)", reader); if (err) { log_error (_("crl_cache_insert via issuer failed: %s\n"), gpg_strerror (err)); goto leave; } } leave: if (reader) crl_close_reader (reader); xfree (distpoint_uri); xfree (issuername_uri); ksba_name_release (distpoint); ksba_name_release (issuername); ksba_free (issuer); return err; } diff --git a/dirmngr/crlcache.h b/dirmngr/crlcache.h index b9e487436..c2995129c 100644 --- a/dirmngr/crlcache.h +++ b/dirmngr/crlcache.h @@ -1,70 +1,70 @@ /* crlcache.h - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #ifndef CRLCACHE_H #define CRLCACHE_H typedef enum { CRL_CACHE_VALID = 0, CRL_CACHE_INVALID, CRL_CACHE_DONTKNOW, CRL_CACHE_CANTUSE } crl_cache_result_t; typedef enum foo { CRL_SIG_OK = 0, CRL_SIG_NOT_OK, CRL_TOO_OLD, CRL_SIG_ERROR, CRL_GENERAL_ERROR } crl_sig_result_t; struct crl_cache_entry_s; typedef struct crl_cache_entry_s *crl_cache_entry_t; void crl_cache_init (void); void crl_cache_deinit (void); int crl_cache_flush(void); crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *cert_id, int force_refresh); gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, int force_refresh); gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader); -gpg_error_t crl_cache_list (FILE* fp); +gpg_error_t crl_cache_list (estream_t fp); gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename); gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert); #endif /* CRLCACHE_H */ diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index ca6c77a84..e89931e89 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -1,479 +1,478 @@ /* crlfetch.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2006, 2007 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include "crlfetch.h" #include "dirmngr.h" #include "misc.h" #include "http.h" #include "estream.h" /* For detecting armored CRLs received via HTTP (yes, such CRLS really exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June 2008) we need a context in the reader callback. */ struct reader_cb_context_s { estream_t fp; /* The stream used with the ksba reader. */ int checked:1; /* PEM/binary detection ahs been done. */ int is_pem:1; /* The file stream is PEM encoded. */ struct b64state b64state; /* The state used for Base64 decoding. */ }; /* We need to associate a reader object with the reader callback context. This table is used for it. */ struct file_reader_map_s { ksba_reader_t reader; struct reader_cb_context_s *cb_ctx; }; #define MAX_FILE_READER 50 static struct file_reader_map_s file_reader_map[MAX_FILE_READER]; /* Associate FP with READER. If the table is full wait until another thread has removed an entry. */ static void register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx) { int i; for (;;) { for (i=0; i < MAX_FILE_READER; i++) if (!file_reader_map[i].reader) { file_reader_map[i].reader = reader; file_reader_map[i].cb_ctx = cb_ctx; return; } log_info (_("reader to file mapping table full - waiting\n")); pth_sleep (2); } } /* Scan the table for an entry matching READER, remove that entry and return the associated file pointer. */ static struct reader_cb_context_s * get_file_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx = NULL; int i; for (i=0; i < MAX_FILE_READER; i++) if (file_reader_map[i].reader == reader) { cb_ctx = file_reader_map[i].cb_ctx; file_reader_map[i].reader = NULL; file_reader_map[i].cb_ctx = NULL; break; } return cb_ctx; } static int my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) { struct reader_cb_context_s *cb_ctx = opaque; int result; result = es_read (cb_ctx->fp, buffer, nbytes, nread); if (result) return result; /* Fixme we should check whether the semantics of es_read are okay and well defined. I have some doubts. */ if (nbytes && !*nread && es_feof (cb_ctx->fp)) return gpg_error (GPG_ERR_EOF); if (!nread && es_ferror (cb_ctx->fp)) return gpg_error (GPG_ERR_EIO); if (!cb_ctx->checked && *nread) { int c = *(unsigned char *)buffer; cb_ctx->checked = 1; if ( ((c & 0xc0) >> 6) == 0 /* class: universal */ && (c & 0x1f) == 16 /* sequence */ && (c & 0x20) /* is constructed */ ) ; /* Binary data. */ else { cb_ctx->is_pem = 1; b64dec_start (&cb_ctx->b64state, ""); } } if (cb_ctx->is_pem && *nread) { size_t nread2; if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) { /* EOF from decoder. */ *nread = 0; result = gpg_error (GPG_ERR_EOF); } else *nread = nread2; } return result; } /* Fetch CRL from URL and return the entire CRL using new ksba reader object in READER. Note that this reader object should be closed only using ldap_close_reader. */ gpg_error_t crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) { gpg_error_t err; parsed_uri_t uri; char *free_this = NULL; int redirects_left = 2; /* We allow for 2 redirect levels. */ *reader = NULL; once_more: err = http_parse_uri (&uri, url); http_release_parsed_uri (uri); if (err && url && !strncmp (url, "https:", 6)) { /* Our HTTP code does not support TLS, thus we can't use this scheme and it is frankly not useful for CRL retrieval anyway. We resort to using http, assuming that the server also provides plain http access. */ free_this = xtrymalloc (strlen (url) + 1); if (free_this) { strcpy (stpcpy (free_this,"http:"), url+6); err = http_parse_uri (&uri, free_this); http_release_parsed_uri (uri); if (!err) { log_info (_("using \"http\" instead of \"https\"\n")); url = free_this; } } } if (!err) /* Yes, our HTTP code groks that. */ { http_t hd; if (opt.disable_http) { log_error (_("CRL access not possible due to disabled %s\n"), "HTTP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else err = http_open_document (&hd, url, NULL, (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) - |HTTP_FLAG_NEED_HEADER |(DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0), - opt.http_proxy, NULL); + opt.http_proxy, NULL, NULL, NULL); switch ( err? 99999 : http_get_status_code (hd) ) { case 200: { estream_t fp = http_get_read_ptr (hd); struct reader_cb_context_s *cb_ctx; cb_ctx = xtrycalloc (1, sizeof *cb_ctx); if (!cb_ctx) err = gpg_error_from_syserror (); if (!err) err = ksba_reader_new (reader); if (!err) { cb_ctx->fp = fp; err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx); } if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); ksba_reader_release (*reader); *reader = NULL; http_close (hd, 0); } else { /* The ksba reader misses a user pointer thus we need to come up with our own way of associating a file pointer (or well the callback context) with the reader. It is only required when closing the reader thus there is no performance issue doing it this way. */ register_file_reader (*reader, cb_ctx); http_close (hd, 1); } } break; case 301: /* Redirection (perm.). */ case 302: /* Redirection (temp.). */ { const char *s = http_get_header (hd, "Location"); log_info (_("URL `%s' redirected to `%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (hd)); if (s && *s && redirects_left-- ) { xfree (free_this); url = NULL; free_this = xtrystrdup (s); if (!free_this) err = gpg_error_from_errno (errno); else { url = free_this; http_close (hd, 0); /* Note, that our implementation of redirection actually handles a redirect to LDAP. */ goto once_more; } } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); /* Or no "Location". */ http_close (hd, 0); } break; case 99999: /* Made up status code foer error reporting. */ log_error (_("error retrieving `%s': %s\n"), url, gpg_strerror (err)); break; default: log_error (_("error retrieving `%s': http status %u\n"), url, http_get_status_code (hd)); err = gpg_error (GPG_ERR_NO_DATA); http_close (hd, 0); } } else /* Let the LDAP code try other schemes. */ { if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else err = url_fetch_ldap (ctrl, url, NULL, 0, reader); } xfree (free_this); return err; } /* Fetch CRL for ISSUER using a default server. Return the entire CRL as a newly opened stream returned in R_FP. */ gpg_error_t crl_fetch_default (ctrl_t ctrl, const char *issuer, ksba_reader_t *reader) { if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList", reader); } /* Fetch a CA certificate for DN using the default server. This function only initiates the fetch; fetch_next_cert must be used to actually read the certificate; end_cert_fetch to end the operation. */ gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn) { if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } return start_default_fetch_ldap (ctrl, context, dn, "cACertificate"); } gpg_error_t start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, strlist_t patterns, const ldap_server_t server) { if (opt.disable_ldap) { log_error (_("certificate search not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } return start_cert_fetch_ldap (ctrl, context, patterns, server); } gpg_error_t fetch_next_cert (cert_fetch_context_t context, unsigned char **value, size_t * valuelen) { return fetch_next_cert_ldap (context, value, valuelen); } /* Fetch the next data from CONTEXT, assuming it is a certificate and return it as a cert object in R_CERT. */ gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert) { gpg_error_t err; unsigned char *value; size_t valuelen; ksba_cert_t cert; *r_cert = NULL; err = fetch_next_cert_ldap (context, &value, &valuelen); if (!err && !value) err = gpg_error (GPG_ERR_BUG); if (err) return err; err = ksba_cert_new (&cert); if (err) { xfree (value); return err; } err = ksba_cert_init_from_mem (cert, value, valuelen); xfree (value); if (err) { ksba_cert_release (cert); return err; } *r_cert = cert; return 0; } void end_cert_fetch (cert_fetch_context_t context) { return end_cert_fetch_ldap (context); } /* Lookup a cert by it's URL. */ gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, unsigned char **value, size_t *valuelen) { const unsigned char *cert_image; size_t cert_image_n; ksba_reader_t reader; ksba_cert_t cert; gpg_error_t err; *value = NULL; *valuelen = 0; cert_image = NULL; reader = NULL; cert = NULL; err = url_fetch_ldap (ctrl, url, NULL, 0, &reader); if (err) goto leave; err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_read_der (cert, reader); if (err) goto leave; cert_image = ksba_cert_get_image (cert, &cert_image_n); if (!cert_image || !cert_image_n) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } *value = xtrymalloc (cert_image_n); if (!*value) { err = gpg_error_from_syserror (); goto leave; } memcpy (*value, cert_image, cert_image_n); *valuelen = cert_image_n; leave: ksba_cert_release (cert); ldap_wrapper_release_context (reader); return err; } /* This function is to be used to close the reader object. In addition to running ksba_reader_release it also releases the LDAP or HTTP contexts associated with that reader. */ void crl_close_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx; if (!reader) return; /* Check whether this is a HTTP one. */ cb_ctx = get_file_reader (reader); if (cb_ctx) { /* This is an HTTP context. */ if (cb_ctx->fp) es_fclose (cb_ctx->fp); /* Release the base64 decoder state. */ if (cb_ctx->is_pem) b64dec_finish (&cb_ctx->b64state); /* Release the callback context. */ xfree (cb_ctx); } else /* This is an ldap wrapper context (Currently not used). */ ldap_wrapper_release_context (reader); /* Now get rid of the reader object. */ ksba_reader_release (reader); } diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c index 00b12b712..5aba0c80a 100644 --- a/dirmngr/dirmngr-client.c +++ b/dirmngr/dirmngr-client.c @@ -1,1027 +1,1027 @@ /* dirmngr-client.c - A client for the dirmngr daemon * Copyright (C) 2004, 2007 g10 Code GmbH * Copyright (C) 2002, 2003 Free Software Foundation, Inc. * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #define JNLIB_NEED_LOG_LOGV #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "i18n.h" #include "util.h" /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oLocal = 'l', oUrl = 'u', oOCSP = 500, oPing, oCacheCert, oValidate, oLookup, oLoadCRL, oSquidMode, oPEM, oEscapedPEM, oForceDefaultResponder }; /* The list of options as used by the argparse.c code. */ static ARGPARSE_OPTS opts[] = { { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") }, { oPing, "ping", 0, N_("check whether a dirmngr is running")}, { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")}, { oValidate, "validate", 0, N_("validate a certificate")}, { oLookup, "lookup", 0, N_("lookup a certificate")}, { oLocal, "local", 0, N_("lookup only locally stored certificates")}, { oUrl, "url", 0, N_("expect an URL for --lookup")}, { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")}, { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")}, { oPEM, "pem", 0, N_("certificates are expected in PEM format")}, { oForceDefaultResponder, "force-default-responder", 0, N_("force the use of the default OCSP responder")}, { 0, NULL, 0, NULL } }; /* The usual structure for the program flags. */ static struct { int quiet; int verbose; const char *dirmngr_program; int force_pipe_server; int force_default_responder; int pem; int escaped_pem; /* PEM is additional percent encoded. */ int url; /* Expect an URL. */ int local; /* Lookup up only local certificates. */ int use_ocsp; } opt; /* Communication structure for the certificate inquire callback. */ struct inq_cert_parm_s { assuan_context_t ctx; const unsigned char *cert; size_t certlen; }; /* Base64 conversion tables. */ static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static unsigned char asctobin[256]; /* runtime initialized */ /* Prototypes. */ static assuan_context_t start_dirmngr (int only_daemon); static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen); static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename); static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern); static gpg_error_t squid_loop_body (assuan_context_t ctx); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 11: p = "dirmngr-client (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr-client [options] " "[certfile|pattern] (-h for help)\n"); break; case 41: p = _("Syntax: dirmngr-client [options] [certfile|pattern]\n" "Test an X.509 certificate against a CRL or do an OCSP check\n" "The process returns 0 if the certificate is valid, 1 if it is\n" "not valid and other error codes for general failures\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; assuan_context_t ctx; gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; int cmd_ping = 0; int cmd_cache_cert = 0; int cmd_validate = 0; int cmd_lookup = 0; int cmd_loadcrl = 0; int cmd_squid_mode = 0; set_strusage (my_strusage); log_set_prefix ("dirmngr-client", JNLIB_LOG_WITH_PREFIX); /* For W32 we need to initialize the socket subsystem. Becuase we don't use Pth we need to do this explicit. */ #ifdef HAVE_W32_SYSTEM { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif /*HAVE_W32_SYSTEM*/ /* Init Assuan. */ assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); /* Setup I18N. */ i18n_init(); /* Parse the command line. */ 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 oQuiet: opt.quiet++; break; case oOCSP: opt.use_ocsp++; break; case oPing: cmd_ping = 1; break; case oCacheCert: cmd_cache_cert = 1; break; case oValidate: cmd_validate = 1; break; case oLookup: cmd_lookup = 1; break; case oUrl: opt.url = 1; break; case oLocal: opt.local = 1; break; case oLoadCRL: cmd_loadcrl = 1; break; case oPEM: opt.pem = 1; break; case oSquidMode: opt.pem = 1; opt.escaped_pem = 1; cmd_squid_mode = 1; break; case oForceDefaultResponder: opt.force_default_responder = 1; break; default : pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); /* Build the helptable for radix64 to bin conversion. */ if (opt.pem) { int i; unsigned char *s; for (i=0; i < 256; i++ ) asctobin[i] = 255; /* Used to detect invalid characters. */ for (s=bintoasc, i=0; *s; s++, i++) asctobin[*s] = i; } if (cmd_ping) err = 0; else if (cmd_lookup || cmd_loadcrl) { if (!argc) usage (1); err = 0; } else if (cmd_squid_mode) { err = 0; if (argc) usage (1); } else if (!argc) { err = read_certificate (NULL, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); } else if (argc == 1) { err = read_certificate (*argv, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from `%s': %s\n"), *argv, gpg_strerror (err)); } else { err = 0; usage (1); } if (log_get_errorcount (0)) exit (2); if (certbuflen > 20000) { log_error (_("certificate too large to make any sense\n")); exit (2); } ctx = start_dirmngr (1); if (!ctx) exit (2); if (cmd_ping) ; else if (cmd_squid_mode) { while (!(err = squid_loop_body (ctx))) ; if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; } else if (cmd_lookup) { int last_err = 0; for (; argc; argc--, argv++) { err = do_lookup (ctx, *argv); if (err) { log_error (_("lookup failed: %s\n"), gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_loadcrl) { int last_err = 0; for (; argc; argc--, argv++) { err = do_loadcrl (ctx, *argv); if (err) { log_error (_("loading CRL `%s' failed: %s\n"), *argv, gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_cache_cert) { err = do_cache (ctx, certbuf, certbuflen); xfree (certbuf); } else if (cmd_validate) { err = do_validate (ctx, certbuf, certbuflen); xfree (certbuf); } else { err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); } assuan_release (ctx); if (cmd_ping) { if (!opt.quiet) log_info (_("a dirmngr daemon is up and running\n")); return 0; } else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode) return err? 1:0; else if (cmd_cache_cert) { if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE ) { if (!opt.quiet) log_info (_("certificate already cached\n")); } else if (err) { log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return 1; } return 0; } else if (cmd_validate && err) { log_error (_("validation of certificate failed: %s\n"), gpg_strerror (err)); return 1; } else if (!err) { if (!opt.quiet) log_info (_("certificate is valid\n")); return 0; } else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) { if (!opt.quiet) log_info (_("certificate has been revoked\n")); return 1; } else { log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); return 2; } } /* Print status line from the assuan protocol. */ static gpg_error_t status_cb (void *opaque, const char *line) { (void)opaque; if (opt.verbose > 2) log_info (_("got status: `%s'\n"), line); return 0; } /* Print data as retrieved by the lookup function. */ static gpg_error_t data_cb (void *opaque, const void *buffer, size_t length) { gpg_error_t err; struct b64state *state = opaque; if (buffer) { err = b64enc_write (state, buffer, length); if (err) log_error (_("error writing base64 encoding: %s\n"), gpg_strerror (err)); } return 0; } /* Try to connect to the dirmngr via socket or fork it off and work by pipes. Handle the server's initial greeting */ static assuan_context_t start_dirmngr (int only_daemon) { int rc; char *infostr, *p; assuan_context_t ctx; int try_default = 0; infostr = opt.force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); if (only_daemon && (!infostr || !*infostr)) { infostr = xstrdup (dirmngr_socket_name ()); try_default = 1; } rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); return NULL; } if (!infostr || !*infostr) { const char *pgmname; const char *argv[3]; int no_close_list[3]; int i; if (only_daemon) { log_error (_("apparently no running dirmngr\n")); return NULL; } if (opt.verbose) log_info (_("no running dirmngr - starting one\n")); if (!opt.dirmngr_program || !*opt.dirmngr_program) opt.dirmngr_program = "./dirmngr"; if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) pgmname = opt.dirmngr_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); - no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); + no_close_list[i++] = assuan_fd_from_posix_fd (es_fileno (es_stderr)); no_close_list[i] = -1; /* Connect to the agent and perform initial handshaking. */ rc = assuan_pipe_connect (ctx, opt.dirmngr_program, argv, no_close_list, NULL, NULL, 0); } else /* Connect to a daemon. */ { int prot; int pid; infostr = xstrdup (infostr); if (!try_default && *infostr) { if ( !(p = strchr (infostr, ':')) || p == infostr) { log_error (_("malformed DIRMNGR_INFO environment variable\n")); xfree (infostr); if (only_daemon) return NULL; /* Try again by starting a new instance. */ opt.force_pipe_server = 1; return start_dirmngr (0); } *p++ = 0; pid = atoi (p); while (*p && *p != ':') p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("dirmngr protocol version %d is not supported\n"), prot); xfree (infostr); if (only_daemon) return NULL; opt.force_pipe_server = 1; return start_dirmngr (0); } } else pid = -1; rc = assuan_socket_connect (ctx, infostr, pid, 0); xfree (infostr); if (gpg_err_code(rc) == GPG_ERR_ASS_CONNECT_FAILED && !only_daemon) { log_error (_("can't connect to the dirmngr - trying fall back\n")); opt.force_pipe_server = 1; return start_dirmngr (0); } } if (rc) { assuan_release (ctx); log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (rc)); return NULL; } return ctx; } /* Read the first PEM certificate from the file FNAME. If fname is NULL the next certificate is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { FILE *fp; int c; int pos; int value; unsigned char *buf; size_t bufsize, buflen; enum { s_init, s_idle, s_lfseen, s_begin, s_b64_0, s_b64_1, s_b64_2, s_b64_3, s_waitend } state = s_init; fp = fname? fopen (fname, "r") : stdin; if (!fp) return gpg_error_from_errno (errno); pos = 0; value = 0; bufsize = 8192; buf = xmalloc (bufsize); buflen = 0; while ((c=getc (fp)) != EOF) { int escaped_c = 0; if (opt.escaped_pem) { if (c == '%') { char tmp[2]; if ((c = getc(fp)) == EOF) break; tmp[0] = c; if ((c = getc(fp)) == EOF) break; tmp[1] = c; if (!hexdigitp (tmp) || !hexdigitp (tmp+1)) { log_error ("invalid percent escape sequence\n"); state = s_idle; /* Force an error. */ /* Skip to end of line. */ while ( (c=getc (fp)) != EOF && c != '\n') ; goto ready; } c = xtoi_2 (tmp); escaped_c = 1; } else if (c == '\n') goto ready; /* Ready. */ } switch (state) { case s_idle: if (c == '\n') { state = s_lfseen; pos = 0; } break; case s_init: state = s_lfseen; case s_lfseen: if (c != "-----BEGIN "[pos]) state = s_idle; else if (pos == 10) state = s_begin; else pos++; break; case s_begin: if (c == '\n') state = s_b64_0; break; case s_b64_0: case s_b64_1: case s_b64_2: case s_b64_3: { if (buflen >= bufsize) { bufsize += 8192; buf = xrealloc (buf, bufsize); } if (c == '-') state = s_waitend; else if ((c = asctobin[c & 0xff]) == 255 ) ; /* Just skip invalid base64 characters. */ else if (state == s_b64_0) { value = c << 2; state = s_b64_1; } else if (state == s_b64_1) { value |= (c>>4)&3; buf[buflen++] = value; value = (c<<4)&0xf0; state = s_b64_2; } else if (state == s_b64_2) { value |= (c>>2)&15; buf[buflen++] = value; value = (c<<6)&0xc0; state = s_b64_3; } else { value |= c&0x3f; buf[buflen++] = value; state = s_b64_0; } } break; case s_waitend: /* Note that we do not check that the base64 decoder has been left in the expected state. We assume that the PEM header is just fine. However we need to wait for the real LF and not a trailing percent escaped one. */ if (c== '\n' && !escaped_c) goto ready; break; default: BUG(); } } ready: if (fname) fclose (fp); if (state == s_init && c == EOF) { xfree (buf); return gpg_error (GPG_ERR_EOF); } else if (state != s_waitend) { log_error ("no certificate or invalid encoded\n"); xfree (buf); return gpg_error (GPG_ERR_INV_ARMOR); } *rbuf = buf; *rbuflen = buflen; return 0; } /* Read a binary certificate from the file FNAME. If fname is NULL the file is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { gpg_error_t err; FILE *fp; unsigned char *buf; size_t nread, bufsize, buflen; if (opt.pem) return read_pem_certificate (fname, rbuf, rbuflen); fp = fname? fopen (fname, "rb") : stdin; if (!fp) return gpg_error_from_errno (errno); buf = NULL; bufsize = 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)) { err = gpg_error_from_errno (errno); xfree (buf); if (fname) fclose (fp); return err; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK if (fname) fclose (fp); *rbuf = buf; *rbuflen = buflen; return 0; } /* Callback for the inquire fiunction to send back the certificate. */ static gpg_error_t inq_cert (void *opaque, const char *line) { struct inq_cert_parm_s *parm = opaque; gpg_error_t err; if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10])) { err = assuan_send_data (parm->ctx, parm->cert, parm->certlen); } else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back and empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDCERT_SKI", 12) && (line[12]==' ' || !line[12])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDISSUERCERT", 14) && (line[14] == ' ' || !line[14])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else { log_info (_("unsupported inquiry `%s'\n"), line); err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); /* Note that this error will let assuan_transact terminate immediately instead of return the error to the caller. It is not clear whether this is the desired behaviour - it may change in future. */ } return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, (opt.use_ocsp && opt.force_default_responder ? "CHECKOCSP --force-default-responder" : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"), NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "CACHECERT", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using dirmngrs internal validate feature. Return a proper error code. */ static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "VALIDATE", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Load a CRL into the dirmngr. */ static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename) { gpg_error_t err; const char *s; char *fname, *line, *p; if (opt.url) fname = xstrdup (filename); else { #ifdef HAVE_CANONICALIZE_FILE_NAME fname = canonicalize_file_name (filename); if (!fname) { log_error ("error canonicalizing `%s': %s\n", filename, strerror (errno)); return gpg_error (GPG_ERR_GENERAL); } #else fname = xstrdup (filename); #endif if (*fname != '/') { log_error (_("absolute file name expected\n")); return gpg_error (GPG_ERR_GENERAL); } } line = xmalloc (8 + 6 + strlen (fname) * 3 + 1); p = stpcpy (line, "LOADCRL "); if (opt.url) p = stpcpy (p, "--url "); for (s = fname; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); xfree (line); xfree (fname); return err; } /* Do a LDAP lookup using PATTERN and print the result in a base-64 encoded format. */ static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern) { gpg_error_t err; const unsigned char *s; char *line, *p; struct b64state state; if (opt.verbose) log_info (_("looking up `%s'\n"), pattern); err = b64enc_start (&state, stdout, NULL); if (err) return err; line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); p = stpcpy (line, "LOOKUP "); if (opt.url) p = stpcpy (p, "--url "); if (opt.local) p = stpcpy (p, "--cache-only "); for (s=pattern; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, data_cb, &state, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); err = b64enc_finish (&state); xfree (line); return err; } /* The body of an endless loop: Read a line from stdin, retrieve the certificate from it, validate it and print "ERR" or "OK" to stdout. Continue. */ static gpg_error_t squid_loop_body (assuan_context_t ctx) { gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; err = read_pem_certificate (NULL, &certbuf, &certbuflen); if (gpg_err_code (err) == GPG_ERR_EOF) return err; if (err) { log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); puts ("ERROR"); return 0; } err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); if (!err) { if (opt.verbose) log_info (_("certificate is valid\n")); puts ("OK"); } else { if (!opt.quiet) { if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) log_info (_("certificate has been revoked\n")); else log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); } puts ("ERROR"); } fflush (stdout); return 0; } diff --git a/dirmngr/dirmngr-err.h b/dirmngr/dirmngr-err.h new file mode 100644 index 000000000..17e825570 --- /dev/null +++ b/dirmngr/dirmngr-err.h @@ -0,0 +1,12 @@ +/* Definition of the gpg-error source. */ + +#ifndef DIRMNGR_ERR_H +#define DIRMNGR_ERR_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_DIRMNGR +#include + +#endif /*DIRMNGR_ERR_H*/ diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 12b74bd00..5a913905d 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1,1829 +1,1848 @@ /* dirmngr.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr 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 #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif #include #include -#include +#ifdef HAVE_SIGNAL_H +# include +#endif #include #define JNLIB_NEED_LOG_LOGV #include "dirmngr.h" #include #include "certcache.h" #include "crlcache.h" #include "crlfetch.h" #include "misc.h" #include "ldapserver.h" #include "asshelp.h" +/* The plain Windows version uses the windows service system. For + example to start the service you may use "sc start dirmngr". + WindowsCE does not support this; the service system over there is + based on a single process with all services being DLLs - we can't + support this easily. */ +#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) +# define USE_W32_SERVICE 1 +#endif + + enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aServer, aDaemon, aService, aListCRLs, aLoadCRL, aFetchCRL, aShutdown, aFlush, aGPGConfList, aGPGConfTest, oOptions, oDebug, oDebugAll, oDebugWait, oDebugLevel, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oLogFile, oBatch, oDisableHTTP, oDisableLDAP, oIgnoreLDAPDP, oIgnoreHTTPDP, oIgnoreOCSPSvcUrl, oHonorHTTPProxy, oHTTPProxy, oLDAPProxy, oOnlyLDAPProxy, oLDAPFile, oLDAPTimeout, oLDAPAddServers, oOCSPResponder, oOCSPSigner, oOCSPMaxClockSkew, oOCSPMaxPeriod, oOCSPCurrentPeriod, oMaxReplies, oFakedSystemTime, oForce, oAllowOCSP, oSocketName, oLDAPWrapperProgram, oHTTPWrapperProgram, oIgnoreCertExtension, aTest }; static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ), ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ), -#ifdef HAVE_W32_SYSTEM +#ifdef USE_W32_SERVICE ARGPARSE_c (aService, "service", N_("run as windows service (background)")), #endif ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")), ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")), ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")), ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")), ARGPARSE_c (aFlush, "flush", N_("flush the cache")), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")), ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")), ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")), ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")), ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")), ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp", N_("ignore HTTP CRL distribution points")), ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp", N_("ignore LDAP CRL distribution points")), ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url", N_("ignore certificate contained OCSP service URLs")), ARGPARSE_s_s (oHTTPProxy, "http-proxy", N_("|URL|redirect all HTTP requests to URL")), ARGPARSE_s_s (oLDAPProxy, "ldap-proxy", N_("|HOST|use HOST for LDAP queries")), ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy", N_("do not use fallback hosts with --ldap-proxy")), ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file", N_("|FILE|read LDAP server list from FILE")), ARGPARSE_s_n (oLDAPAddServers, "add-servers", N_("add new servers discovered in CRL distribution" " points to serverlist")), ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout", N_("|N|set LDAP timeout to N seconds")), ARGPARSE_s_s (oOCSPResponder, "ocsp-responder", N_("|URL|use OCSP responder at URL")), ARGPARSE_s_s (oOCSPSigner, "ocsp-signer", N_("|FPR|OCSP response signed by FPR")), ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"), ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"), ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"), ARGPARSE_s_i (oMaxReplies, "max-replies", N_("|N|do not return more than N items in one query")), ARGPARSE_s_s (oSocketName, "socket-name", N_("|FILE|listen on socket FILE")), ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ ARGPARSE_p_u (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"), ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"), ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"), ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"), ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing " "of all commands and options)\n")), ARGPARSE_end () }; #define DEFAULT_MAX_REPLIES 10 #define DEFAULT_LDAP_TIMEOUT 100 /* arbitrary large timeout */ /* For the cleanup handler we need to keep track of the socket's name. */ static const char *socket_name; /* We need to keep track of the server's nonces (these are dummies for POSIX systems). */ static assuan_sock_nonce_t socket_nonce; /* Only if this flag has been set we will remove the socket file. */ static int cleanup_socket; /* 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; /* Helper to implement --debug-level. */ static const char *debug_level; /* Flag indicating that a shutdown has been requested. */ static volatile int shutdown_pending; /* Counter for the active connections. */ static int active_connections; /* The timer tick used for housekeeping stuff. For Windows we use a longer period as the SetWaitableTimer seems to signal earlier than the 2 seconds. */ #ifdef HAVE_W32_SYSTEM #define TIMERTICK_INTERVAL (4) #else #define TIMERTICK_INTERVAL (2) /* Seconds. */ #endif /* This union is used to avoid compiler warnings in case a pointer is 64 bit and an int 32 bit. We store an integer in a pointer and get it back later (pth_key_getdata et al.). */ union int_and_ptr_u { int aint; assuan_fd_t afd; void *aptr; }; /* The key used to store the current file descriptor in the thread local storage. We use this in conjunction with the log_set_pid_suffix_cb feature.. */ #ifndef HAVE_W32_SYSTEM static int my_tlskey_current_fd; #endif /* Prototypes. */ static void cleanup (void); static ldap_server_t parse_ldapserver_file (const char* filename); static fingerprint_list_t parse_ocsp_signer (const char *string); static void handle_connections (assuan_fd_t listen_fd); /* Pth wrapper function definitions. */ ASSUAN_SYSTEM_PTH_IMPL; GCRY_THREAD_OPTION_PTH_IMPL; static int fixed_gcry_pth_init (void) { return pth_self ()? 0 : (pth_init () == FALSE) ? errno : 0; } static const char * my_strusage( int level ) { const char *p; switch ( level ) { case 11: p = "dirmngr (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr [options] (-h for help)"); break; case 41: p = _("Syntax: dirmngr [options] [command [args]]\n" "LDAP and OCSP access for GnuPG\n"); break; default: p = NULL; } return p; } /* Callback from libksba to hash a provided buffer. Our current implementation does only allow SHA-1 for hashing. This may be extended by mapping the name, testing for algorithm availibility and adjust the length checks accordingly. */ static gpg_error_t my_ksba_hash_buffer (void *arg, const char *oid, const void *buffer, size_t length, size_t resultsize, unsigned char *result, size_t *resultlen) { (void)arg; if (oid && strcmp (oid, "1.3.14.3.2.26")) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (resultsize < 20) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); gcry_md_hash_buffer (2, result, buffer, length); *resultlen = 20; return 0; } /* 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 (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_ASSUAN_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE); else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level `%s' given\n"), debug_level); log_info (_("valid debug levels are: %s\n"), "none, basic, advanced, expert, guru"); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) { opt.verbose = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); } static void wrong_args (const char *text) { - fputs (_("usage: dirmngr [options] "), stderr); - fputs (text, stderr); - putc ('\n', stderr); + es_fputs (_("usage: dirmngr [options] "), es_stderr); + es_fputs (text, es_stderr); + es_putc ('\n', es_stderr); dirmngr_exit (2); } /* Helper to start the reaper thread for the ldap wrapper. */ static void launch_reaper_thread (void) { static int done; pth_attr_t tattr; if (done) return; done = 1; tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper"); if (!pth_spawn (tattr, ldap_wrapper_thread, NULL)) { log_error (_("error spawning ldap wrapper reaper thread: %s\n"), strerror (errno) ); dirmngr_exit (1); } pth_attr_destroy (tattr); } /* Helper to stop the reaper thread for the ldap wrapper. */ static void shutdown_reaper (void) { ldap_wrapper_wait_connections (); } /* Handle options which are allowed to be reset after program start. Return true if 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.ldap_wrapper_program = NULL; opt.disable_http = 0; opt.disable_ldap = 0; opt.honor_http_proxy = 0; opt.http_proxy = NULL; opt.ldap_proxy = NULL; opt.only_ldap_proxy = 0; opt.ignore_http_dp = 0; opt.ignore_ldap_dp = 0; opt.ignore_ocsp_service_url = 0; opt.allow_ocsp = 0; opt.ocsp_responder = NULL; opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */ opt.ocsp_max_period = 90 * 86400; /* 90 days. */ opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */ opt.max_replies = DEFAULT_MAX_REPLIES; while (opt.ocsp_signer) { fingerprint_list_t tmp = opt.ocsp_signer->next; xfree (opt.ocsp_signer); opt.ocsp_signer = tmp; } FREE_STRLIST (opt.ignored_cert_extensions); 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 handled. */ 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 oLDAPWrapperProgram: opt.ldap_wrapper_program = pargs->r.ret_str; break; case oHTTPWrapperProgram: opt.http_wrapper_program = pargs->r.ret_str; break; case oDisableHTTP: opt.disable_http = 1; break; case oDisableLDAP: opt.disable_ldap = 1; break; case oHonorHTTPProxy: opt.honor_http_proxy = 1; break; case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break; case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break; case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break; case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break; case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break; case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break; case oAllowOCSP: opt.allow_ocsp = 1; break; case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break; case oOCSPSigner: opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str); break; case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break; case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break; case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break; case oMaxReplies: opt.max_replies = pargs->r.ret_int; break; case oIgnoreCertExtension: add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str); break; default: return 0; /* Not handled. */ } return 1; /* Handled. */ } -#ifdef HAVE_W32_SYSTEM +#ifdef USE_W32_SERVICE /* The global status of our service. */ SERVICE_STATUS_HANDLE service_handle; SERVICE_STATUS service_status; DWORD WINAPI w32_service_control (DWORD control, DWORD event_type, LPVOID event_data, LPVOID context) { /* event_type and event_data are not used here. */ switch (control) { case SERVICE_CONTROL_SHUTDOWN: /* For shutdown we will try to force termination. */ service_status.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus (service_handle, &service_status); shutdown_pending = 3; break; case SERVICE_CONTROL_STOP: service_status.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus (service_handle, &service_status); shutdown_pending = 1; break; default: break; } return 0; } -#endif /*HAVE_W32_SYSTEM*/ +#endif /*USE_W32_SERVICE*/ #ifndef HAVE_W32_SYSTEM static int pid_suffix_callback (unsigned long *r_suffix) { union int_and_ptr_u value; value.aptr = pth_key_getdata (my_tlskey_current_fd); *r_suffix = value.aint; return (*r_suffix != -1); /* Use decimal representation. */ } #endif /*!HAVE_W32_SYSTEM*/ -#ifdef HAVE_W32_SYSTEM -#define main real_main +#ifdef USE_W32_SERVICE +# define main real_main #endif int main (int argc, char **argv) { -#ifdef HAVE_W32_SYSTEM -#undef main +#ifdef USE_W32_SERVICE +# undef main #endif enum cmd_and_opt_values cmd = 0; ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; int default_config =1; int greeting = 0; int nogreeting = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; char *ldapfile = NULL; int debug_wait = 0; int rc; int homedir_seen = 0; struct assuan_malloc_hooks malloc_hooks; -#ifdef HAVE_W32_SYSTEM +#ifdef USE_W32_SERVICE /* The option will be set by main() below if we should run as a system daemon. */ if (opt.system_service) { service_handle = RegisterServiceCtrlHandlerEx ("DirMngr", &w32_service_control, NULL /*FIXME*/); if (service_handle == 0) log_error ("failed to register service control handler: ec=%d", (int) GetLastError ()); service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; service_status.dwCurrentState = SERVICE_START_PENDING; service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; service_status.dwWin32ExitCode = NO_ERROR; service_status.dwServiceSpecificExitCode = NO_ERROR; service_status.dwCheckPoint = 0; service_status.dwWaitHint = 10000; /* 10 seconds timeout. */ SetServiceStatus (service_handle, &service_status); } -#endif /*HAVE_W32_SYSTEM*/ +#endif /*USE_W32_SERVICE*/ set_strusage (my_strusage); log_set_prefix ("dirmngr", 1|4); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); /* Libgcrypt requires us to register the threading model first. Note that this will also do the pth_init. */ gcry_threads_pth.init = fixed_gcry_pth_init; rc = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); if (rc) { log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", gpg_strerror (rc)); } gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* Check that the libraries are suitable. Do it here because the option parsing may need services of the libraries. */ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt", NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); if (!ksba_check_version (NEED_KSBA_VERSION) ) log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba", NEED_KSBA_VERSION, ksba_check_version (NULL) ); ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL); /* Init Assuan. */ malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_set_system_hooks (ASSUAN_SYSTEM_PTH); assuan_sock_init (); setup_libassuan_logging (&opt.debug); setup_libgcrypt_logging (); /* Setup defaults. */ shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; opt.homedir = default_homedir (); /* Now with Pth running we can set the logging callback. Our windows implementation does not yet feature the Pth TLS functions. */ #ifndef HAVE_W32_SYSTEM if (pth_key_create (&my_tlskey_current_fd, NULL)) if (pth_key_setdata (my_tlskey_current_fd, NULL)) log_set_pid_suffix_cb (pid_suffix_callback); #endif /*!HAVE_W32_SYSTEM*/ /* Reset rereadable options to default values. */ parse_rereadable_options (NULL, 0); /* LDAP defaults. */ opt.add_new_ldapservers = 0; opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT; /* Other defaults. */ socket_name = dirmngr_socket_name (); /* 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 == 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; homedir_seen = 1; } else if (pargs.r_opt == aDaemon) opt.system_daemon = 1; else if (pargs.r_opt == aService) { /* Redundant. The main function takes care of it. */ opt.system_service = 1; opt.system_daemon = 1; } #ifdef HAVE_W32_SYSTEM else if (pargs.r_opt == aGPGConfList || pargs.r_opt == aGPGConfTest) /* We set this so we switch to the system configuration directory below. This is a crutch to solve the problem that the user configuration is never used on Windows. Also see below at aGPGConfList. */ opt.system_daemon = 1; #endif } /* If --daemon has been given on the command line but not --homedir, we switch to /etc/dirmngr as default home directory. Note, that this also overrides the GNUPGHOME environment variable. */ if (opt.system_daemon && !homedir_seen) { opt.homedir = gnupg_sysconfdir (); opt.homedir_data = gnupg_datadir (); opt.homedir_cache = gnupg_cachedir (); } if (default_config) configname = make_filename (opt.homedir, "dirmngr.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 aServer: case aDaemon: case aService: case aShutdown: case aFlush: case aListCRLs: case aLoadCRL: case aFetchCRL: case aGPGConfList: case aGPGConfTest: cmd = pargs.r_opt; 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 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: /* Ignore this option here. */; 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 oLDAPFile: ldapfile = pargs.r.ret_str; break; case oLDAPAddServers: opt.add_new_ldapservers = 1; break; case oLDAPTimeout: opt.ldaptimeout = pargs.r.ret_int; break; case oFakedSystemTime: gnupg_set_time ((time_t)pargs.r.ret_ulong, 0); break; case oForce: opt.force = 1; break; case oSocketName: socket_name = pargs.r.ret_str; break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { - fclose( configfp ); + fclose (configfp); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ opt.config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); if (nogreeting ) greeting = 0; if (!opt.homedir_data) opt.homedir_data = opt.homedir; if (!opt.homedir_cache) opt.homedir_cache = opt.homedir; if (greeting) { - fprintf (stderr, "%s %s; %s\n", - strusage(11), strusage(13), strusage(14) ); - fprintf (stderr, "%s\n", strusage(15) ); + es_fprintf (es_stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + es_fprintf (es_stderr, "%s\n", strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION log_info ("NOTE: this is a development version!\n"); #endif + if (!access ("/etc/dirmngr", F_OK) && !strncmp (opt.homedir, "/etc/", 5)) + log_info + ("NOTE: DirMngr is now a proper part of GnuPG. The configuration and" + " other directory names changed. Please check that no other version" + " of dirmngr is still installed. To disable this warning, remove the" + " directory `/etc/dirmngr'.\n"); + if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; gnupg_get_isotime (tbuf); log_info (_("WARNING: running with faked system time %s\n"), tbuf); } set_debug (); /* Get LDAP server list from file. */ if (!ldapfile) { ldapfile = make_filename (opt.homedir, opt.system_daemon? "ldapservers.conf":"dirmngr_ldapservers.conf", NULL); opt.ldapservers = parse_ldapserver_file (ldapfile); xfree (ldapfile); } else opt.ldapservers = parse_ldapserver_file (ldapfile); #ifndef HAVE_W32_SYSTEM /* We need to ignore the PIPE signal because the we might log to a socket and that code handles EPIPE properly. The ldap wrapper also requires us to ignore this silly signal. Assuan would set this signal to ignore anyway.*/ signal (SIGPIPE, SIG_IGN); #endif /* Ready. Now to our duties. */ if (!cmd && opt.system_service) cmd = aDaemon; else if (!cmd) cmd = aServer; rc = 0; if (cmd == aServer) { if (argc) wrong_args ("--server"); if (logfile) { log_set_file (logfile); log_set_prefix (NULL, 2|4); } if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } launch_reaper_thread (); cert_cache_init (); crl_cache_init (); start_command_handler (ASSUAN_INVALID_FD); shutdown_reaper (); } else if (cmd == aDaemon) { assuan_fd_t fd; pid_t pid; int len; struct sockaddr_un serv_addr; if (argc) wrong_args ("--daemon"); /* 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); } #ifndef HAVE_W32_SYSTEM if (strchr (socket_name, ':')) { log_error (_("colons are not allowed in the socket name\n")); dirmngr_exit (1); } #endif if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) { log_error (_("name of socket too long\n")); dirmngr_exit (1); } fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) { log_error (_("can't create socket: %s\n"), strerror (errno)); cleanup (); dirmngr_exit (1); } memset (&serv_addr, 0, sizeof serv_addr); serv_addr.sun_family = AF_UNIX; strcpy (serv_addr.sun_path, socket_name); len = (offsetof (struct sockaddr_un, sun_path) + strlen (serv_addr.sun_path) + 1); rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len); if (rc == -1 && errno == EADDRINUSE) { - remove (socket_name); + gnupg_remove (socket_name); rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len); } if (rc != -1 && (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce))) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { log_error (_("error binding socket to `%s': %s\n"), serv_addr.sun_path, gpg_strerror (gpg_error_from_errno (errno))); assuan_sock_close (fd); dirmngr_exit (1); } cleanup_socket = 1; if (listen (FD2INT (fd), 5) == -1) { log_error (_("listen() failed: %s\n"), strerror (errno)); assuan_sock_close (fd); dirmngr_exit (1); } if (opt.verbose) log_info (_("listening on socket `%s'\n"), socket_name ); - fflush (NULL); + es_fflush (NULL); #ifdef HAVE_W32_SYSTEM pid = getpid (); printf ("set DIRMNGR_INFO=%s;%lu;1\n", socket_name, (ulong) pid); #else pid = pth_fork (); if (pid == (pid_t)-1) { log_fatal (_("fork failed: %s\n"), strerror (errno) ); dirmngr_exit (1); } if (pid) { /* We are the parent */ char *infostr; /* Don't let cleanup() remove the socket - the child is responsible for doing that. */ cleanup_socket = 0; close (fd); /* Create the info string: :: */ if (asprintf (&infostr, "DIRMNGR_INFO=%s:%lu:1", socket_name, (ulong)pid ) < 0) { log_error (_("out of core\n")); kill (pid, SIGTERM); dirmngr_exit (1); } /* 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 DIRMNGR_INFO;\n", infostr); } free (infostr); exit (0); /*NEVER REACHED*/ } /* end parent */ /* This is the child */ /* Detach from tty and put process into a new session */ if (!nodetach ) { int i; 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) ); dirmngr_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)); dirmngr_exit (1); } } #endif launch_reaper_thread (); cert_cache_init (); crl_cache_init (); -#ifdef HAVE_W32_SYSTEM +#ifdef USE_W32_SERVICE if (opt.system_service) { service_status.dwCurrentState = SERVICE_RUNNING; SetServiceStatus (service_handle, &service_status); } #endif handle_connections (fd); assuan_sock_close (fd); shutdown_reaper (); -#ifdef HAVE_W32_SYSTEM +#ifdef USE_W32_SERVICE if (opt.system_service) { service_status.dwCurrentState = SERVICE_STOPPED; SetServiceStatus (service_handle, &service_status); } #endif } else if (cmd == aListCRLs) { /* Just list the CRL cache and exit. */ if (argc) wrong_args ("--list-crls"); launch_reaper_thread (); crl_cache_init (); - crl_cache_list (stdout); + crl_cache_list (es_stdout); } else if (cmd == aLoadCRL) { struct server_control_s ctrlbuf; memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); launch_reaper_thread (); cert_cache_init (); crl_cache_init (); if (!argc) rc = crl_cache_load (&ctrlbuf, NULL); else { for (; !rc && argc; argc--, argv++) rc = crl_cache_load (&ctrlbuf, *argv); } } else if (cmd == aFetchCRL) { ksba_reader_t reader; struct server_control_s ctrlbuf; if (argc != 1) wrong_args ("--fetch-crl URL"); memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); launch_reaper_thread (); cert_cache_init (); crl_cache_init (); rc = crl_fetch (&ctrlbuf, argv[0], &reader); if (rc) log_error (_("fetching CRL from `%s' failed: %s\n"), argv[0], gpg_strerror (rc)); else { rc = crl_cache_insert (&ctrlbuf, argv[0], reader); if (rc) log_error (_("processing CRL from `%s' failed: %s\n"), argv[0], gpg_strerror (rc)); crl_close_reader (reader); } } else if (cmd == aFlush) { /* Delete cache and exit. */ if (argc) wrong_args ("--flush"); rc = crl_cache_flush(); } else if (cmd == aGPGConfTest) dirmngr_exit (0); else if (cmd == aGPGConfList) { unsigned long flags = 0; char *filename; char *filename_esc; /* 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 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) #define GC_OPT_FLAG_NO_CHANGE (1UL <<7) #ifdef HAVE_W32_SYSTEM /* On Windows systems, dirmngr always runs as system daemon, and the per-user configuration is never used. So we short-cut everything to use the global system configuration of dirmngr above, and here we set the no change flag to make these read-only. */ flags |= GC_OPT_FLAG_NO_CHANGE; #endif /* First the configuration file. This is not an option, but it is vital information for GPG Conf. */ if (!opt.config_filename) opt.config_filename = make_filename (opt.homedir, "dirmngr.conf", NULL ); filename = percent_escape (opt.config_filename, NULL); printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, filename); xfree (filename); printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE); /* --csh and --sh are mutually exclusive, something we can not express in GPG Conf. --options is only usable from the command line, really. --debug-all interacts with --debug, and having both of them is thus problematic. --no-detach is also only usable on the command line. --batch is unused. */ filename = make_filename (opt.homedir, opt.system_daemon? "ldapservers.conf":"dirmngr_ldapservers.conf", NULL); filename_esc = percent_escape (filename, NULL); printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename_esc); xfree (filename); printf ("ldaptimeout:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT); printf ("max-replies:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES); printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE); /* Note: The next one is to fix a typo in gpgconf - should be removed eventually. */ printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); } cleanup (); return !!rc; } -#ifdef HAVE_W32_SYSTEM +#ifdef USE_W32_SERVICE int main (int argc, char *argv[]) { int i; /* Find out if we run in daemon mode or on the command line. */ for (i = 1; i < argc; i++) if (!strcmp (argv[i], "--service")) { opt.system_service = 1; opt.system_daemon = 1; break; } if (!opt.system_service) return real_main (argc, argv); else { SERVICE_TABLE_ENTRY DispatchTable [] = { /* Ignore warning. */ { "DirMngr", &real_main }, { NULL, NULL } }; if (!StartServiceCtrlDispatcher (DispatchTable)) return 1; return 0; } } -#endif +#endif /*USE_W32_SERVICE*/ static void cleanup (void) { crl_cache_deinit (); cert_cache_deinit (1); ldapserver_list_free (opt.ldapservers); opt.ldapservers = NULL; if (cleanup_socket) { cleanup_socket = 0; if (socket_name && *socket_name) - remove (socket_name); + gnupg_remove (socket_name); } } void dirmngr_exit (int rc) { cleanup (); exit (rc); } void dirmngr_init_default_ctrl (ctrl_t ctrl) { (void)ctrl; /* Nothing for now. */ } /* Create a list of LDAP servers from the file FILENAME. Returns the list or NULL in case of errors. The format fo such a file is line oriented where empty lines and lines starting with a hash mark are ignored. All other lines are assumed to be colon seprated with these fields: 1. field: Hostname 2. field: Portnumber 3. field: Username 4. field: Password 5. field: Base DN */ static ldap_server_t parse_ldapserver_file (const char* filename) { char buffer[1024]; char *p; ldap_server_t server, serverstart, *serverend; int c; unsigned int lineno = 0; - FILE *fp; + estream_t fp; - fp = fopen (filename, "r"); + fp = es_fopen (filename, "r"); if (!fp) { log_error (_("error opening `%s': %s\n"), filename, strerror (errno)); return NULL; } serverstart = NULL; serverend = &serverstart; - while (fgets (buffer, sizeof buffer, fp)) + while (es_fgets (buffer, sizeof buffer, fp)) { lineno++; if (!*buffer || buffer[strlen(buffer)-1] != '\n') { - if (*buffer && feof (fp)) + if (*buffer && es_feof (fp)) ; /* Last line not terminated - continue. */ else { log_error (_("%s:%u: line too long - skipped\n"), filename, lineno); - while ( (c=fgetc (fp)) != EOF && c != '\n') + while ( (c=es_fgetc (fp)) != EOF && c != '\n') ; /* Skip until end of line. */ continue; } } /* Skip empty and comment lines.*/ for (p=buffer; spacep (p); p++) ; if (!*p || *p == '\n' || *p == '#') continue; /* Parse the colon separated fields. */ server = ldapserver_parse_one (buffer, filename, lineno); if (server) { *serverend = server; serverend = &server->next; } } - if (ferror (fp)) + if (es_ferror (fp)) log_error (_("error reading `%s': %s\n"), filename, strerror (errno)); - fclose (fp); + es_fclose (fp); return serverstart; } static fingerprint_list_t parse_ocsp_signer (const char *string) { gpg_error_t err; char *fname; - FILE *fp; + estream_t fp; char line[256]; char *p; fingerprint_list_t list, *list_tail, item; unsigned int lnr = 0; int c, i, j; int errflag = 0; /* Check whether this is not a filename and treat it as a direct fingerprint specification. */ if (!strpbrk (string, "/.~\\")) { item = xcalloc (1, sizeof *item); for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++) if ( string[i] != ':' ) item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i]; item->hexfpr[j] = 0; if (j != 40 || !(spacep (string+i) || !string[i])) { log_error (_("%s:%u: invalid fingerprint detected\n"), "--ocsp-signer", 0); xfree (item); return NULL; } return item; } /* Well, it is a filename. */ if (*string == '/' || (*string == '~' && string[1] == '/')) fname = make_filename (string, NULL); else { if (string[0] == '.' && string[1] == '/' ) string += 2; fname = make_filename (opt.homedir, string, NULL); } - fp = fopen (fname, "r"); + fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open `%s': %s\n"), fname, gpg_strerror (err)); xfree (fname); return NULL; } list = NULL; list_tail = &list; for (;;) { - if (!fgets (line, DIM(line)-1, fp) ) + if (!es_fgets (line, DIM(line)-1, fp) ) { - if (!feof (fp)) + if (!es_feof (fp)) { err = gpg_error_from_syserror (); log_error (_("%s:%u: read error: %s\n"), fname, lnr, gpg_strerror (err)); errflag = 1; } - fclose (fp); + es_fclose (fp); if (errflag) { while (list) { fingerprint_list_t tmp = list->next; xfree (list); list = tmp; } } xfree (fname); return list; /* Ready. */ } lnr++; if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line. */ - while ( (c=getc (fp)) != EOF && c != '\n') + while ( (c=es_getc (fp)) != EOF && c != '\n') ; err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG /* */: GPG_ERR_INCOMPLETE_LINE); log_error (_("%s:%u: read error: %s\n"), fname, lnr, gpg_strerror (err)); errflag = 1; continue; } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; if (!*p || *p == '\n' || *p == '#') continue; item = xcalloc (1, sizeof *item); *list_tail = item; list_tail = &item->next; for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) if ( p[i] != ':' ) item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; item->hexfpr[j] = 0; if (j != 40 || !(spacep (p+i) || p[i] == '\n')) { log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr); errflag = 1; } i++; while (spacep (p+i)) i++; if (p[i] && p[i] != '\n') log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr); } /*NOTREACHED*/ } /* Stuff used in daemon mode. */ /* 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 (!opt.config_filename) return; /* No config file. */ fp = fopen (opt.config_filename, "r"); if (!fp) { log_error (_("option file `%s': %s\n"), opt.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, opt.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 (); } /* The signal handler. */ static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: log_info (_("SIGHUP received - " "re-reading configuration and flushing caches\n")); reread_configuration (); cert_cache_deinit (0); crl_cache_deinit (); cert_cache_init (); crl_cache_init (); break; case SIGUSR1: cert_cache_print_stats (); 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 %d active connections\n"), active_connections); shutdown_pending++; if (shutdown_pending > 2) { log_info (_("shutdown forced\n")); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); dirmngr_exit (0); } break; case SIGINT: log_info (_("SIGINT received - immediate shutdown\n")); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); dirmngr_exit (0); break; #endif default: log_info (_("signal %d received - no action defined\n"), signo); } } /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { /* Nothing real to do right now. Actually we need the timeout only for W32 where we don't use signals and need a way for the loop to check for the shutdown flag. */ #ifdef HAVE_W32_SYSTEM if (shutdown_pending) log_info (_("SIGTERM received - shutting down ...\n")); if (shutdown_pending > 2) { log_info (_("shutdown forced\n")); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); dirmngr_exit (0); } #endif /*HAVE_W32_SYSTEM*/ } /* Check the nonce on a new connection. This is a NOP unless we we are using our Unix domain socket emulation under Windows. */ static int check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce) { if (assuan_sock_check_nonce (fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT (fd), strerror (errno)); assuan_sock_close (fd); return -1; } else return 0; } /* Helper to call a connection's main fucntion. */ static void * start_connection_thread (void *arg) { union int_and_ptr_u argval; assuan_fd_t fd; argval.aptr = arg; fd = argval.afd; if (check_nonce (fd, &socket_nonce)) return NULL; #ifndef HAVE_W32_SYSTEM pth_key_setdata (my_tlskey_current_fd, argval.aptr); #endif active_connections++; if (opt.verbose) log_info (_("handler for fd %d started\n"), FD2INT (fd)); start_command_handler (fd); if (opt.verbose) log_info (_("handler for fd %d terminated\n"), FD2INT (fd)); active_connections--; #ifndef HAVE_W32_SYSTEM argval.afd = ASSUAN_INVALID_FD; pth_key_setdata (my_tlskey_current_fd, argval.aptr); #endif return NULL; } /* Main loop in daemon mode. */ static void handle_connections (assuan_fd_t listen_fd) { pth_attr_t tattr; pth_event_t ev, time_ev; sigset_t sigs, oldsigs; int signo; struct sockaddr_un paddr; socklen_t plen = sizeof( paddr ); assuan_fd_t fd; tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 1024*1024); pth_attr_set (tattr, PTH_ATTR_NAME, "dirmngr"); #ifndef HAVE_W32_SYSTEM /* FIXME */ sigemptyset (&sigs ); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGUSR1); sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #else sigs = 0; ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #endif time_ev = NULL; for (;;) { if (shutdown_pending) { if (!active_connections) break; /* ready */ /* Do not accept anymore connections but wait for existing connections to terminate. */ signo = 0; pth_wait (ev); if (pth_event_occurred (ev) && signo) handle_signal (signo); continue; } if (!time_ev) time_ev = pth_event (PTH_EVENT_TIME, pth_timeout (TIMERTICK_INTERVAL, 0)); if (time_ev) pth_event_concat (ev, time_ev, NULL); fd = (assuan_fd_t) pth_accept_ev (FD2INT (listen_fd), (struct sockaddr *)&paddr, &plen, ev); if (time_ev) pth_event_isolate (time_ev); if (fd == ASSUAN_INVALID_FD) { 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 (_("accept 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 a new thread and because we don't want any signals (as we are handling them here) to be delivered to a new thread we need to block those signals. */ pth_sigmask (SIG_BLOCK, &sigs, &oldsigs); /* Create thread to handle this connection. */ { union int_and_ptr_u argval; argval.afd = fd; if (!pth_spawn (tattr, start_connection_thread, argval.aptr)) { log_error (_("error spawning connection handler: %s\n"), strerror (errno) ); assuan_sock_close (fd); } } /* 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); pth_attr_destroy (tattr); cleanup (); log_info ("%s %s stopped\n", strusage(11), strusage(13)); } diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index e6fa0d318..67f2122d9 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -1,189 +1,185 @@ /* dirmngr.h - Common definitions for the dirmngr * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2004 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef DIRMNGR_H #define DIRMNGR_H -#ifdef GPG_ERR_SOURCE_DEFAULT -#error GPG_ERR_SOURCE_DEFAULT already defined -#endif -#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_DIRMNGR -#include +#include "./dirmngr-err.h" #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include #include #include "../common/util.h" #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ #include "../common/i18n.h" /* This objects keeps information about a particular LDAP server and is used as item of a single linked list of servers. */ struct ldap_server_s { struct ldap_server_s* next; char *host; int port; char *user; char *pass; char *base; }; typedef struct ldap_server_s *ldap_server_t; /* A list of fingerprints. */ struct fingerprint_list_s; typedef struct fingerprint_list_s *fingerprint_list_t; struct fingerprint_list_s { fingerprint_list_t next; char hexfpr[20+20+1]; }; /* 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 dry_run; /* don't change any persistent data */ int batch; /* batch mode */ const char *homedir; /* Configuration directory name */ const char *homedir_data; /* Ditto for data files (/usr/share/dirmngr). */ const char *homedir_cache; /* Ditto for cache files (/var/cache/dirmngr). */ char *config_filename; /* Name of a config file, which will be reread on a HUP if it is not NULL. */ char *ldap_wrapper_program; /* Override value for the LDAP wrapper program. */ char *http_wrapper_program; /* Override value for the HTTP wrapper program. */ int system_service; /* We are running as W32 service (implies daemon). */ int system_daemon; /* We are running in system daemon mode. */ int running_detached; /* We are running in detached mode. */ int force; /* Force loading outdated CRLs. */ int disable_http; /* Do not use HTTP at all. */ int disable_ldap; /* Do not use LDAP at all. */ int honor_http_proxy; /* Honor the http_proxy env variable. */ const char *http_proxy; /* Use given HTTP proxy. */ const char *ldap_proxy; /* Use given LDAP proxy. */ int only_ldap_proxy; /* Only use the LDAP proxy; no fallback. */ int ignore_http_dp; /* Ignore HTTP CRL distribution points. */ int ignore_ldap_dp; /* Ignore LDAP CRL distribution points. */ int ignore_ocsp_service_url; /* Ignore OCSP service URLs as given in the certificate. */ /* A list of certificate extension OIDs which are ignored so that one can claim that a critical extension has been handled. One OID per string. */ strlist_t ignored_cert_extensions; int allow_ocsp; /* Allow using OCSP. */ int max_replies; unsigned int ldaptimeout; ldap_server_t ldapservers; int add_new_ldapservers; const char *ocsp_responder; /* Standard OCSP responder's URL. */ fingerprint_list_t ocsp_signer; /* The list of fingerprints with allowed standard OCSP signer certificates. */ unsigned int ocsp_max_clock_skew; /* Allowed seconds of clocks skew. */ unsigned int ocsp_max_period; /* Seconds a response is at maximum considered valid after thisUpdate. */ unsigned int ocsp_current_period; /* Seconds a response is considered current after nextUpdate. */ } opt; #define DBG_X509_VALUE 1 /* debug x.509 parsing */ #define DBG_LOOKUP_VALUE 2 /* debug lookup 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_LOOKUP (opt.debug & DBG_LOOKUP_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) /* A simple list of certificate references. */ struct cert_ref_s { struct cert_ref_s *next; unsigned char fpr[20]; }; typedef struct cert_ref_s *cert_ref_t; /* Forward references; access only through server.c. */ struct server_local_s; /* Connection control structure. */ struct server_control_s { int refcount; /* Count additional references to this object. */ 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 force_crl_refresh; /* Always load a fresh CRL. */ int check_revocations_nest_level; /* Internal to check_revovations. */ cert_ref_t ocsp_certs; /* Certificates from the current OCSP response. */ int audit_events; /* Send audit events to client. */ }; /*-- dirmngr.c --*/ void dirmngr_exit( int ); /* Wrapper for exit() */ void dirmngr_init_default_ctrl (ctrl_t ctrl); /*-- server.c --*/ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl); ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer); ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer); ksba_cert_t get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid); gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr); void start_command_handler (gnupg_fd_t fd); gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...); gpg_error_t dirmngr_tick (ctrl_t ctrl); #endif /*DIRMNGR_H*/ diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index f862081c1..fccbc58d1 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -1,630 +1,634 @@ /* dirmngr-ldap.c - The LDAP helper for dirmngr. * Copyright (C) 2004 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include -#include +#ifdef HAVE_SIGNAL_H +# include +#endif #include #include #include #include #ifdef HAVE_W32_SYSTEM #include #include #include #include "ldap-url.h" #else /* For OpenLDAP, to enable the API that we're using. */ #define LDAP_DEPRECATED 1 #include #endif #define JNLIB_NEED_LOG_LOGV #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "i18n.h" #include "util.h" #define DEFAULT_LDAP_TIMEOUT 100 /* Arbitrary long timeout. */ /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oTimeout = 500, oMulti, oProxy, oHost, oPort, oUser, oPass, oEnvPass, oDN, oFilter, oAttr, oOnlySearchTimeout, oLogWithPID }; /* The list of options as used by the argparse.c code. */ static ARGPARSE_OPTS opts[] = { { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oTimeout, "timeout", 1, N_("|N|set LDAP timeout to N seconds")}, { oMulti, "multi", 0, N_("return all values in" " a record oriented format")}, { oProxy, "proxy", 2, N_("|NAME|ignore host part and connect through NAME")}, { oHost, "host", 2, N_("|NAME|connect to host NAME")}, { oPort, "port", 1, N_("|N|connect to port N")}, { oUser, "user", 2, N_("|NAME|use user NAME for authentication")}, { oPass, "pass", 2, N_("|PASS|use password PASS" " for authentication")}, { oEnvPass, "env-pass", 0, N_("take password from $DIRMNGR_LDAP_PASS")}, { oDN, "dn", 2, N_("|STRING|query DN STRING")}, { oFilter, "filter", 2, N_("|STRING|use STRING as filter expression")}, { oAttr, "attr", 2, N_("|STRING|return the attribute STRING")}, { oOnlySearchTimeout, "only-search-timeout", 0, "@"}, { oLogWithPID,"log-with-pid", 0, "@"}, { 0, NULL, 0, NULL } }; /* The usual structure for the program flags. */ static struct { int quiet; int verbose; struct timeval timeout; /* Timeout for the LDAP search functions. */ unsigned int alarm_timeout; /* And for the alarm based timeout. */ int multi; /* Note that we can't use const for the strings because ldap_* are not defined that way. */ char *proxy; /* Host and Port override. */ char *user; /* Authentication user. */ char *pass; /* Authentication password. */ char *host; /* Override host. */ int port; /* Override port. */ char *dn; /* Override DN. */ char *filter;/* Override filter. */ char *attr; /* Override attribute. */ } opt; /* Prototypes. */ static void catch_alarm (int dummy); static int process_url (const char *url); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 11: p = "dirmngr_ldap (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n"); break; case 41: p = _("Syntax: dirmngr_ldap [options] [URL]\n" "Internal LDAP helper for Dirmngr.\n" "Interface and options may change without notice.\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int any_err = 0; char *p; int only_search_timeout = 0; #ifdef HAVE_W32_SYSTEM /* Yeah, right. Sigh. */ +#error FIXME _setmode (_fileno (stdout), _O_BINARY); #endif set_strusage (my_strusage); log_set_prefix ("dirmngr_ldap", JNLIB_LOG_WITH_PREFIX); /* Setup I18N. */ i18n_init(); /* LDAP defaults */ opt.timeout.tv_sec = DEFAULT_LDAP_TIMEOUT; opt.timeout.tv_usec = 0; opt.alarm_timeout = 0; /* Parse the command line. */ 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 oQuiet: opt.quiet++; break; case oTimeout: opt.timeout.tv_sec = pargs.r.ret_int; opt.timeout.tv_usec = 0; opt.alarm_timeout = pargs.r.ret_int; break; case oOnlySearchTimeout: only_search_timeout = 1; break; case oMulti: opt.multi = 1; break; case oUser: opt.user = pargs.r.ret_str; break; case oPass: opt.pass = pargs.r.ret_str; break; case oEnvPass: opt.pass = getenv ("DIRMNGR_LDAP_PASS"); break; case oProxy: opt.proxy = pargs.r.ret_str; break; case oHost: opt.host = pargs.r.ret_str; break; case oPort: opt.port = pargs.r.ret_int; break; case oDN: opt.dn = pargs.r.ret_str; break; case oFilter: opt.filter = pargs.r.ret_str; break; case oAttr: opt.attr = pargs.r.ret_str; break; case oLogWithPID: { unsigned int oldflags; log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | JNLIB_LOG_WITH_PID); } break; default : pargs.err = 2; break; } } if (only_search_timeout) opt.alarm_timeout = 0; if (opt.proxy) { opt.host = xstrdup (opt.proxy); p = strchr (opt.host, ':'); if (p) { *p++ = 0; opt.port = atoi (p); } if (!opt.port) opt.port = 389; /* make sure ports gets overridden. */ } if (opt.port < 0 || opt.port > 65535) log_error (_("invalid port number %d\n"), opt.port); if (log_get_errorcount (0)) exit (2); if (argc < 1) usage (1); if (opt.alarm_timeout) { #ifndef HAVE_W32_SYSTEM # if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION) struct sigaction act; act.sa_handler = catch_alarm; sigemptyset (&act.sa_mask); act.sa_flags = 0; if (sigaction (SIGALRM,&act,NULL)) # else if (signal (SIGALRM, catch_alarm) == SIG_ERR) # endif log_fatal ("unable to register timeout handler\n"); #endif } for (; argc; argc--, argv++) if (process_url (*argv)) any_err = 1; return any_err; } static void catch_alarm (int dummy) { (void)dummy; _exit (10); } static void set_timeout (void) { #ifndef HAVE_W32_SYSTEM /* FIXME for W32. */ if (opt.alarm_timeout) alarm (opt.alarm_timeout); #endif } /* Helper for fetch_ldap(). */ static int print_ldap_entries (LDAP *ld, LDAPMessage *msg, char *want_attr) { LDAPMessage *item; int any = 0; for (item = ldap_first_entry (ld, msg); item; item = ldap_next_entry (ld, item)) { BerElement *berctx; char *attr; if (opt.verbose > 1) log_info (_("scanning result for attribute `%s'\n"), want_attr? want_attr : "[all]"); if (opt.multi) { /* Write item marker. */ - if (fwrite ("I\0\0\0\0", 5, 1, stdout) != 1) + if (es_fwrite ("I\0\0\0\0", 5, 1, es_stdout) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); return -1; } } for (attr = ldap_first_attribute (ld, item, &berctx); attr; attr = ldap_next_attribute (ld, item, berctx)) { struct berval **values; int idx; if (opt.verbose > 1) log_info (_(" available attribute `%s'\n"), attr); set_timeout (); /* I case we want only one attribute we do a case insensitive compare without the optional extension (i.e. ";binary"). Case insensitive is not really correct but the best we can do. */ if (want_attr) { char *cp1, *cp2; int cmpres; cp1 = strchr (want_attr, ';'); if (cp1) *cp1 = 0; cp2 = strchr (attr, ';'); if (cp2) *cp2 = 0; cmpres = ascii_strcasecmp (want_attr, attr); if (cp1) *cp1 = ';'; if (cp2) *cp2 = ';'; if (cmpres) { ldap_memfree (attr); continue; /* Not found: Try next attribute. */ } } values = ldap_get_values_len (ld, item, attr); if (!values) { if (opt.verbose) log_info (_("attribute `%s' not found\n"), attr); ldap_memfree (attr); continue; } if (opt.verbose) { log_info (_("found attribute `%s'\n"), attr); if (opt.verbose > 1) for (idx=0; values[idx]; idx++) log_info (" length[%d]=%d\n", idx, (int)values[0]->bv_len); } if (opt.multi) { /* Write attribute marker. */ unsigned char tmp[5]; size_t n = strlen (attr); tmp[0] = 'A'; tmp[1] = (n >> 24); tmp[2] = (n >> 16); tmp[3] = (n >> 8); tmp[4] = (n); - if (fwrite (tmp, 5, 1, stdout) != 1 - || fwrite (attr, n, 1, stdout) != 1) + if (es_fwrite (tmp, 5, 1, es_stdout) != 1 + || es_fwrite (attr, n, 1, es_stdout) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } } for (idx=0; values[idx]; idx++) { if (opt.multi) { /* Write value marker. */ unsigned char tmp[5]; size_t n = values[0]->bv_len; tmp[0] = 'V'; tmp[1] = (n >> 24); tmp[2] = (n >> 16); tmp[3] = (n >> 8); tmp[4] = (n); - if (fwrite (tmp, 5, 1, stdout) != 1) + if (es_fwrite (tmp, 5, 1, es_stdout) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } } #if 1 /* Note: this does not work for STDOUT on a Windows console, where it fails with "Not enough space" for CRLs which are 52 KB or larger. */ - if (fwrite (values[0]->bv_val, values[0]->bv_len, - 1, stdout) != 1) +#warning still true - implement in estream + if (es_fwrite (values[0]->bv_val, values[0]->bv_len, + 1, es_stdout) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } #else /* On Windows console STDOUT, we have to break up the writes into small parts. */ { int n = 0; while (n < values[0]->bv_len) { int cnt = values[0]->bv_len - n; /* The actual limit is (52 * 1024 - 1) on Windows XP SP2. */ #define MAX_CNT (32*1024) if (cnt > MAX_CNT) cnt = MAX_CNT; - if (fwrite (((char *) values[0]->bv_val) + n, cnt, 1, - stdout) != 1) + if (es_fwrite (((char *) values[0]->bv_val) + n, cnt, 1, + es_stdout) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } n += cnt; } } #endif any = 1; if (!opt.multi) break; /* Print only the first value. */ } ldap_value_free_len (values); ldap_memfree (attr); if (want_attr || !opt.multi) break; /* We only want to return the first attribute. */ } ber_free (berctx, 0); } if (opt.verbose > 1 && any) log_info ("result has been printed\n"); return any?0:-1; } /* Helper for the URL based LDAP query. */ static int fetch_ldap (const char *url, const LDAPURLDesc *ludp) { LDAP *ld; LDAPMessage *msg; int rc = 0; char *host, *dn, *filter, *attrs[2], *attr; int port; host = opt.host? opt.host : ludp->lud_host; port = opt.port? opt.port : ludp->lud_port; dn = opt.dn? opt.dn : ludp->lud_dn; filter = opt.filter? opt.filter : ludp->lud_filter; attrs[0] = opt.attr? opt.attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL; attrs[1] = NULL; attr = attrs[0]; if (!port) port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389; if (opt.verbose) { log_info (_("processing url `%s'\n"), url); if (opt.user) log_info (_(" user `%s'\n"), opt.user); if (opt.pass) log_info (_(" pass `%s'\n"), *opt.pass?"*****":""); if (host) log_info (_(" host `%s'\n"), host); log_info (_(" port %d\n"), port); if (dn) log_info (_(" DN `%s'\n"), dn); if (filter) log_info (_(" filter `%s'\n"), filter); if (opt.multi && !opt.attr && ludp->lud_attrs) { int i; for (i=0; ludp->lud_attrs[i]; i++) log_info (_(" attr `%s'\n"), ludp->lud_attrs[i]); } else if (attr) log_info (_(" attr `%s'\n"), attr); } if (!host || !*host) { log_error (_("no host name in `%s'\n"), url); return -1; } if (!opt.multi && !attr) { log_error (_("no attribute given for query `%s'\n"), url); return -1; } if (!opt.multi && !opt.attr && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1]) log_info (_("WARNING: using first attribute only\n")); set_timeout (); ld = ldap_init (host, port); if (!ld) { log_error (_("LDAP init to `%s:%d' failed: %s\n"), host, port, strerror (errno)); return -1; } if (ldap_simple_bind_s (ld, opt.user, opt.pass)) { log_error (_("binding to `%s:%d' failed: %s\n"), host, port, strerror (errno)); /* FIXME: Need deinit (ld)? */ return -1; } set_timeout (); rc = ldap_search_st (ld, dn, ludp->lud_scope, filter, opt.multi && !opt.attr && ludp->lud_attrs? ludp->lud_attrs:attrs, 0, &opt.timeout, &msg); if (rc == LDAP_SIZELIMIT_EXCEEDED && opt.multi) { - if (fwrite ("E\0\0\0\x09truncated", 14, 1, stdout) != 1) + if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, es_stdout) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); return -1; } } else if (rc) { log_error (_("searching `%s' failed: %s\n"), url, ldap_err2string (rc)); if (rc != LDAP_NO_SUCH_OBJECT) { /* FIXME: Need deinit (ld)? */ /* Hmmm: Do we need to released MSG in case of an error? */ return -1; } } rc = print_ldap_entries (ld, msg, opt.multi? NULL:attr); ldap_msgfree (msg); /* FIXME: Need deinit (ld)? */ return rc; } /* Main processing. Take the URL and run the LDAP query. The result is printed to stdout, errors are logged to the log stream. */ static int process_url (const char *url) { int rc; LDAPURLDesc *ludp = NULL; if (!ldap_is_ldap_url (url)) { log_error (_("`%s' is not an LDAP URL\n"), url); return -1; } if (ldap_url_parse (url, &ludp)) { log_error (_("`%s' is an invalid LDAP URL\n"), url); return -1; } rc = fetch_ldap (url, ludp); ldap_free_urldesc (ludp); return rc; } diff --git a/dirmngr/http.c b/dirmngr/http.c deleted file mode 100644 index b65a92adc..000000000 --- a/dirmngr/http.c +++ /dev/null @@ -1,1863 +0,0 @@ -/* http.c - HTTP protocol handler - * Copyright (C) 1999, 2001, 2002, 2003, 2004, - * 2006, 2009 Free Software Foundation, Inc. - * - * This file is part of GnuPG. - * - * GnuPG is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 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. - */ - -/* Simple HTTP client implementation. We try to keep the code as - self-contained as possible. There are some contraints however: - - - estream is required. We now require estream because it provides a - very useful and portable asprintf implementation and the fopencookie - function. - - stpcpy is required - - fixme: list other requirements. - - - - With HTTP_USE_GNUTLS support for https is provided (this also - requires estream). - - With HTTP_NO_WSASTARTUP the socket initialization is not done - under Windows. This is useful if the socket layer has already - been initialized elsewhere. This also avoids the installation of - an exit handler to cleanup the socket layer. -*/ - -#warning Duplicated code with common/http.c - -#ifdef HAVE_CONFIG_H -# include -#endif -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_W32_SYSTEM -# include -#else /*!HAVE_W32_SYSTEM*/ -# include -# include -# include -# include -# include -# include -# include -#endif /*!HAVE_W32_SYSTEM*/ - -#include - -#ifdef HTTP_USE_GNUTLS -# include -/* For non-understandable reasons GNUTLS dropped the _t suffix from - all types. yes, ISO-C might be read as this but there are still - other name space conflicts and using _t is actually a Good - Thing. */ -typedef gnutls_session gnutls_session_t; -typedef gnutls_transport_ptr gnutls_transport_ptr_t; -#endif /*HTTP_USE_GNUTLS*/ - -#ifdef TEST -#undef USE_DNS_SRV -#endif - -#include "util.h" -#include "i18n.h" -#include "http.h" -#ifdef USE_DNS_SRV -#include "srv.h" -#else /*!USE_DNS_SRV*/ -/* If we are not compiling with SRV record support we provide stub - data structures. */ -#ifndef MAXDNAME -#define MAXDNAME 1025 -#endif -struct srventry -{ - unsigned short priority; - unsigned short weight; - unsigned short port; - int run_count; - char target[MAXDNAME]; -}; -#endif/*!USE_DNS_SRV*/ - - -#ifdef HAVE_W32_SYSTEM -#define sock_close(a) closesocket(a) -#else -#define sock_close(a) close(a) -#endif - -#ifndef EAGAIN -#define EAGAIN EWOULDBLOCK -#endif - -#define HTTP_PROXY_ENV "http_proxy" -#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */ -#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ - "01234567890@" \ - "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" - -/* A long counter type. */ -#ifdef HAVE_STRTOULL -typedef unsigned long long longcounter_t; -#define counter_strtoul(a) strtoull ((a), NULL, 10) -#else -typedef unsigned long longcounter_t; -#define counter_strtoul(a) strtoul ((a), NULL, 10) -#endif - -#ifndef HTTP_USE_GNUTLS -typedef void * gnutls_session_t; -#endif - -static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part); -static int remove_escapes (char *string); -static int insert_escapes (char *buffer, const char *string, - const char *special); -static uri_tuple_t parse_tuple (char *string); -static gpg_error_t send_request (http_t hd, - const char *auth, const char *proxy); -static char *build_rel_path (parsed_uri_t uri); -static gpg_error_t parse_response (http_t hd); - -static int connect_server (const char *server, unsigned short port, - unsigned int flags, const char *srvtag); - -static ssize_t cookie_read (void *cookie, void *buffer, size_t size); -static ssize_t cookie_write (void *cookie, const void *buffer, size_t size); -static int cookie_close (void *cookie); - -static es_cookie_io_functions_t cookie_functions = - { - cookie_read, - cookie_write, - NULL, - cookie_close - }; - -struct cookie_s -{ - int fd; /* File descriptor or -1 if already closed. */ - gnutls_session_t tls_session; /* TLS session context or NULL if not used. */ - - /* The remaining content length and a flag telling whether to use - the content length. */ - longcounter_t content_length; - unsigned int content_length_valid:1; - - /* Flag to communicate with the close handler. */ - unsigned int keep_socket:1; -}; -typedef struct cookie_s *cookie_t; - - -#ifdef HTTP_USE_GNUTLS -static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int); -#endif /*HTTP_USE_GNUTLS*/ - - -/* An object to save header lines. */ -struct header_s -{ - struct header_s *next; - char *value; /* The value of the header (malloced). */ - char name[1]; /* The name of the header (canonicalized). */ -}; -typedef struct header_s *header_t; - - -/* Our handle context. */ -struct http_context_s -{ - unsigned int status_code; - int sock; - unsigned int in_data:1; - unsigned int is_http_0_9:1; - estream_t fp_read; - estream_t fp_write; - void *write_cookie; - void *read_cookie; - void *tls_context; - parsed_uri_t uri; - http_req_t req_type; - char *buffer; /* Line buffer. */ - size_t buffer_size; - unsigned int flags; - header_t headers; /* Received headers. */ -}; - - - - -#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP) - -#if GNUPG_MAJOR_VERSION == 1 -#define REQ_WINSOCK_MAJOR 1 -#define REQ_WINSOCK_MINOR 1 -#else -#define REQ_WINSOCK_MAJOR 2 -#define REQ_WINSOCK_MINOR 2 -#endif - - -static void -deinit_sockets (void) -{ - WSACleanup(); -} - -static void -init_sockets (void) -{ - static int initialized; - static WSADATA wsdata; - - if (initialized) - return; - - if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) ) - { - log_error ("error initializing socket library: ec=%d\n", - (int)WSAGetLastError () ); - return; - } - if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR - || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR ) - { - log_error ("socket library version is %x.%x - but %d.%d needed\n", - LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion), - REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR); - WSACleanup(); - return; - } - atexit ( deinit_sockets ); - initialized = 1; -} -#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/ - - - -/* - * Helper function to create an HTTP header with hex encoded data. A - * new buffer is returned. This buffer is the concatenation of the - * string PREFIX, the hex-encoded DATA of length LEN and the string - * SUFFIX. On error NULL is returned and ERRNO set. - */ -static char * -make_header_line (const char *prefix, const char *suffix, - const void *data, size_t len ) -{ - static unsigned char bintoasc[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - const unsigned int *s = data; - char *buffer, *p; - - buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1); - if (!buffer) - return NULL; - p = stpcpy (buffer, prefix); - for ( ; len >= 3 ; len -= 3, s += 3 ) - { - *p++ = bintoasc[(s[0] >> 2) & 077]; - *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; - *p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077]; - *p++ = bintoasc[s[2]&077]; - } - if ( len == 2 ) - { - *p++ = bintoasc[(s[0] >> 2) & 077]; - *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; - *p++ = bintoasc[((s[1]<<2)&074)]; - *p++ = '='; - } - else if ( len == 1 ) - { - *p++ = bintoasc[(s[0] >> 2) & 077]; - *p++ = bintoasc[(s[0] <<4)&060]; - *p++ = '='; - *p++ = '='; - } - strcpy (p, suffix); - return buffer; -} - - - - -void -http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) ) -{ -#ifdef HTTP_USE_GNUTLS - tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb; -#else - (void)cb; -#endif -} - - - -/* Start a HTTP retrieval and return on success in R_HD a context - pointer for completing the the request and to wait for the - response. */ -gpg_error_t -http_open (http_t *r_hd, http_req_t reqtype, const char *url, - const char *auth, unsigned int flags, const char *proxy, - void *tls_context) -{ - gpg_error_t err; - http_t hd; - - *r_hd = NULL; - - if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST)) - return gpg_error (GPG_ERR_INV_ARG); - - /* Make need_header default unless ignore_cl is set. We might want - to drop the need_header entirely. */ - if (!(flags & HTTP_FLAG_IGNORE_CL)) - flags |= HTTP_FLAG_NEED_HEADER; - - /* Create the handle. */ - hd = xtrycalloc (1, sizeof *hd); - if (!hd) - return gpg_error_from_syserror (); - hd->sock = -1; - hd->req_type = reqtype; - hd->flags = flags; - hd->tls_context = tls_context; - - err = http_parse_uri (&hd->uri, url); - if (!err) - err = send_request (hd, auth, proxy); - - if (err) - { - if (!hd->fp_read && !hd->fp_write && hd->sock != -1) - sock_close (hd->sock); - if (hd->fp_read) - es_fclose (hd->fp_read); - if (hd->fp_write) - es_fclose (hd->fp_write); - http_release_parsed_uri (hd->uri); - xfree (hd); - } - else - *r_hd = hd; - return err; -} - - -void -http_start_data (http_t hd) -{ - if (!hd->in_data) - { - es_fputs ("\r\n", hd->fp_write); - es_fflush (hd->fp_write); - hd->in_data = 1; - } - else - es_fflush (hd->fp_write); -} - - -gpg_error_t -http_wait_response (http_t hd) -{ - gpg_error_t err; - cookie_t cookie; - - /* Make sure that we are in the data. */ - http_start_data (hd); - - cookie = hd->write_cookie; - if (!cookie) - return gpg_error (GPG_ERR_INTERNAL); - - /* Close the write stream but keep the socket open. */ - cookie->keep_socket = 1; - es_fclose (hd->fp_write); - hd->fp_write = NULL; - hd->write_cookie = NULL; - - /* Shutdown one end of the socket is desired. As per HTTP/1.0 this - is not required but some very old servers (e.g. the original pksd - key server didn't worked without it. */ - if ((hd->flags & HTTP_FLAG_SHUTDOWN)) - shutdown (hd->sock, 1); - hd->in_data = 0; - - /* Create a new cookie and a stream for reading. */ - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) - return gpg_error_from_syserror (); - cookie->fd = hd->sock; - if (hd->uri->use_tls) - cookie->tls_session = hd->tls_context; - - hd->read_cookie = cookie; - hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); - if (!hd->fp_read) - { - xfree (cookie); - hd->read_cookie = NULL; - return gpg_error_from_syserror (); - } - - err = parse_response (hd); - return err; -} - - -/* Convenience function to send a request and wait for the response. - Closes the handle on error. If PROXY is not NULL, this value will - be used as an HTTP proxy and any enabled $http_proxy gets - ignored. */ -gpg_error_t -http_open_document (http_t *r_hd, const char *document, - const char *auth, unsigned int flags, const char *proxy, - void *tls_context) -{ - gpg_error_t err; - - err = http_open (r_hd, HTTP_REQ_GET, document, auth, flags, - proxy, tls_context); - if (err) - return err; - - err = http_wait_response (*r_hd); - if (err) - http_close (*r_hd, 0); - - return err; -} - - -void -http_close (http_t hd, int keep_read_stream) -{ - if (!hd) - return; - if (!hd->fp_read && !hd->fp_write && hd->sock != -1) - sock_close (hd->sock); - if (hd->fp_read && !keep_read_stream) - es_fclose (hd->fp_read); - if (hd->fp_write) - es_fclose (hd->fp_write); - http_release_parsed_uri (hd->uri); - while (hd->headers) - { - header_t tmp = hd->headers->next; - xfree (hd->headers->value); - xfree (hd->headers); - hd->headers = tmp; - } - xfree (hd->buffer); - xfree (hd); -} - - -estream_t -http_get_read_ptr (http_t hd) -{ - return hd?hd->fp_read:NULL; -} - -estream_t -http_get_write_ptr (http_t hd) -{ - return hd?hd->fp_write:NULL; -} - -unsigned int -http_get_status_code (http_t hd) -{ - return hd?hd->status_code:0; -} - - - -/* - * Parse an URI and put the result into the newly allocated RET_URI. - * The caller must always use release_parsed_uri() to releases the - * resources (even on error). - */ -gpg_error_t -http_parse_uri (parsed_uri_t * ret_uri, const char *uri) -{ - *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri)); - strcpy ((*ret_uri)->buffer, uri); - return do_parse_uri (*ret_uri, 0); -} - -void -http_release_parsed_uri (parsed_uri_t uri) -{ - if (uri) - { - uri_tuple_t r, r2; - - for (r = uri->query; r; r = r2) - { - r2 = r->next; - xfree (r); - } - xfree (uri); - } -} - - -static gpg_error_t -do_parse_uri (parsed_uri_t uri, int only_local_part) -{ - uri_tuple_t *tail; - char *p, *p2, *p3, *pp; - int n; - - p = uri->buffer; - n = strlen (uri->buffer); - - /* Initialize all fields to an empty string or an empty list. */ - uri->scheme = uri->host = uri->path = p + n; - uri->port = 0; - uri->params = uri->query = NULL; - uri->use_tls = 0; - - /* A quick validity check. */ - if (strspn (p, VALID_URI_CHARS) != n) - return gpg_error (GPG_ERR_BAD_URI); /* Invalid characters found. */ - - if (!only_local_part) - { - /* Find the scheme. */ - if (!(p2 = strchr (p, ':')) || p2 == p) - return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */ - *p2++ = 0; - for (pp=p; *pp; pp++) - *pp = tolower (*(unsigned char*)pp); - uri->scheme = p; - if (!strcmp (uri->scheme, "http")) - uri->port = 80; -#ifdef HTTP_USE_GNUTLS - else if (!strcmp (uri->scheme, "https")) - { - uri->port = 443; - uri->use_tls = 1; - } -#endif - else - return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */ - - p = p2; - - /* Find the hostname */ - if (*p != '/') - return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */ - - p++; - if (*p == '/') /* There seems to be a hostname. */ - { - p++; - if ((p2 = strchr (p, '/'))) - *p2++ = 0; - - /* Check for username/password encoding */ - if ((p3 = strchr (p, '@'))) - { - uri->auth = p; - *p3++ = '\0'; - p = p3; - } - - for (pp=p; *pp; pp++) - *pp = tolower (*(unsigned char*)pp); - uri->host = p; - if ((p3 = strchr (p, ':'))) - { - *p3++ = 0; - uri->port = atoi (p3); - } - - uri->host = p; - if ((n = remove_escapes (uri->host)) < 0) - return gpg_error (GPG_ERR_BAD_URI); - if (n != strlen (p)) - return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */ - p = p2 ? p2 : NULL; - } - } /* End global URI part. */ - - /* Parse the pathname part */ - if (!p || !*p) - return 0; /* We don't have a path. Okay. */ - - /* TODO: Here we have to check params. */ - - /* Do we have a query part? */ - if ((p2 = strchr (p, '?'))) - *p2++ = 0; - - uri->path = p; - if ((n = remove_escapes (p)) < 0) - return gpg_error (GPG_ERR_BAD_URI); - if (n != strlen (p)) - return gpg_error (GPG_ERR_BAD_URI); /* Path includes a Nul. */ - p = p2 ? p2 : NULL; - - if (!p || !*p) - return 0; /* We don't have a query string. Okay. */ - - /* Now parse the query string. */ - tail = &uri->query; - for (;;) - { - uri_tuple_t elem; - - if ((p2 = strchr (p, '&'))) - *p2++ = 0; - if (!(elem = parse_tuple (p))) - return gpg_error (GPG_ERR_BAD_URI); - *tail = elem; - tail = &elem->next; - - if (!p2) - break; /* Ready. */ - p = p2; - } - - return 0; -} - - -/* - * Remove all %xx escapes; this is done in-place. Returns: New length - * of the string. - */ -static int -remove_escapes (char *string) -{ - int n = 0; - unsigned char *p, *s; - - for (p = s = (unsigned char*)string; *s; s++) - { - if (*s == '%') - { - if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2])) - { - s++; - *p = *s >= '0' && *s <= '9' ? *s - '0' : - *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; - *p <<= 4; - s++; - *p |= *s >= '0' && *s <= '9' ? *s - '0' : - *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; - p++; - n++; - } - else - { - *p++ = *s++; - if (*s) - *p++ = *s++; - if (*s) - *p++ = *s++; - if (*s) - *p = 0; - return -1; /* Bad URI. */ - } - } - else - { - *p++ = *s; - n++; - } - } - *p = 0; /* Make sure to keep a string terminator. */ - return n; -} - - -static int -insert_escapes (char *buffer, const char *string, - const char *special) -{ - const unsigned char *s = (const unsigned char*)string; - int n = 0; - - for (; *s; s++) - { - if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s)) - { - if (buffer) - *(unsigned char*)buffer++ = *s; - n++; - } - else - { - if (buffer) - { - sprintf (buffer, "%%%02X", *s); - buffer += 3; - } - n += 3; - } - } - return n; -} - - -/* Allocate a new string from STRING using standard HTTP escaping as - well as escaping of characters given in SPECIALS. A common pattern - for SPECIALS is "%;?&=". However it depends on the needs, for - example "+" and "/: often needs to be escaped too. Returns NULL on - failure and sets ERRNO. */ -char * -http_escape_string (const char *string, const char *specials) -{ - int n; - char *buf; - - n = insert_escapes (NULL, string, specials); - buf = xtrymalloc (n+1); - if (buf) - { - insert_escapes (buf, string, specials); - buf[n] = 0; - } - return buf; -} - - - -static uri_tuple_t -parse_tuple (char *string) -{ - char *p = string; - char *p2; - int n; - uri_tuple_t tuple; - - if ((p2 = strchr (p, '='))) - *p2++ = 0; - if ((n = remove_escapes (p)) < 0) - return NULL; /* Bad URI. */ - if (n != strlen (p)) - return NULL; /* Name with a Nul in it. */ - tuple = xtrycalloc (1, sizeof *tuple); - if (!tuple) - return NULL; /* Out of core. */ - tuple->name = p; - if (!p2) /* We have only the name, so we assume an empty value string. */ - { - tuple->value = p + strlen (p); - tuple->valuelen = 0; - tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */ - } - else /* Name and value. */ - { - if ((n = remove_escapes (p2)) < 0) - { - xfree (tuple); - return NULL; /* Bad URI. */ - } - tuple->value = p2; - tuple->valuelen = n; - } - return tuple; -} - - -/* - * Send a HTTP request to the server - * Returns 0 if the request was successful - */ -static gpg_error_t -send_request (http_t hd, const char *auth, const char *proxy) -{ - gnutls_session_t tls_session; - gpg_error_t err; - const char *server; - char *request, *p; - unsigned short port; - const char *http_proxy = NULL; - char *proxy_authstr = NULL; - char *authstr = NULL; - int save_errno; - cookie_t cookie; - - - tls_session = hd->tls_context; - if (hd->uri->use_tls && !tls_session) - { - log_error ("TLS requested but no GNUTLS context provided\n"); - return gpg_error (GPG_ERR_INTERNAL); - } - - server = *hd->uri->host ? hd->uri->host : "localhost"; - port = hd->uri->port ? hd->uri->port : 80; - - if ( (proxy && *proxy) - || ( (hd->flags & HTTP_FLAG_TRY_PROXY) - && (http_proxy = getenv (HTTP_PROXY_ENV)) - && *http_proxy )) - { - parsed_uri_t uri; - - if (proxy) - http_proxy = proxy; - - err = http_parse_uri (&uri, http_proxy); - if (err) - { - log_error ("invalid HTTP proxy (%s): %s\n", - http_proxy, gpg_strerror (err)); - http_release_parsed_uri (uri); - return gpg_error (GPG_ERR_CONFIGURATION); - - } - - if (uri->auth) - { - remove_escapes (uri->auth); - proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", - "\r\n", - uri->auth, strlen(uri->auth)); - if (!proxy_authstr) - { - err = gpg_error_from_syserror (); - http_release_parsed_uri (uri); - return err; - } - } - - hd->sock = connect_server (*uri->host ? uri->host : "localhost", - uri->port ? uri->port : 80, - hd->flags, hd->uri->scheme); - save_errno = errno; - http_release_parsed_uri (uri); - } - else - { - hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme); - save_errno = errno; - } - - if (hd->sock == -1) - { - xfree (proxy_authstr); - return (save_errno - ? gpg_error_from_errno (save_errno) - : gpg_error (GPG_ERR_NOT_FOUND)); - } - -#ifdef HTTP_USE_GNUTLS - if (hd->uri->use_tls) - { - int rc; - - gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock); - do - { - rc = gnutls_handshake (tls_session); - } - while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN); - if (rc < 0) - { - log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); - xfree (proxy_authstr); - return gpg_error (GPG_ERR_NETWORK); - } - - if (tls_callback) - { - err = tls_callback (hd, tls_session, 0); - if (err) - { - log_info ("TLS connection authentication failed: %s\n", - gpg_strerror (err)); - xfree (proxy_authstr); - return err; - } - } - } -#endif /*HTTP_USE_GNUTLS*/ - - if (auth || hd->uri->auth) - { - char *myauth; - - if (auth) - { - myauth = xtrystrdup (auth); - if (!myauth) - { - xfree (proxy_authstr); - return gpg_error_from_syserror (); - } - remove_escapes (myauth); - } - else - { - remove_escapes (hd->uri->auth); - myauth = hd->uri->auth; - } - - authstr = make_header_line ("Authorization: Basic %s", "\r\n", - myauth, strlen (myauth)); - if (auth) - xfree (myauth); - - if (!authstr) - { - xfree (proxy_authstr); - return gpg_error_from_syserror (); - } - } - - p = build_rel_path (hd->uri); - if (!p) - return gpg_error_from_syserror (); - - if (http_proxy && *http_proxy) - { - request = es_asprintf - ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s", - hd->req_type == HTTP_REQ_GET ? "GET" : - hd->req_type == HTTP_REQ_HEAD ? "HEAD" : - hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - server, port, *p == '/' ? "" : "/", p, - authstr ? authstr : "", - proxy_authstr ? proxy_authstr : ""); - } - else - { - char portstr[35]; - - if (port == 80) - *portstr = 0; - else - snprintf (portstr, sizeof portstr, ":%u", port); - - request = es_asprintf - ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", - hd->req_type == HTTP_REQ_GET ? "GET" : - hd->req_type == HTTP_REQ_HEAD ? "HEAD" : - hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - *p == '/' ? "" : "/", p, server, portstr, - authstr? authstr:""); - } - xfree (p); - if (!request) - { - err = gpg_error_from_syserror (); - xfree (authstr); - xfree (proxy_authstr); - return err; - } - - /* First setup estream so that we can write even the first line - using estream. This is also required for the sake of gnutls. */ - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) - { - err = gpg_error_from_syserror (); - goto leave; - } - cookie->fd = hd->sock; - hd->write_cookie = cookie; - if (hd->uri->use_tls) - cookie->tls_session = tls_session; - hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); - if (!hd->fp_write) - { - xfree (cookie); - hd->write_cookie = NULL; - err = gpg_error_from_syserror (); - } - else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) - err = gpg_error_from_syserror (); - else - err = 0; - - leave: - es_free (request); - xfree (authstr); - xfree (proxy_authstr); - - return err; -} - - -/* - * Build the relative path from the parsed URI. Minimal - * implementation. May return NULL in case of memory failure; errno - * is then set accordingly. - */ -static char * -build_rel_path (parsed_uri_t uri) -{ - uri_tuple_t r; - char *rel_path, *p; - int n; - - /* Count the needed space. */ - n = insert_escapes (NULL, uri->path, "%;?&"); - /* TODO: build params. */ - for (r = uri->query; r; r = r->next) - { - n++; /* '?'/'&' */ - n += insert_escapes (NULL, r->name, "%;?&="); - if (!r->no_value) - { - n++; /* '=' */ - n += insert_escapes (NULL, r->value, "%;?&="); - } - } - n++; - - /* Now allocate and copy. */ - p = rel_path = xtrymalloc (n); - if (!p) - return NULL; - n = insert_escapes (p, uri->path, "%;?&"); - p += n; - /* TODO: add params. */ - for (r = uri->query; r; r = r->next) - { - *p++ = r == uri->query ? '?' : '&'; - n = insert_escapes (p, r->name, "%;?&="); - p += n; - if (!r->no_value) - { - *p++ = '='; - /* TODO: Use valuelen. */ - n = insert_escapes (p, r->value, "%;?&="); - p += n; - } - } - *p = 0; - return rel_path; -} - - -/* Transform a header name into a standard capitalized format; e.g. - "Content-Type". Conversion stops at the colon. As usual we don't - use the localized versions of ctype.h. */ -static void -capitalize_header_name (char *name) -{ - int first = 1; - - for (; *name && *name != ':'; name++) - { - if (*name == '-') - first = 1; - else if (first) - { - if (*name >= 'a' && *name <= 'z') - *name = *name - 'a' + 'A'; - first = 0; - } - else if (*name >= 'A' && *name <= 'Z') - *name = *name - 'A' + 'a'; - } -} - - -/* Store an HTTP header line in LINE away. Line continuation is - supported as well as merging of headers with the same name. This - function may modify LINE. */ -static gpg_error_t -store_header (http_t hd, char *line) -{ - size_t n; - char *p, *value; - header_t h; - - n = strlen (line); - if (n && line[n-1] == '\n') - { - line[--n] = 0; - if (n && line[n-1] == '\r') - line[--n] = 0; - } - if (!n) /* we are never called to hit this. */ - return gpg_error (GPG_ERR_BUG); - if (*line == ' ' || *line == '\t') - { - /* Continuation. This won't happen too often as it is not - recommended. We use a straightforward implementaion. */ - if (!hd->headers) - return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); - n += strlen (hd->headers->value); - p = xtrymalloc (n+1); - if (!p) - return gpg_error_from_syserror (); - strcpy (stpcpy (p, hd->headers->value), line); - xfree (hd->headers->value); - hd->headers->value = p; - return 0; - } - - capitalize_header_name (line); - p = strchr (line, ':'); - if (!p) - return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); - *p++ = 0; - while (*p == ' ' || *p == '\t') - p++; - value = p; - - for (h=hd->headers; h; h = h->next) - if ( !strcmp (h->name, line) ) - break; - if (h) - { - /* We have already seen a line with that name. Thus we assume - it is a comma separated list and merge them. */ - p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1); - if (!p) - return gpg_error_from_syserror (); - strcpy (stpcpy (stpcpy (p, h->value), ","), value); - xfree (h->value); - h->value = p; - return 0; - } - - /* Append a new header. */ - h = xtrymalloc (sizeof *h + strlen (line)); - if (!h) - return gpg_error_from_syserror (); - strcpy (h->name, line); - h->value = xtrymalloc (strlen (value)+1); - if (!h->value) - { - xfree (h); - return gpg_error_from_syserror (); - } - strcpy (h->value, value); - h->next = hd->headers; - hd->headers = h; - - return 0; -} - - -/* Return the header NAME from the last response. The returned value - is valid as along as HD has not been closed and no othe request has - been send. If the header was not found, NULL is returned. Name - must be canonicalized, that is the first letter of each dash - delimited part must be uppercase and all other letters lowercase. - Note that the context must have been opened with the - HTTP_FLAG_NEED_HEADER. */ -const char * -http_get_header (http_t hd, const char *name) -{ - header_t h; - - for (h=hd->headers; h; h = h->next) - if ( !strcmp (h->name, name) ) - return h->value; - return NULL; -} - - - -/* - * Parse the response from a server. - * Returns: Errorcode and sets some files in the handle - */ -static gpg_error_t -parse_response (http_t hd) -{ - char *line, *p, *p2; - size_t maxlen, len; - cookie_t cookie = hd->read_cookie; - const char *s; - - /* Delete old header lines. */ - while (hd->headers) - { - header_t tmp = hd->headers->next; - xfree (hd->headers->value); - xfree (hd->headers); - hd->headers = tmp; - } - - /* Wait for the status line. */ - do - { - maxlen = MAX_LINELEN; - len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); - line = hd->buffer; - if (!line) - return gpg_error_from_syserror (); /* Out of core. */ - if (!maxlen) - return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */ - if (!len) - return gpg_error (GPG_ERR_EOF); - if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) - log_info ("RESP: `%.*s'\n", - (int)strlen(line)-(*line&&line[1]?2:0),line); - } - while (!*line); - - if ((p = strchr (line, '/'))) - *p++ = 0; - if (!p || strcmp (line, "HTTP")) - return 0; /* Assume http 0.9. */ - - if ((p2 = strpbrk (p, " \t"))) - { - *p2++ = 0; - p2 += strspn (p2, " \t"); - } - if (!p2) - return 0; /* Also assume http 0.9. */ - p = p2; - /* TODO: Add HTTP version number check. */ - if ((p2 = strpbrk (p, " \t"))) - *p2++ = 0; - if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1]) - || !isdigit ((unsigned int)p[2]) || p[3]) - { - /* Malformed HTTP status code - assume http 0.9. */ - hd->is_http_0_9 = 1; - hd->status_code = 200; - return 0; - } - hd->status_code = atoi (p); - - /* Skip all the header lines and wait for the empty line. */ - do - { - maxlen = MAX_LINELEN; - len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); - line = hd->buffer; - if (!line) - return gpg_error_from_syserror (); /* Out of core. */ - /* Note, that we can silently ignore truncated lines. */ - if (!len) - return gpg_error (GPG_ERR_EOF); - /* Trim line endings of empty lines. */ - if ((*line == '\r' && line[1] == '\n') || *line == '\n') - *line = 0; - if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) - log_info ("RESP: `%.*s'\n", - (int)strlen(line)-(*line&&line[1]?2:0),line); - if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line ) - { - gpg_error_t err = store_header (hd, line); - if (err) - return err; - } - } - while (len && *line); - - cookie->content_length_valid = 0; - if (!(hd->flags & HTTP_FLAG_IGNORE_CL)) - { - s = http_get_header (hd, "Content-Length"); - if (s) - { - cookie->content_length_valid = 1; - cookie->content_length = counter_strtoul (s); - } - } - - return 0; -} - -#if 0 -static int -start_server () -{ - struct sockaddr_in mya; - struct sockaddr_in peer; - int fd, client; - fd_set rfds; - int addrlen; - int i; - - if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1) - { - log_error ("socket() failed: %s\n", strerror (errno)); - return -1; - } - i = 1; - if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i))) - log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); - - mya.sin_family = AF_INET; - memset (&mya.sin_addr, 0, sizeof (mya.sin_addr)); - mya.sin_port = htons (11371); - - if (bind (fd, (struct sockaddr *) &mya, sizeof (mya))) - { - log_error ("bind to port 11371 failed: %s\n", strerror (errno)); - sock_close (fd); - return -1; - } - - if (listen (fd, 5)) - { - log_error ("listen failed: %s\n", strerror (errno)); - sock_close (fd); - return -1; - } - - for (;;) - { - FD_ZERO (&rfds); - FD_SET (fd, &rfds); - - if (select (fd + 1, &rfds, NULL, NULL, NULL) <= 0) - continue; /* ignore any errors */ - - if (!FD_ISSET (fd, &rfds)) - continue; - - addrlen = sizeof peer; - client = accept (fd, (struct sockaddr *) &peer, &addrlen); - if (client == -1) - continue; /* oops */ - - log_info ("connect from %s\n", inet_ntoa (peer.sin_addr)); - - fflush (stdout); - fflush (stderr); - if (!fork ()) - { - int c; - FILE *fp; - - fp = fdopen (client, "r"); - while ((c = getc (fp)) != EOF) - putchar (c); - fclose (fp); - exit (0); - } - sock_close (client); - } - - - return 0; -} -#endif - -/* Actually connect to a server. Returns the file descripto or -1 on - error. ERRNO is set on error. */ -static int -connect_server (const char *server, unsigned short port, - unsigned int flags, const char *srvtag) -{ - int sock = -1; - int srvcount = 0; - int hostfound = 0; - int srv, connected; - int last_errno = 0; - struct srventry *serverlist = NULL; - -#ifdef HAVE_W32_SYSTEM - unsigned long inaddr; - -#ifndef HTTP_NO_WSASTARTUP - init_sockets (); -#endif - /* Win32 gethostbyname doesn't handle IP addresses internally, so we - try inet_addr first on that platform only. */ - inaddr = inet_addr(server); - if ( inaddr != INADDR_NONE ) - { - struct sockaddr_in addr; - - memset(&addr,0,sizeof(addr)); - - sock = socket(AF_INET,SOCK_STREAM,0); - if ( sock==INVALID_SOCKET ) - { - log_error("error creating socket: ec=%d\n",(int)WSAGetLastError()); - return -1; - } - - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr)); - - if (!connect (sock,(struct sockaddr *)&addr,sizeof(addr)) ) - return sock; - sock_close(sock); - return -1; - } -#endif /*HAVE_W32_SYSTEM*/ - -#ifdef USE_DNS_SRV - /* Do the SRV thing */ - if ((flags & HTTP_FLAG_TRY_SRV) && srvtag) - { - /* We're using SRV, so append the tags. */ - if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME) - { - char srvname[MAXDNAME]; - - stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag), - "._tcp."), server); - srvcount = getsrv (srvname, &serverlist); - } - } -#else /*!USE_DNS_SRV*/ - (void)flags; - (void)srvtag; -#endif /*!USE_DNS_SRV*/ - - if (!serverlist) - { - /* Either we're not using SRV, or the SRV lookup failed. Make - up a fake SRV record. */ - serverlist = xtrycalloc (1, sizeof *serverlist); - if (!serverlist) - return -1; /* Out of core. */ - serverlist->port = port; - strncpy (serverlist->target, server, MAXDNAME); - serverlist->target[MAXDNAME-1] = '\0'; - srvcount = 1; - } - -#ifdef HAVE_GETADDRINFO - connected = 0; - for (srv=0; srv < srvcount && !connected; srv++) - { - struct addrinfo hints, *res, *ai; - char portstr[35]; - - sprintf (portstr, "%hu", port); - memset (&hints, 0, sizeof (hints)); - hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res)) - continue; /* Not found - try next one. */ - hostfound = 1; - - for (ai = res; ai && !connected; ai = ai->ai_next) - { - if (sock != -1) - sock_close (sock); - sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sock == -1) - { - int save_errno = errno; - log_error ("error creating socket: %s\n", strerror (errno)); - freeaddrinfo (res); - xfree (serverlist); - errno = save_errno; - return -1; - } - - if (connect (sock, ai->ai_addr, ai->ai_addrlen)) - last_errno = errno; - else - connected = 1; - } - freeaddrinfo (res); - } -#else /* !HAVE_GETADDRINFO */ - connected = 0; - for (srv=0; srv < srvcount && !connected; srv++) - { - int i; - struct hostent *host = NULL; - struct sockaddr_in addr; - - /* Note: This code is not thread-safe. */ - - memset (&addr, 0, sizeof (addr)); - host = gethostbyname (serverlist[srv].target); - if (!host) - continue; - hostfound = 1; - - if (sock != -1) - sock_close (sock); - sock = socket (host->h_addrtype, SOCK_STREAM, 0); - if (sock == -1) - { - log_error (_("error creating socket: %s\n"), strerror (errno)); - xfree (serverlist); - return -1; - } - - addr.sin_family = host->h_addrtype; - if (addr.sin_family != AF_INET) - { - log_error ("unknown address family for `%s'\n", - serverlist[srv].target); - xfree (serverlist); - return -1; - } - addr.sin_port = htons (serverlist[srv].port); - if (host->h_length != 4) - { - log_error ("illegal address length for `%s'\n", - serverlist[srv].target); - xfree (serverlist); - return -1; - } - - /* Try all A records until one responds. */ - for (i = 0; host->h_addr_list[i] && !connected; i++) - { - memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length); - if (connect (sock, (struct sockaddr *) &addr, sizeof (addr))) - last_errno = errno; - else - { - connected = 1; - break; - } - } - } -#endif /* !HAVE_GETADDRINFO */ - - xfree (serverlist); - - if (!connected) - { -#ifdef HAVE_W32_SYSTEM - log_error ("can't connect to `%s': %s%sec=%d\n", - server, - hostfound? "":_("host not found"), - hostfound? "":" - ", (int)WSAGetLastError()); -#else - log_error ("can't connect to `%s': %s\n", - server, - hostfound? strerror (last_errno):"host not found"); -#endif - if (sock != -1) - sock_close (sock); - errno = last_errno; - return -1; - } - return sock; -} - - - - -/* Read handler for estream. */ -static ssize_t -cookie_read (void *cookie, void *buffer, size_t size) -{ - cookie_t c = cookie; - int nread; - - if (c->content_length_valid) - { - if (!c->content_length) - return 0; /* EOF */ - if (c->content_length < size) - size = c->content_length; - } - -#ifdef HTTP_USE_GNUTLS - if (c->tls_session) - { - again: - nread = gnutls_record_recv (c->tls_session, buffer, size); - if (nread < 0) - { - if (nread == GNUTLS_E_INTERRUPTED) - goto again; - if (nread == GNUTLS_E_AGAIN) - { - struct timeval tv; - - tv.tv_sec = 0; - tv.tv_usec = 50000; - select (0, NULL, NULL, NULL, &tv); - goto again; - } - if (nread == GNUTLS_E_REHANDSHAKE) - goto again; /* A client is allowed to just ignore this request. */ - log_info ("TLS network read failed: %s\n", gnutls_strerror (nread)); - errno = EIO; - return -1; - } - } - else -#endif /*HTTP_USE_GNUTLS*/ - { - do - { - nread = pth_read (c->fd, buffer, size); - } - while (nread == -1 && errno == EINTR); - } - - if (c->content_length_valid && nread > 0) - { - if (nread < c->content_length) - c->content_length -= nread; - else - c->content_length = 0; - } - - return nread; -} - -/* Write handler for estream. */ -static ssize_t -cookie_write (void *cookie, const void *buffer, size_t size) -{ - cookie_t c = cookie; - int nwritten = 0; - -#ifdef HTTP_USE_GNUTLS - if (c->tls_session) - { - int nleft = size; - while (nleft > 0) - { - nwritten = gnutls_record_send (c->tls_session, buffer, nleft); - if (nwritten <= 0) - { - if (nwritten == GNUTLS_E_INTERRUPTED) - continue; - if (nwritten == GNUTLS_E_AGAIN) - { - struct timeval tv; - - tv.tv_sec = 0; - tv.tv_usec = 50000; - select (0, NULL, NULL, NULL, &tv); - continue; - } - log_info ("TLS network write failed: %s\n", - gnutls_strerror (nwritten)); - errno = EIO; - return -1; - } - nleft -= nwritten; - buffer += nwritten; - } - } - else -#endif /*HTTP_USE_GNUTLS*/ - { - do - { - nwritten = pth_write (c->fd, buffer, size); - } - while (nwritten == -1 && errno == EINTR); - } - - return nwritten; -} - -/* Close handler for estream. */ -static int -cookie_close (void *cookie) -{ - cookie_t c = cookie; - - if (!c) - return 0; - -#ifdef HTTP_USE_GNUTLS - if (c->tls_session && !c->keep_socket) - { - /* This indicates that the read end has been closed. */ - gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR); - } -#endif /*HTTP_USE_GNUTLS*/ - if (c->fd != -1 && !c->keep_socket) - sock_close (c->fd); - - xfree (c); - return 0; -} - - - - -/**** Test code ****/ -#ifdef TEST - -static gpg_error_t -verify_callback (http_t hd, void *tls_context, int reserved) -{ - log_info ("verification of certificates skipped\n"); - return 0; -} - - - -/* static void */ -/* my_gnutls_log (int level, const char *text) */ -/* { */ -/* fprintf (stderr, "gnutls:L%d: %s", level, text); */ -/* } */ - -int -main (int argc, char **argv) -{ - int rc; - parsed_uri_t uri; - uri_tuple_t r; - http_t hd; - int c; - gnutls_session_t tls_session = NULL; -#ifdef HTTP_USE_GNUTLS - gnutls_certificate_credentials certcred; - const int certprio[] = { GNUTLS_CRT_X509, 0 }; -#endif /*HTTP_USE_GNUTLS*/ - header_t hdr; - - es_init (); - log_set_prefix ("http-test", 1 | 4); - if (argc == 1) - { - /*start_server (); */ - return 0; - } - - if (argc != 2) - { - fprintf (stderr, "usage: http-test uri\n"); - return 1; - } - argc--; - argv++; - -#ifdef HTTP_USE_GNUTLS - rc = gnutls_global_init (); - if (rc) - log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); - rc = gnutls_certificate_allocate_credentials (&certcred); - if (rc) - log_error ("gnutls_certificate_allocate_credentials failed: %s\n", - gnutls_strerror (rc)); -/* rc = gnutls_certificate_set_x509_trust_file */ -/* (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */ -/* if (rc) */ -/* log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */ -/* gnutls_strerror (rc)); */ - rc = gnutls_init (&tls_session, GNUTLS_CLIENT); - if (rc) - log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc)); - rc = gnutls_set_default_priority (tls_session); - if (rc) - log_error ("gnutls_set_default_priority failed: %s\n", - gnutls_strerror (rc)); - rc = gnutls_certificate_type_set_priority (tls_session, certprio); - if (rc) - log_error ("gnutls_certificate_type_set_priority failed: %s\n", - gnutls_strerror (rc)); - rc = gnutls_credentials_set (tls_session, GNUTLS_CRD_CERTIFICATE, certcred); - if (rc) - log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc)); -/* gnutls_global_set_log_function (my_gnutls_log); */ -/* gnutls_global_set_log_level (4); */ - - http_register_tls_callback (verify_callback); -#endif /*HTTP_USE_GNUTLS*/ - - rc = http_parse_uri (&uri, *argv); - if (rc) - { - log_error ("`%s': %s\n", *argv, gpg_strerror (rc)); - http_release_parsed_uri (uri); - return 1; - } - - printf ("Scheme: %s\n", uri->scheme); - printf ("Host : %s\n", uri->host); - printf ("Port : %u\n", uri->port); - printf ("Path : %s\n", uri->path); - for (r = uri->params; r; r = r->next) - { - printf ("Params: %s", r->name); - if (!r->no_value) - { - printf ("=%s", r->value); - if (strlen (r->value) != r->valuelen) - printf (" [real length=%d]", (int) r->valuelen); - } - putchar ('\n'); - } - for (r = uri->query; r; r = r->next) - { - printf ("Query : %s", r->name); - if (!r->no_value) - { - printf ("=%s", r->value); - if (strlen (r->value) != r->valuelen) - printf (" [real length=%d]", (int) r->valuelen); - } - putchar ('\n'); - } - http_release_parsed_uri (uri); - uri = NULL; - - rc = http_open_document (&hd, *argv, NULL, - HTTP_FLAG_NEED_HEADER, - NULL, tls_session); - if (rc) - { - log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc)); - return 1; - } - log_info ("open_http_document succeeded; status=%u\n", - http_get_status_code (hd)); - for (hdr = hd->headers; hdr; hdr = hdr->next) - printf ("HDR: %s: %s\n", hdr->name, hdr->value); - switch (http_get_status_code (hd)) - { - case 200: - while ((c = es_getc (http_get_read_ptr (hd))) != EOF) - putchar (c); - break; - case 301: - case 302: - printf ("Redirected to `%s'\n", http_get_header (hd, "Location")); - break; - } - http_close (hd, 0); - -#ifdef HTTP_USE_GNUTLS - gnutls_deinit (tls_session); - gnutls_certificate_free_credentials (certcred); - gnutls_global_deinit (); -#endif /*HTTP_USE_GNUTLS*/ - - return 0; -} -#endif /*TEST*/ - -/* -Local Variables: -compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls" -End: -*/ diff --git a/dirmngr/http.h b/dirmngr/http.h deleted file mode 100644 index 6e688b8d1..000000000 --- a/dirmngr/http.h +++ /dev/null @@ -1,109 +0,0 @@ -/* http.h - HTTP protocol handler - * Copyright (C) 1999, 2000, 2001, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - */ -#ifndef GNUPG_COMMON_HTTP_H -#define GNUPG_COMMON_HTTP_H - -#include -#include "estream.h" - -struct uri_tuple_s { - struct uri_tuple_s *next; - const char *name; /* A pointer into name. */ - char *value; /* A pointer to value (a Nul is always appended). */ - size_t valuelen; /* The real length of the value; we need it - because the value may contain embedded Nuls. */ - int no_value; /* True if no value has been given in the URL. */ -}; -typedef struct uri_tuple_s *uri_tuple_t; - -struct parsed_uri_s -{ - /* All these pointers point into BUFFER; most stuff is not escaped. */ - char *scheme; /* Pointer to the scheme string (lowercase). */ - int use_tls; /* Whether TLS should be used. */ - char *auth; /* username/password for basic auth */ - char *host; /* Host (converted to lowercase). */ - unsigned short port; /* Port (always set if the host is set). */ - char *path; /* Path. */ - uri_tuple_t params; /* ";xxxxx" */ - uri_tuple_t query; /* "?xxx=yyy" */ - char buffer[1]; /* Buffer which holds a (modified) copy of the URI. */ -}; -typedef struct parsed_uri_s *parsed_uri_t; - -typedef enum - { - HTTP_REQ_GET = 1, - HTTP_REQ_HEAD = 2, - HTTP_REQ_POST = 3 - } -http_req_t; - -/* We put the flag values into an enum, so that gdb can display them. */ -enum - { - HTTP_FLAG_TRY_PROXY = 1, - HTTP_FLAG_SHUTDOWN = 2, - HTTP_FLAG_TRY_SRV = 4, - HTTP_FLAG_LOG_RESP = 8, - HTTP_FLAG_NEED_HEADER = 16, - HTTP_FLAG_IGNORE_CL = 32 - }; - -struct http_context_s; -typedef struct http_context_s *http_t; - -void http_register_tls_callback (gpg_error_t (*cb) (http_t, void *, int)); - -gpg_error_t http_parse_uri (parsed_uri_t *ret_uri, const char *uri); - -void http_release_parsed_uri (parsed_uri_t uri); - -gpg_error_t http_open (http_t *r_hd, http_req_t reqtype, - const char *url, - const char *auth, - unsigned int flags, - const char *proxy, - void *tls_context); - -void http_start_data (http_t hd); - -gpg_error_t http_wait_response (http_t hd); - -void http_close (http_t hd, int keep_read_stream); - -gpg_error_t http_open_document (http_t *r_hd, - const char *document, - const char *auth, - unsigned int flags, - const char *proxy, - void *tls_context); - -estream_t http_get_read_ptr (http_t hd); -estream_t http_get_write_ptr (http_t hd); -unsigned int http_get_status_code (http_t hd); -const char *http_get_header (http_t hd, const char *name); - -char *http_escape_string (const char *string, const char *specials); - - -#endif /*GNUPG_COMMON_HTTP_H*/ diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index a8db51d17..ec727f014 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -1,799 +1,797 @@ /* ocsp.c - OCSP management * Copyright (C) 2004, 2007 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "http.h" #include "validate.h" #include "certcache.h" #include "ocsp.h" #include "estream.h" /* The maximum size we allow as a response from an OCSP reponder. */ #define MAX_RESPONSE_SIZE 65536 static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1"; /* Telesec attribute used to implement a positive confirmation. CertHash ::= SEQUENCE { HashAlgorithm AlgorithmIdentifier, certificateHash OCTET STRING } */ static const char oidstr_certHash[] = "1.3.36.8.3.13"; /* Read from FP and return a newly allocated buffer in R_BUFFER with the entire data read from FP. */ static gpg_error_t read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen) { gpg_error_t err; unsigned char *buffer; size_t bufsize, nbytes; *r_buffer = NULL; *r_buflen = 0; bufsize = 4096; buffer = xtrymalloc (bufsize); if (!buffer) return gpg_error_from_errno (errno); nbytes = 0; for (;;) { unsigned char *tmp; size_t nread = 0; assert (nbytes < bufsize); nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp); if (nread < bufsize-nbytes && es_ferror (fp)) { err = gpg_error_from_errno (errno); log_error (_("error reading from responder: %s\n"), strerror (errno)); xfree (buffer); return err; } if ( !(nread == bufsize-nbytes && !es_feof (fp))) { /* Response succesfully received. */ nbytes += nread; *r_buffer = buffer; *r_buflen = nbytes; return 0; } nbytes += nread; /* Need to enlarge the buffer. */ if (bufsize >= MAX_RESPONSE_SIZE) { log_error (_("response from server too large; limit is %d bytes\n"), MAX_RESPONSE_SIZE); xfree (buffer); return gpg_error (GPG_ERR_TOO_LARGE); } bufsize += 4096; tmp = xtryrealloc (buffer, bufsize); if (!tmp) { err = gpg_error_from_errno (errno); xfree (buffer); return err; } buffer = tmp; } } /* Construct an OCSP request, send it to the configured OCSP responder and parse the response. On success the OCSP context may be used to further process the reponse. */ static gpg_error_t do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_md_hd_t md, const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert) { gpg_error_t err; unsigned char *request, *response; size_t requestlen, responselen; http_t http; ksba_ocsp_response_status_t response_status; const char *t; int redirects_left = 2; char *free_this = NULL; (void)ctrl; if (opt.disable_http) { log_error (_("OCSP request not possible due to disabled HTTP\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } err = ksba_ocsp_add_target (ocsp, cert, issuer_cert); if (err) { log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err)); return err; } { size_t n; unsigned char nonce[32]; n = ksba_ocsp_set_nonce (ocsp, NULL, 0); if (n > sizeof nonce) n = sizeof nonce; gcry_create_nonce (nonce, n); ksba_ocsp_set_nonce (ocsp, nonce, n); } err = ksba_ocsp_build_request (ocsp, &request, &requestlen); if (err) { log_error (_("error building OCSP request: %s\n"), gpg_strerror (err)); return err; } once_more: err = http_open (&http, HTTP_REQ_POST, url, NULL, - (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) - |HTTP_FLAG_NEED_HEADER, - opt.http_proxy, - NULL); + (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0), + opt.http_proxy, NULL, NULL, NULL); if (err) { log_error (_("error connecting to `%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } es_fprintf (http_get_write_ptr (http), "Content-Type: application/ocsp-request\r\n" "Content-Length: %lu\r\n", (unsigned long)requestlen ); http_start_data (http); if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1) { err = gpg_error_from_errno (errno); log_error ("error sending request to `%s': %s\n", url, strerror (errno)); http_close (http, 0); xfree (request); xfree (free_this); return err; } xfree (request); request = NULL; err = http_wait_response (http); if (err || http_get_status_code (http) != 200) { if (err) log_error (_("error reading HTTP response for `%s': %s\n"), url, gpg_strerror (err)); else { switch (http_get_status_code (http)) { case 301: case 302: { const char *s = http_get_header (http, "Location"); log_info (_("URL `%s' redirected to `%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); if (s && *s && redirects_left-- ) { xfree (free_this); url = NULL; free_this = xtrystrdup (s); if (!free_this) err = gpg_error_from_errno (errno); else { url = free_this; http_close (http, 0); goto once_more; } } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); } break; default: log_error (_("error accessing `%s': http status %u\n"), url, http_get_status_code (http)); err = gpg_error (GPG_ERR_NO_DATA); break; } } http_close (http, 0); xfree (free_this); return err; } err = read_response (http_get_read_ptr (http), &response, &responselen); http_close (http, 0); if (err) { log_error (_("error reading HTTP response for `%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } err = ksba_ocsp_parse_response (ocsp, response, responselen, &response_status); if (err) { log_error (_("error parsing OCSP response for `%s': %s\n"), url, gpg_strerror (err)); xfree (response); xfree (free_this); return err; } switch (response_status) { case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break; case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break; case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break; case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break; case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break; case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break; case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break; case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break; case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break; default: t = "[unknown status]"; break; } if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS) { if (opt.verbose) log_info (_("OCSP responder at `%s' status: %s\n"), url, t); err = ksba_ocsp_hash_response (ocsp, response, responselen, HASH_FNC, md); if (err) log_error (_("hashing the OCSP response for `%s' failed: %s\n"), url, gpg_strerror (err)); } else { log_error (_("OCSP responder at `%s' status: %s\n"), url, t); err = gpg_error (GPG_ERR_GENERAL); } xfree (response); xfree (free_this); return err; } /* Validate that CERT is indeed valid to sign an OCSP response. If SIGNER_FPR_LIST is not NULL we simply check that CERT matches one of the fingerprints in this list. */ static gpg_error_t validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert, fingerprint_list_t signer_fpr_list) { gpg_error_t err; char *fpr; if (signer_fpr_list) { fpr = get_fingerprint_hexstring (cert); for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr); signer_fpr_list = signer_fpr_list->next) ; if (signer_fpr_list) err = 0; else { log_error (_("not signed by a default OCSP signer's certificate")); err = gpg_error (GPG_ERR_BAD_CA_CERT); } xfree (fpr); } else if (opt.system_daemon) { err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_OCSP, NULL); } else { /* We avoid duplicating the entire certificate validation code from gpgsm here. Because we have no way calling back to the client and letting it compute the validity, we use the ugly hack of telling the client that the response will only be valid if the certificate given in this status message is valid. Note, that in theory we could simply ask the client via an inquire to validate a certificate but this might involve calling DirMngr again recursivly - we can't do that as of now (neither DirMngr nor gpgsm have the ability for concurrent access to DirMngr. */ /* FIXME: We should cache this certificate locally, so that the next call to dirmngr won't need to look it up - if this works at all. */ fpr = get_fingerprint_hexstring (cert); dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL); xfree (fpr); err = 0; } return err; } /* Helper for check_signature. */ static int check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig, gcry_sexp_t s_hash, fingerprint_list_t signer_fpr_list) { gpg_error_t err; ksba_sexp_t pubkey; gcry_sexp_t s_pkey = NULL; pubkey = ksba_cert_get_public_key (cert); if (!pubkey) err = gpg_error (GPG_ERR_INV_OBJ); else err = canon_sexp_to_gcry (pubkey, &s_pkey); xfree (pubkey); if (!err) err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (!err) err = validate_responder_cert (ctrl, cert, signer_fpr_list); if (!err) { gcry_sexp_release (s_pkey); return 0; /* Successfully verified the signature. */ } /* We simply ignore all errors. */ gcry_sexp_release (s_pkey); return -1; } /* Check the signature of an OCSP repsonse. OCSP is the context, S_SIG the signature value and MD the handle of the hash we used for the response. This function automagically finds the correct public key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been used and thus the certificate is one of those identified by the fingerprints. */ static gpg_error_t check_signature (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md, fingerprint_list_t signer_fpr_list) { gpg_error_t err; int algo, cert_idx; gcry_sexp_t s_hash; ksba_cert_t cert; /* Create a suitable S-expression with the hash value of our response. */ gcry_md_final (md); algo = gcry_md_get_algo (md); if (algo != GCRY_MD_SHA1 ) { log_error (_("only SHA-1 is supported for OCSP responses\n")); return gpg_error (GPG_ERR_DIGEST_ALGO); } err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash sha1 %b))", gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo)); if (err) { log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err)); return err; } /* Get rid of old OCSP specific certificate references. */ release_ctrl_ocsp_certs (ctrl); if (signer_fpr_list && !signer_fpr_list->next) { /* There is exactly one signer fingerprint given. Thus we use the default OCSP responder's certificate and instantly know the certificate to use. */ cert = get_cert_byhexfpr (signer_fpr_list->hexfpr); if (!cert) cert = get_cert_local (ctrl, signer_fpr_list->hexfpr); if (cert) { err = check_signature_core (ctrl, cert, s_sig, s_hash, signer_fpr_list); ksba_cert_release (cert); cert = NULL; if (!err) { gcry_sexp_release (s_hash); return 0; /* Successfully verified the signature. */ } } } else { char *name; ksba_sexp_t keyid; /* Put all certificates included in the response into the cache and setup a list of those certificate which will later be preferred used when locating certificates. */ for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx)); cert_idx++) { cert_ref_t cref; cref = xtrymalloc (sizeof *cref); if (!cref) log_error (_("allocating list item failed: %s\n"), gcry_strerror (err)); else if (!cache_cert_silent (cert, &cref->fpr)) { cref->next = ctrl->ocsp_certs; ctrl->ocsp_certs = cref; } else xfree (cref); } /* Get the certificate by means of the responder ID. */ err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid); if (err) { log_error (_("error getting responder ID: %s\n"), gcry_strerror (err)); return err; } cert = find_cert_bysubject (ctrl, name, keyid); if (!cert) { log_error ("responder certificate "); if (name) log_printf ("`/%s' ", name); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } log_printf ("not found\n"); } ksba_free (name); ksba_free (keyid); if (cert) { err = check_signature_core (ctrl, cert, s_sig, s_hash, signer_fpr_list); ksba_cert_release (cert); if (!err) { gcry_sexp_release (s_hash); return 0; /* Successfully verified the signature. */ } } } gcry_sexp_release (s_hash); log_error (_("no suitable certificate found to verify the OCSP response\n")); return gpg_error (GPG_ERR_NO_PUBKEY); } /* Check whether the certificate either given by fingerprint CERT_FPR or directly through the CERT object is valid by running an OCSP transaction. With FORCE_DEFAULT_RESPONDER set only the configured default responder is used. */ gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, int force_default_responder) { gpg_error_t err; ksba_ocsp_t ocsp = NULL; ksba_cert_t issuer_cert = NULL; ksba_sexp_t sigval = NULL; gcry_sexp_t s_sig = NULL; ksba_isotime_t current_time; ksba_isotime_t this_update, next_update, revocation_time, produced_at; ksba_isotime_t tmp_time; ksba_status_t status; ksba_crl_reason_t reason; char *url_buffer = NULL; const char *url; gcry_md_hd_t md = NULL; int i, idx; char *oid; ksba_name_t name; fingerprint_list_t default_signer = NULL; /* Get the certificate. */ if (cert) { ksba_cert_ref (cert); err = find_issuing_cert (ctrl, cert, &issuer_cert); if (err) { log_error (_("issuer certificate not found: %s\n"), gpg_strerror (err)); goto leave; } } else { cert = get_cert_local (ctrl, cert_fpr); if (!cert) { log_error (_("caller did not return the target certificate\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer_cert = get_issuing_cert_local (ctrl, NULL); if (!issuer_cert) { log_error (_("caller did not return the issuing certificate\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } } /* Create an OCSP instance. */ err = ksba_ocsp_new (&ocsp); if (err) { log_error (_("failed to allocate OCSP context: %s\n"), gpg_strerror (err)); goto leave; } /* Figure out the OCSP responder to use. 1. Try to get the reponder from the certificate. We do only take http and https style URIs into account. 2. If this fails use the default responder, if any. */ url = NULL; for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder && !(err=ksba_cert_get_authority_info_access (cert, idx, &oid, &name)); idx++) { if ( !strcmp (oid, oidstr_ocsp) ) { for (i=0; !url && ksba_name_enum (name, i); i++) { char *p = ksba_name_get_uri (name, i); if (p && (!ascii_strncasecmp (p, "http:", 5) || !ascii_strncasecmp (p, "https:", 6))) url = url_buffer = p; else xfree (p); } } ksba_name_release (name); ksba_free (oid); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err)); goto leave; } if (!url) { if (!opt.ocsp_responder || !*opt.ocsp_responder) { log_info (_("no default OCSP responder defined\n")); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } if (!opt.ocsp_signer) { log_info (_("no default OCSP signer defined\n")); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } url = opt.ocsp_responder; default_signer = opt.ocsp_signer; if (opt.verbose) log_info (_("using default OCSP responder `%s'\n"), url); } else { if (opt.verbose) log_info (_("using OCSP responder `%s'\n"), url); } /* Ask the OCSP responder. */ err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) { log_error (_("failed to establish a hashing context for OCSP: %s\n"), gpg_strerror (err)); goto leave; } err = do_ocsp_request (ctrl, ocsp, md, url, cert, issuer_cert); if (err) goto leave; /* We got a useful answer, check that the answer has a valid signature. */ sigval = ksba_ocsp_get_sig_val (ocsp, produced_at); if (!sigval || !*produced_at) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) ) goto leave; xfree (sigval); sigval = NULL; err = check_signature (ctrl, ocsp, s_sig, md, default_signer); if (err) goto leave; /* We only support one certificate per request. Check that the answer matches the right certificate. */ err = ksba_ocsp_get_status (ocsp, cert, &status, this_update, next_update, revocation_time, &reason); if (err) { log_error (_("error getting OCSP status for target certificate: %s\n"), gpg_strerror (err)); goto leave; } /* In case the certificate has been revoked, we better invalidate our cached validation status. */ if (status == KSBA_STATUS_REVOKED) { time_t validated_at = 0; /* That is: No cached validation available. */ err = ksba_cert_set_user_data (cert, "validated_at", &validated_at, sizeof (validated_at)); if (err) { log_error ("set_user_data(validated_at) failed: %s\n", gpg_strerror (err)); err = 0; /* The certificate is anyway revoked, and that is a more important message than the failure of our cache. */ } } if (opt.verbose) { log_info (_("certificate status is: %s (this=%s next=%s)\n"), status == KSBA_STATUS_GOOD? _("good"): status == KSBA_STATUS_REVOKED? _("revoked"): status == KSBA_STATUS_UNKNOWN? _("unknown"): status == KSBA_STATUS_NONE? _("none"): "?", this_update, next_update); if (status == KSBA_STATUS_REVOKED) log_info (_("certificate has been revoked at: %s due to: %s\n"), revocation_time, reason == KSBA_CRLREASON_UNSPECIFIED? "unspecified": reason == KSBA_CRLREASON_KEY_COMPROMISE? "key compromise": reason == KSBA_CRLREASON_CA_COMPROMISE? "CA compromise": reason == KSBA_CRLREASON_AFFILIATION_CHANGED? "affiliation changed": reason == KSBA_CRLREASON_SUPERSEDED? "superseeded": reason == KSBA_CRLREASON_CESSATION_OF_OPERATION? "cessation of operation": reason == KSBA_CRLREASON_CERTIFICATE_HOLD? "certificate on hold": reason == KSBA_CRLREASON_REMOVE_FROM_CRL? "removed from CRL": reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN? "privilege withdrawn": reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise": reason == KSBA_CRLREASON_OTHER? "other":"?"); } if (status == KSBA_STATUS_REVOKED) err = gpg_error (GPG_ERR_CERT_REVOKED); else if (status == KSBA_STATUS_UNKNOWN) err = gpg_error (GPG_ERR_NO_DATA); else if (status != KSBA_STATUS_GOOD) err = gpg_error (GPG_ERR_GENERAL); /* Allow for some clock skew. */ gnupg_get_isotime (current_time); add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew); if (strcmp (this_update, current_time) > 0 ) { log_error (_("OCSP responder returned a status in the future\n")); log_info ("used now: %s this_update: %s\n", current_time, this_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } /* Check that THIS_UPDATE is not too far back in the past. */ gnupg_copy_time (tmp_time, this_update); add_seconds_to_isotime (tmp_time, opt.ocsp_max_period+opt.ocsp_max_clock_skew); if (!*tmp_time || strcmp (tmp_time, current_time) < 0 ) { log_error (_("OCSP responder returned a non-current status\n")); log_info ("used now: %s this_update: %s\n", current_time, this_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } /* Check that we are not beyound NEXT_UPDATE (plus some extra time). */ if (*next_update) { gnupg_copy_time (tmp_time, next_update); add_seconds_to_isotime (tmp_time, opt.ocsp_current_period+opt.ocsp_max_clock_skew); if (!*tmp_time && strcmp (tmp_time, current_time) < 0 ) { log_error (_("OCSP responder returned an too old status\n")); log_info ("used now: %s next_update: %s\n", current_time, next_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } } leave: gcry_md_close (md); gcry_sexp_release (s_sig); xfree (sigval); ksba_cert_release (issuer_cert); ksba_cert_release (cert); ksba_ocsp_release (ocsp); xfree (url_buffer); return err; } /* Release the list of OCSP certificates hold in the CTRL object. */ void release_ctrl_ocsp_certs (ctrl_t ctrl) { while (ctrl->ocsp_certs) { cert_ref_t tmp = ctrl->ocsp_certs->next; xfree (ctrl->ocsp_certs); ctrl->ocsp_certs = tmp; } } diff --git a/dirmngr/server.c b/dirmngr/server.c index a7b623cb1..ce0a5b3c8 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1,1539 +1,1539 @@ /* dirmngr.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #define JNLIB_NEED_LOG_LOGV #include "dirmngr.h" #include #include "crlcache.h" #include "crlfetch.h" #include "ldapserver.h" #include "ocsp.h" #include "certcache.h" #include "validate.h" #include "misc.h" /* To avoid DoS attacks we limit the size of a certificate to something reasonable. */ #define MAX_CERT_LENGTH (8*1024) #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) /* Control structure per connection. */ struct server_local_s { /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* Per-session LDAP serfver. */ ldap_server_t ldapservers; }; /* Accessor for the local ldapservers variable. */ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl) { if (ctrl && ctrl->server_local) return ctrl->server_local->ldapservers; else return NULL; } /* Copy the % and + escaped string S into the buffer D and replace the escape sequences. Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1. NOte further that If S contains an escaped binary nul the resulting string D will contain the 0 as well as all other characters but it will be impossible to know whether this is the original EOS or a copied Nul. */ static void strcpy_escaped_plus (char *d, const unsigned 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))); } /* Same as has_option but only considers options at the begin of the line. This is useful for commands which allow arbitrary strings on the line. */ static int has_leading_option (const char *line, const char *name) { const char *s; int n; if (name[0] != '-' || name[1] != '-' || !name[2] || spacep (name+2)) return 0; n = strlen (name); while ( *line == '-' && line[1] == '-' ) { s = line; while (*line && !spacep (line)) line++; if (n == (line - s) && !strncmp (s, name, n)) return 1; while (spacep (line)) line++; } return 0; } /* Same as has_option but does only test for the name of the option and ignores an argument, i.e. with NAME being "--hash" it would return a pointer for "--hash" as well as for "--hash=foo". If thhere is no such option NULL is returned. The pointer returned points right behind the option name, this may be an equal sign, Nul or a space. */ /* static const char * */ /* has_option_name (const char *line, const char *name) */ /* { */ /* const char *s; */ /* int n = strlen (name); */ /* s = strstr (line, name); */ /* return (s && (s == line || spacep (s-1)) */ /* && (!s[n] || spacep (s+n) || s[n] == '=')) ? (s+n) : NULL; */ /* } */ /* Skip over options. It is assumed that leading spaces have been removed (this is the case for lines passed to a handler from assuan). Blanks after the options are also removed. */ static char * skip_options (char *line) { while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } return line; } /* Common code for get_cert_local and get_issuer_cert_local. */ static ksba_cert_t do_get_cert_local (ctrl_t ctrl, const char *name, const char *command) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; if (name) { buf = xmalloc ( strlen (command) + 1 + strlen(name) + 1); strcpy (stpcpy (stpcpy (buf, command), " "), name); } else buf = xstrdup (command); rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), command, gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask back to return a certificate for name, given as a regular gpgsm certificate indentificates (e.g. fingerprint or one of the other methods). Alternatively, NULL may be used for NAME to return the current target certificate. Either return the certificate in a KSBA object or NULL if it is not available. */ ksba_cert_t get_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDCERT"); } /* Ask back to return the issuing certificate for name, given as a regular gpgsm certificate indentificates (e.g. fingerprint or one of the other methods). Alternatively, NULL may be used for NAME to return thecurrent target certificate. Either return the certificate in a KSBA object or NULL if it is not available. */ ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_issuing_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDISSUERCERT"); } /* Ask back to return a certificate with subject NAME and a subjectKeyIdentifier of KEYID. */ ksba_cert_t get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; char *hexkeyid; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local_ski called w/o context\n"); return NULL; } if (!name || !keyid) { log_debug ("get_cert_local_ski called with insufficient arguments\n"); return NULL; } hexkeyid = serial_hex (keyid); if (!hexkeyid) { log_debug ("serial_hex() failed\n"); return NULL; } buf = xtrymalloc (15 + strlen (hexkeyid) + 2 + strlen(name) + 1); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexkeyid); return NULL; } strcpy (stpcpy (stpcpy (stpcpy (buf, "SENDCERT_SKI "), hexkeyid)," /"),name); xfree (hexkeyid); rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI", gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask the client via an inquiry to check the istrusted status of the certificate specified by the hexified fingerprint HEXFPR. Returns 0 if the certificate is trusted by the client or an error code. */ gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr) { unsigned char *value; size_t valuelen; int rc; char request[100]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx || !hexfpr) return gpg_error (GPG_ERR_INV_ARG); snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr); rc = assuan_inquire (ctrl->server_local->assuan_ctx, request, &value, &valuelen, 100); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), request, gpg_strerror (rc)); return rc; } /* The expected data is: "1" or "1 cruft" (not a C-string). */ if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1))) rc = 0; else rc = gpg_error (GPG_ERR_NOT_TRUSTED); xfree (value); return rc; } /* Ask the client to return the certificate associated with the current command. This is sometimes needed because the client usually sends us just the cert ID, assuming that the request can be satisfied from the cache, where the cert ID is used as key. */ static int inquire_cert_and_load_crl (assuan_context_t ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; ksba_cert_t cert = NULL; err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0); if (err) return err; /* { */ /* FILE *fp = fopen ("foo.der", "r"); */ /* value = xmalloc (2000); */ /* valuelen = fread (value, 1, 2000, fp); */ /* fclose (fp); */ /* } */ if (!valuelen) /* No data returned; return a comprehensible error. */ return gpg_error (GPG_ERR_MISSING_CERT); err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, value, valuelen); if(err) goto leave; xfree (value); value = NULL; err = crl_cache_reload_crl (ctrl, cert); leave: ksba_cert_release (cert); xfree (value); return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "force-crl-refresh")) { int i = *value? atoi (value) : 0; ctrl->force_crl_refresh = i; } else if (!strcmp (key, "audit-events")) { int i = *value? atoi (value) : 0; ctrl->audit_events = i; } else return gpg_error (GPG_ERR_UNKNOWN_OPTION); return 0; } static const char hlp_ldapserver[] = "LDAPSERVER \n" "\n" "Add a new LDAP server to the list of configured LDAP servers.\n" "DATA is in the same format as expected in the configure file."; static gpg_error_t cmd_ldapserver (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ldap_server_t server; ldap_server_t *last_next_p; while (spacep (line)) line++; if (*line == '\0') return PARM_ERROR (_("ldapserver missing")); server = ldapserver_parse_one (line, "", 0); if (! server) return gpg_error (GPG_ERR_INV_ARG); last_next_p = &ctrl->server_local->ldapservers; while (*last_next_p) last_next_p = &(*last_next_p)->next; *last_next_p = server; return 0; } static const char hlp_isvalid[] = "ISVALID [--only-ocsp] [--force-default-responder]" " |\n" "\n" "This command checks whether the certificate identified by the\n" "certificate_id is valid. This is done by consulting CRLs or\n" "whatever has been configured. Note, that the returned error codes\n" "are from gpg-error.h. The command may callback using the inquire\n" "function. See the manual for details.\n" "\n" "The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n" "delimited by a single dot. The first part is the SHA-1 hash of the\n" "issuer name and the second part the serial number.\n" "\n" "Alternatively the certificate's fingerprint may be given in which\n" "case an OCSP request is done before consulting the CRL.\n" "\n" "If the option --only-ocsp is given, no fallback to a CRL check will\n" "be used.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used."; static gpg_error_t cmd_isvalid (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *issuerhash, *serialno; gpg_error_t err; int did_inquire = 0; int ocsp_mode = 0; int only_ocsp; int force_default_responder; only_ocsp = has_option (line, "--only-ocsp"); force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); issuerhash = xstrdup (line); /* We need to work on a copy of the line because that same Assuan context may be used for an inquiry. That is because Assuan reuses its line buffer. */ serialno = strchr (issuerhash, '.'); if (serialno) *serialno++ = 0; else { char *endp = strchr (issuerhash, ' '); if (endp) *endp = 0; if (strlen (issuerhash) != 40) { xfree (issuerhash); return PARM_ERROR (_("serialno missing in cert ID")); } ocsp_mode = 1; } again: if (ocsp_mode) { /* Note, that we ignore the given issuer hash and instead rely on the current certificate semantics used with this command. */ if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder); /* Fixme: If we got no ocsp response and --only-ocsp is not used we should fall back to CRL mode. Thus we need to clear OCSP_MODE, get the issuerhash and the serialno from the current certificate and jump to again. */ } else if (only_ocsp) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else { switch (crl_cache_isvalid (ctrl, issuerhash, serialno, ctrl->force_crl_refresh)) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: if (did_inquire) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (!(err = inquire_cert_and_load_crl (ctx))) { did_inquire = 1; goto again; } break; case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("crl_cache_isvalid returned invalid code\n"); } } if (err) log_error (_("command %s failed: %s\n"), "ISVALID", gpg_strerror (err)); xfree (issuerhash); return err; } /* If the line contains a SHA-1 fingerprint as the first argument, return the FPR vuffer on success. The function checks that the fingerprint consists of valid characters and prints and error message if it does not and returns NULL. Fingerprints are considered optional and thus no explicit error is returned. NULL is also returned if there is no fingerprint at all available. FPR must be a caller provided buffer of at least 20 bytes. Note that colons within the fingerprint are allowed to separate 2 hex digits; this allows for easier cutting and pasting using the usual fingerprint rendering. */ static unsigned char * get_fingerprint_from_line (const char *line, unsigned char *fpr) { const char *s; int i; for (s=line, i=0; *s && *s != ' '; s++ ) { if ( hexdigitp (s) && hexdigitp (s+1) ) { if ( i >= 20 ) return NULL; /* Fingerprint too long. */ fpr[i++] = xtoi_2 (s); s++; } else if ( *s != ':' ) return NULL; /* Invalid. */ } if ( i != 20 ) return NULL; /* Fingerprint to short. */ return fpr; } static const char hlp_checkcrl[] = "CHECKCRL []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by consulting the\n" "CRL responsible for this certificate. If the fingerprint has not\n" "been given or the certificate is not known, the function \n" "inquires the certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificate by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, cert); if (!err) err = crl_cache_cert_isvalid (ctrl, cert, 0); } leave: if (err) log_error (_("command %s failed: %s\n"), "CHECKCRL", gpg_strerror (err)); ksba_cert_release (cert); return err; } static const char hlp_checkocsp[] = "CHECKOCSP [--force-default-responder] []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by asking an OCSP\n" "responder responsible for this certificate. The optional\n" "fingerprint may be used for a quick check in case an OCSP check has\n" "been done for this certificate recently (we always cache OCSP\n" "responses for a couple of minutes). If the fingerprint has not been\n" "given or there is no cached result, the function inquires the\n" "certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificates by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certifciates.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkocsp (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; int force_default_responder; force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder); leave: if (err) log_error (_("command %s failed: %s\n"), "CHECKOCSP", gpg_strerror (err)); ksba_cert_release (cert); return err; } static int lookup_cert_by_url (assuan_context_t ctx, const char *url) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; unsigned char *value = NULL; size_t valuelen; /* Fetch single certificate given it's URL. */ err = fetch_cert_by_url (ctrl, url, &value, &valuelen); if (err) { log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the data, flush the buffer and then send an END. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); goto leave; } leave: return err; } /* Send the certificate, flush the buffer and then send an END. */ static gpg_error_t return_one_cert (void *opaque, ksba_cert_t cert) { assuan_context_t ctx = opaque; gpg_error_t err; const unsigned char *der; size_t derlen; der = ksba_cert_get_image (cert, &derlen); if (!der) err = gpg_error (GPG_ERR_INV_CERT_OBJ); else { err = assuan_send_data (ctx, der, derlen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); } if (err) log_error (_("error sending data: %s\n"), gpg_strerror (err)); return err; } /* Lookup certificates from the internal cache or using the ldap servers. */ static int lookup_cert_by_pattern (assuan_context_t ctx, char *line, int single, int cache_only) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; char *p; strlist_t sl, list = NULL; int truncated = 0, truncation_forced = 0; int count = 0; int local_count = 0; unsigned char *value = NULL; size_t valuelen; struct ldapserver_iter ldapserver_iter; cert_fetch_context_t fetch_context; int any_no_data = 0; /* Break the line down into an STRLIST */ for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_errno (errno); goto leave; } memset (sl, 0, sizeof *sl); strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } /* First look through the internal cache. The certifcates retruned here are not counted towards the truncation limit. */ if (single && !cache_only) ; /* Do not read from the local cache in this case. */ else { for (sl=list; sl; sl = sl->next) { err = get_certs_bypattern (sl->d, return_one_cert, ctx); if (!err) local_count++; if (!err && single) goto ready; if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (cache_only) any_no_data = 1; } else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only) { /* No real fault because the internal pattern lookup can't yet cope with all types of pattern. */ err = 0; } if (err) goto ready; } } /* Loop over all configured servers unless we want only the certificates from the cache. */ for (ldapserver_iter_begin (&ldapserver_iter, ctrl); !cache_only && !ldapserver_iter_end_p (&ldapserver_iter) && ldapserver_iter.server->host && !truncation_forced; ldapserver_iter_next (&ldapserver_iter)) { ldap_server_t ldapserver = ldapserver_iter.server; if (DBG_LOOKUP) log_debug ("cmd_lookup: trying %s:%d base=%s\n", ldapserver->host, ldapserver->port, ldapserver->base?ldapserver->base : "[default]"); /* Fetch certificates matching pattern */ err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver); if ( gpg_err_code (err) == GPG_ERR_NO_DATA ) { if (DBG_LOOKUP) log_debug ("cmd_lookup: no data\n"); err = 0; any_no_data = 1; continue; } if (err) { log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err)); goto leave; } /* Fetch the certificates for this query. */ while (!truncation_forced) { xfree (value); value = NULL; err = fetch_next_cert (fetch_context, &value, &valuelen); if (gpg_err_code (err) == GPG_ERR_NO_DATA ) { err = 0; any_no_data = 1; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_TRUNCATED) { truncated = 1; err = 0; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; /* Ready. */ } if (!err && !value) { err = gpg_error (GPG_ERR_BUG); goto leave; } if (err) { log_error (_("fetch_next_cert failed: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (DBG_LOOKUP) log_debug ("cmd_lookup: returning one cert%s\n", truncated? " (truncated)":""); /* Send the data, flush the buffer and then send an END line as a certificate delimiter. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (++count >= opt.max_replies ) { truncation_forced = 1; log_info (_("max_replies %d exceeded\n"), opt.max_replies ); } if (single) break; } end_cert_fetch (fetch_context); } ready: if (truncated || truncation_forced) { char str[50]; sprintf (str, "%d", count); assuan_write_status (ctx, "TRUNCATED", str); } if (!err && !count && !local_count && any_no_data) err = gpg_error (GPG_ERR_NO_DATA); leave: free_strlist (list); return err; } static const char hlp_lookup[] = "LOOKUP [--url] [--single] [--cache-only] \n" "\n" "Lookup certificates matching PATTERN. With --url the pattern is\n" "expected to be one URL.\n" "\n" "If --url is not given: To allow for multiple patterns (which are ORed)\n" "quoting is required: Spaces are translated to \"+\" or \"%20\";\n" "obviously this requires that the usual escape quoting rules are applied.\n" "\n" "If --url is given no special escaping is required because URLs are\n" "already escaped this way.\n" "\n" "If --single is given the first and only the first match will be\n" "returned. If --cache-only is _not_ given, no local query will be\n" "done.\n" "\n" "If --cache-only is given no external lookup is done so that only\n" "certificates from the cache may get returned."; static gpg_error_t cmd_lookup (assuan_context_t ctx, char *line) { gpg_error_t err; int lookup_url, single, cache_only; lookup_url = has_leading_option (line, "--url"); single = has_leading_option (line, "--single"); cache_only = has_leading_option (line, "--cache-only"); line = skip_options (line); if (lookup_url && cache_only) err = gpg_error (GPG_ERR_NOT_FOUND); else if (lookup_url && single) err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); else if (lookup_url) err = lookup_cert_by_url (ctx, line); else err = lookup_cert_by_pattern (ctx, line, single, cache_only); if (err) log_error (_("command %s failed: %s\n"), "LOOKUP", gpg_strerror (err)); return err; } static const char hlp_loadcrl[] = "LOADCRL [--url] \n" "\n" "Load the CRL in the file with name FILENAME into our cache. Note\n" "that FILENAME should be given with an absolute path because\n" "Dirmngrs cwd is not known. With --url the CRL is directly loaded\n" "from the given URL.\n" "\n" "This command is usually used by gpgsm using the invocation \"gpgsm\n" "--call-dirmngr loadcrl \". A direct invocation of Dirmngr\n" "is not useful because gpgsm might need to callback gpgsm to ask for\n" "the CA's certificate."; static gpg_error_t cmd_loadcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int use_url = has_leading_option (line, "--url"); line = skip_options (line); if (use_url) { ksba_reader_t reader; err = crl_fetch (ctrl, line, &reader); if (err) log_error (_("fetching CRL from `%s' failed: %s\n"), line, gpg_strerror (err)); else { err = crl_cache_insert (ctrl, line, reader); if (err) log_error (_("processing CRL from `%s' failed: %s\n"), line, gpg_strerror (err)); crl_close_reader (reader); } } else { char *buf; buf = xtrymalloc (strlen (line)+1); if (!buf) err = gpg_error_from_syserror (); else { strcpy_escaped_plus (buf, line); err = crl_cache_load (ctrl, buf); xfree (buf); } } if (err) log_error (_("command %s failed: %s\n"), "LOADCRL", gpg_strerror (err)); return err; } static const char hlp_listcrls[] = "LISTCRLS\n" "\n" "List the content of all CRLs in a readable format. This command is\n" "usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n" "listcrls\". It may also be used directly using \"dirmngr\n" "--list-crls\"."; static gpg_error_t cmd_listcrls (assuan_context_t ctx, char *line) { gpg_error_t err; - FILE *fp = assuan_get_data_fp (ctx); + estream_t fp = assuan_get_data_fp (ctx); (void)line; if (!fp) return PARM_ERROR (_("no data stream")); err = crl_cache_list (fp); if (err) log_error (_("command %s failed: %s\n"), "LISTCRLS", gpg_strerror (err)); return err; } static const char hlp_cachecert[] = "CACHECERT\n" "\n" "Put a certificate into the internal cache. This command might be\n" "useful if a client knows in advance certificates required for a\n" "test and wnats to make sure they get added to the internal cache.\n" "It is also helpful for debugging. To get the actual certificate,\n" "this command immediately inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob."; static gpg_error_t cmd_cachecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; unsigned char *value = NULL; size_t valuelen; (void)line; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; err = cache_cert (cert); leave: if (err) log_error (_("command %s failed: %s\n"), "CACHECERT", gpg_strerror (err)); ksba_cert_release (cert); return err; } static const char hlp_validate[] = "VALIDATE\n" "\n" "Validate a certificate using the certificate validation function\n" "used internally by dirmngr. This command is only useful for\n" "debugging. To get the actual certificate, this command immediately\n" "inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob."; static gpg_error_t cmd_validate (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; unsigned char *value = NULL; size_t valuelen; (void)line; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; /* If we have this certificate already in our cache, use the cached version for validation because this will take care of any cached results. */ { unsigned char fpr[20]; ksba_cert_t tmpcert; cert_compute_fpr (cert, fpr); tmpcert = get_cert_byfpr (fpr); if (tmpcert) { ksba_cert_release (cert); cert = tmpcert; } } err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_CERT, NULL); leave: if (err) log_error (_("command %s failed: %s\n"), "VALIDATE", gpg_strerror (err)); ksba_cert_release (cert); return err; } /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "LDAPSERVER", cmd_ldapserver, hlp_ldapserver }, { "ISVALID", cmd_isvalid, hlp_isvalid }, { "CHECKCRL", cmd_checkcrl, hlp_checkcrl }, { "CHECKOCSP", cmd_checkocsp, hlp_checkocsp }, { "LOOKUP", cmd_lookup, hlp_lookup }, { "LOADCRL", cmd_loadcrl, hlp_loadcrl }, { "LISTCRLS", cmd_listcrls, hlp_listcrls }, { "CACHECERT", cmd_cachecert, hlp_cachecert }, { "VALIDATE", cmd_validate, hlp_validate }, { "INPUT", NULL }, { "OUTPUT", NULL }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ldapserver_list_free (ctrl->server_local->ldapservers); ctrl->server_local->ldapservers = NULL; return 0; } /* Startup the server and run the main command loop. With FD = -1 used stdin/stdout. */ void start_command_handler (assuan_fd_t fd) { static const char hello[] = "Dirmngr " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl_t ctrl; ctrl = xtrycalloc (1, sizeof *ctrl); if (ctrl) ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl || !ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), strerror (errno)); xfree (ctrl); return; } dirmngr_init_default_ctrl (ctrl); rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); dirmngr_exit (2); } if (fd == ASSUAN_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } if (!hello_line) { size_t n; const char *cfgname; cfgname = opt.config_filename? opt.config_filename : "[none]"; n = (30 + strlen (opt.homedir) + strlen (cfgname) + strlen (hello) + 1); hello_line = xmalloc (n+1); snprintf (hello_line, n, "Home: %s\n" "Config: %s\n" "%s", opt.homedir, cfgname, hello); hello_line[n] = 0; } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } ldap_wrapper_connection_cleanup (ctrl); ldapserver_list_free (ctrl->server_local->ldapservers); ctrl->server_local->ldapservers = NULL; ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { release_ctrl_ocsp_certs (ctrl); xfree (ctrl->server_local); xfree (ctrl); } } /* Send a status line back to the client. KEYWORD is the status keyword, the optioal string argumenst are blank separated added to the line, the last argument must be a NULL. */ gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, keyword); if (ctrl->server_local) { assuan_context_t 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 = assuan_write_status (ctx, keyword, buf); } va_end (arg_ptr); return err; } /* Note, that we ignore CTRL for now but use the first connection to send the progress info back. */ gpg_error_t dirmngr_tick (ctrl_t ctrl) { static time_t next_tick = 0; gpg_error_t err = 0; time_t now = time (NULL); if (!next_tick) { next_tick = now + 1; } else if ( now > next_tick ) { if (ctrl) { err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL); if (err) { /* Take this as in indication for a cancel request. */ err = gpg_error (GPG_ERR_CANCELED); } now = time (NULL); } next_tick = now + 1; } return err; } diff --git a/doc/gpg.texi b/doc/gpg.texi index 1b85129d6..3b22fa70e 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1,2937 +1,2938 @@ @c Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, @c 2008, 2009 Free Software Foundation, Inc. @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @node Invoking GPG @chapter Invoking GPG @cindex GPG command options @cindex command options @cindex options, GPG command @c Begin GnuPG 1.x specific stuff @ifset gpgone @macro gpgname gpg @end macro @manpage gpg.1 @ifset manverb .B gpg \- OpenPGP encryption and signing tool @end ifset @mansect synopsis @ifset manverb .B gpg .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .I command .RI [ args ] @end ifset @end ifset @c End GnuPG 1.x specific stuff @c Begin GnuPG 2 specific stuff @ifclear gpgone @macro gpgname gpg2 @end macro @manpage gpg2.1 @ifset manverb .B gpg2 \- OpenPGP encryption and signing tool @end ifset @mansect synopsis @ifset manverb .B gpg2 .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .I command .RI [ args ] @end ifset @end ifclear @c Begin GnuPG 2 specific stuff @mansect description @command{@gpgname} is the OpenPGP part of the GNU Privacy Guard (GnuPG). It is a tool to provide digital encryption and signing services using the OpenPGP standard. @command{@gpgname} features complete key management and all bells and whistles you can expect from a decent OpenPGP implementation. @ifset gpgone This is the standalone version of @command{gpg}. For desktop use you should consider using @command{gpg2}. @end ifset @ifclear gpgone In contrast to the standalone version @command{gpg}, which is more suited for server and embedded platforms, this version is installed under the name @command{gpg2} and more targeted to the desktop as it requires several other modules to be installed. The standalone version will be kept maintained and it is possible to install both versions on the same system. If you need to use different configuration files, you should make use of something like @file{gpg.conf-2} instead of just @file{gpg.conf}. @end ifclear @manpause @ifclear gpgone Documentation for the old standard @command{gpg} is available as a man page and at @inforef{Top,GnuPG 1,gpg}. @end ifclear @xref{Option Index}, for an index to @command{@gpgname}'s commands and options. @mancont @menu * GPG Commands:: List of all commands. * GPG Options:: List of all options. * GPG Configuration:: Configuration files. * GPG Examples:: Some usage examples. Developer information: @c * Unattended Usage:: Using @command{gpg} from other programs. @c * GPG Protocol:: The protocol the server mode uses. @end menu @c ******************************************* @c *************** **************** @c *************** COMMANDS **************** @c *************** **************** @c ******************************************* @mansect commands @node GPG Commands @section Commands Commands are not distinguished from options except for the fact that only one command is allowed. @command{@gpgname} may be run with no commands, in which case it will perform a reasonable action depending on the type of file it is given as input (an encrypted message is decrypted, a signature is verified, a file containing keys is listed). Please remember that option as well as command parsing stops as soon as a non-option is encountered, you can explicitly stop parsing by using the special option @option{--}. @menu * General GPG Commands:: Commands not specific to the functionality. * Operational GPG Commands:: Commands to select the type of operation. * OpenPGP Key Management:: How to manage your keys. @end menu @c ******************************************* @c ********** GENERAL COMMANDS ************* @c ******************************************* @node General GPG Commands @subsection Commands not specific to the function @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help @itemx -h @opindex help Print a usage message summarizing the most useful command line options. Note that you cannot abbreviate this command. @item --warranty @opindex warranty Print warranty information. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @end table @c ******************************************* @c ******** OPERATIONAL COMMANDS *********** @c ******************************************* @node Operational GPG Commands @subsection Commands to select the type of operation @table @gnupgtabopt @item --sign @itemx -s @opindex sign Make a signature. This command may be combined with @option{--encrypt} (for a signed and encrypted message), @option{--symmetric} (for a signed and symmetrically encrypted message), or @option{--encrypt} and @option{--symmetric} together (for a signed message that may be decrypted via a secret key or a passphrase). The key to be used for signing is chosen by default or can be set with the @option{--local-user} and @option{--default-key} options. @item --clearsign @opindex clearsign Make a clear text signature. The content in a clear text signature is readable without any special software. OpenPGP software is only needed to verify the signature. Clear text signatures may modify end-of-line whitespace for platform independence and are not intended to be reversible. The key to be used for signing is chosen by default or can be set with the @option{--local-user} and @option{--default-key} options. @item --detach-sign @itemx -b @opindex detach-sign Make a detached signature. @item --encrypt @itemx -e @opindex encrypt Encrypt data. This option may be combined with @option{--sign} (for a signed and encrypted message), @option{--symmetric} (for a message that may be decrypted via a secret key or a passphrase), or @option{--sign} and @option{--symmetric} together (for a signed message that may be decrypted via a secret key or a passphrase). @item --symmetric @itemx -c @opindex symmetric Encrypt with a symmetric cipher using a passphrase. The default symmetric cipher used is CAST5, but may be chosen with the @option{--cipher-algo} option. This option may be combined with @option{--sign} (for a signed and symmetrically encrypted message), @option{--encrypt} (for a message that may be decrypted via a secret key or a passphrase), or @option{--sign} and @option{--encrypt} together (for a signed message that may be decrypted via a secret key or a passphrase). @item --store @opindex store Store only (make a simple RFC1991 literal data packet). @item --decrypt @itemx -d @opindex decrypt Decrypt the file given on the command line (or STDIN if no file is specified) and write it to STDOUT (or the file specified with @option{--output}). If the decrypted file is signed, the signature is also verified. This command differs from the default operation, as it never writes to the filename which is included in the file and it rejects files which don't begin with an encrypted message. @item --verify @opindex verify Assume that the first argument is a signed file or a detached signature and verify it without generating any output. With no arguments, the signature packet is read from STDIN. If only a sigfile is given, it may be a complete signature or a detached signature, in which case the signed stuff is expected in a file without the ".sig" or ".asc" extension. With more than 1 argument, the first should be a detached signature and the remaining files are the signed stuff. To read the signed stuff from STDIN, use @samp{-} as the second filename. For security reasons a detached signature cannot read the signed material from STDIN without denoting it in the above way. @item --multifile @opindex multifile This modifies certain other commands to accept multiple files for processing on the command line or read from STDIN with each filename on a separate line. This allows for many files to be processed at once. @option{--multifile} may currently be used along with @option{--verify}, @option{--encrypt}, and @option{--decrypt}. Note that @option{--multifile --verify} may not be used with detached signatures. @item --verify-files @opindex verify-files Identical to @option{--multifile --verify}. @item --encrypt-files @opindex encrypt-files Identical to @option{--multifile --encrypt}. @item --decrypt-files @opindex decrypt-files Identical to @option{--multifile --decrypt}. @item --list-keys @itemx -k @itemx --list-public-keys @opindex list-keys List all keys from the public keyrings, or just the keys given on the command line. @ifset gpgone @option{-k} is slightly different from @option{--list-keys} in that it allows only for one argument and takes the second argument as the keyring to search. This is for command line compatibility with PGP 2 and has been removed in @command{gpg2}. @end ifset Avoid using the output of this command in scripts or other programs as it is likely to change as GnuPG changes. See @option{--with-colons} for a machine-parseable key listing command that is appropriate for use in scripts and other programs. @item --list-secret-keys @itemx -K @opindex list-secret-keys List all keys from the secret keyrings, or just the ones given on the command line. A @code{#} after the letters @code{sec} means that the secret key is not usable (for example, if it was created via @option{--export-secret-subkeys}). @item --list-sigs @opindex list-sigs Same as @option{--list-keys}, but the signatures are listed too. @ifclear gpgone This command has the same effect as using @option{--list-keys} with @option{--with-sig-list}. @end ifclear For each signature listed, there are several flags in between the "sig" tag and keyid. These flags give additional information about each signature. From left to right, they are the numbers 1-3 for certificate check level (see @option{--ask-cert-level}), "L" for a local or non-exportable signature (see @option{--lsign-key}), "R" for a nonRevocable signature (see the @option{--edit-key} command "nrsign"), "P" for a signature that contains a policy URL (see @option{--cert-policy-url}), "N" for a signature that contains a notation (see @option{--cert-notation}), "X" for an eXpired signature (see @option{--ask-cert-expire}), and the numbers 1-9 or "T" for 10 and above to indicate trust signature levels (see the @option{--edit-key} command "tsign"). @item --check-sigs @opindex check-sigs Same as @option{--list-sigs}, but the signatures are verified. Note that for performance reasons the revocation status of a signing key is not shown. @ifclear gpgone This command has the same effect as using @option{--list-keys} with @option{--with-sig-check}. @end ifclear The status of the verification is indicated by a flag directly following the "sig" tag (and thus before the flags described above for @option{--list-sigs}). A "!" indicates that the signature has been successfully verified, a "-" denotes a bad signature and a "%" is used if an error occurred while checking the signature (e.g. a non supported algorithm). @ifclear gpgone @item --locate-keys @opindex locate-keys Locate the keys given as arguments. This command basically uses the same algorithm as used when locating keys for encryption or signing and may thus be used to see what keys @command{@gpgname} might use. In particular external methods as defined by @option{--auto-key-locate} may be used to locate a key. Only public keys are listed. @end ifclear @item --fingerprint @opindex fingerprint List all keys (or the specified ones) along with their fingerprints. This is the same output as @option{--list-keys} but with the additional output of a line with the fingerprint. May also be combined with @option{--list-sigs} or @option{--check-sigs}. If this command is given twice, the fingerprints of all secondary keys are listed too. @item --list-packets @opindex list-packets List only the sequence of packets. This is mainly useful for debugging. @item --card-edit @opindex card-edit Present a menu to work with a smartcard. The subcommand "help" provides an overview on available commands. For a detailed description, please see the Card HOWTO at http://www.gnupg.org/documentation/howtos.html#GnuPG-cardHOWTO . @item --card-status @opindex card-status Show the content of the smart card. @item --change-pin @opindex change-pin Present a menu to allow changing the PIN of a smartcard. This functionality is also available as the subcommand "passwd" with the @option{--card-edit} command. @item --delete-key @code{name} @opindex delete-key Remove key from the public keyring. In batch mode either @option{--yes} is required or the key must be specified by fingerprint. This is a safeguard against accidental deletion of multiple keys. @item --delete-secret-key @code{name} @opindex delete-secret-key Remove key from the secret and public keyring. In batch mode the key must be specified by fingerprint. @item --delete-secret-and-public-key @code{name} @opindex delete-secret-and-public-key Same as @option{--delete-key}, but if a secret key exists, it will be removed first. In batch mode the key must be specified by fingerprint. @item --export @opindex export Either export all keys from all keyrings (default keyrings and those registered via option @option{--keyring}), or if at least one name is given, those of the given name. The new keyring is written to STDOUT or to the file given with option @option{--output}. Use together with @option{--armor} to mail those keys. @item --send-keys @code{key IDs} @opindex send-keys Similar to @option{--export} but sends the keys to a keyserver. Fingerprints may be used instead of key IDs. Option @option{--keyserver} must be used to give the name of this keyserver. Don't send your complete keyring to a keyserver --- select only those keys which are new or changed by you. If no key IDs are given, @command{gpg} does nothing. @item --export-secret-keys @itemx --export-secret-subkeys @opindex export-secret-keys @opindex export-secret-subkeys Same as @option{--export}, but exports the secret keys instead. This is normally not very useful and a security risk. The second form of the command has the special property to render the secret part of the primary key useless; this is a GNU extension to OpenPGP and other implementations can not be expected to successfully import such a key. See the option @option{--simple-sk-checksum} if you want to import such an exported key with an older OpenPGP implementation. @item --import @itemx --fast-import @opindex import Import/merge keys. This adds the given keys to the keyring. The fast version is currently just a synonym. There are a few other options which control how this command works. Most notable here is the @option{--import-options merge-only} option which does not insert new keys but does only the merging of new signatures, user-IDs and subkeys. @item --recv-keys @code{key IDs} @opindex recv-keys Import the keys with the given key IDs from a keyserver. Option @option{--keyserver} must be used to give the name of this keyserver. @item --refresh-keys @opindex refresh-keys Request updates from a keyserver for keys that already exist on the local keyring. This is useful for updating a key with the latest signatures, user IDs, etc. Calling this with no arguments will refresh the entire keyring. Option @option{--keyserver} must be used to give the name of the keyserver for all keys that do not have preferred keyservers set (see @option{--keyserver-options honor-keyserver-url}). @item --search-keys @code{names} @opindex search-keys Search the keyserver for the given names. Multiple names given here will be joined together to create the search string for the keyserver. Option @option{--keyserver} must be used to give the name of this keyserver. Keyservers that support different search methods allow using the syntax specified in "How to specify a user ID" below. Note that different keyserver types support different search methods. Currently only LDAP supports them all. @item --fetch-keys @code{URIs} @opindex fetch-keys Retrieve keys located at the specified URIs. Note that different installations of GnuPG may support different protocols (HTTP, FTP, LDAP, etc.) @item --update-trustdb @opindex update-trustdb Do trust database maintenance. This command iterates over all keys and builds the Web of Trust. This is an interactive command because it may have to ask for the "ownertrust" values for keys. The user has to give an estimation of how far she trusts the owner of the displayed key to correctly certify (sign) other keys. GnuPG only asks for the ownertrust value if it has not yet been assigned to a key. Using the @option{--edit-key} menu, the assigned value can be changed at any time. @item --check-trustdb @opindex check-trustdb Do trust database maintenance without user interaction. From time to time the trust database must be updated so that expired keys or signatures and the resulting changes in the Web of Trust can be tracked. Normally, GnuPG will calculate when this is required and do it automatically unless @option{--no-auto-check-trustdb} is set. This command can be used to force a trust database check at any time. The processing is identical to that of @option{--update-trustdb} but it skips keys with a not yet defined "ownertrust". For use with cron jobs, this command can be used together with @option{--batch} in which case the trust database check is done only if a check is needed. To force a run even in batch mode add the option @option{--yes}. @anchor{option --export-ownertrust} @item --export-ownertrust @opindex export-ownertrust Send the ownertrust values to STDOUT. This is useful for backup purposes as these values are the only ones which can't be re-created from a corrupted trustdb. Example: @c man:.RS @example @gpgname{} --export-ownertrust > otrust.txt @end example @c man:.RE @item --import-ownertrust @opindex import-ownertrust Update the trustdb with the ownertrust values stored in @code{files} (or STDIN if not given); existing values will be overwritten. In case of a severely damaged trustdb and if you have a recent backup of the ownertrust values (e.g. in the file @file{otrust.txt}, you may re-create the trustdb using these commands: @c man:.RS @example cd ~/.gnupg rm trustdb.gpg @gpgname{} --import-ownertrust < otrust.txt @end example @c man:.RE @item --rebuild-keydb-caches @opindex rebuild-keydb-caches When updating from version 1.0.6 to 1.0.7 this command should be used to create signature caches in the keyring. It might be handy in other situations too. @item --print-md @code{algo} @itemx --print-mds @opindex print-md Print message digest of algorithm ALGO for all given files or STDIN. With the second form (or a deprecated "*" as algo) digests for all available algorithms are printed. -@item --gen-random @code{0|1|2} +@item --gen-random @code{0|1|2} @code{count} @opindex gen-random -Emit @var{count} random bytes of the given quality level. If count is -not given or zero, an endless sequence of random bytes will be emitted. -PLEASE, don't use this command unless you know what you are doing; it -may remove precious entropy from the system! +Emit @var{count} random bytes of the given quality level 0, 1 or 2. If +@var{count} is not given or zero, an endless sequence of random bytes +will be emitted. If used with @option{--armor} the output will be +base64 encoded. PLEASE, don't use this command unless you know what +you are doing; it may remove precious entropy from the system! @item --gen-prime @code{mode} @code{bits} @opindex gen-prime Use the source, Luke :-). The output format is still subject to change. @item --enarmor @item --dearmor @opindex enarmor @opindex --enarmor Pack or unpack an arbitrary input into/from an OpenPGP ASCII armor. This is a GnuPG extension to OpenPGP and in general not very useful. @end table @c ******************************************* @c ******* KEY MANGEMENT COMMANDS ********** @c ******************************************* @node OpenPGP Key Management @subsection How to manage your keys This section explains the main commands for key management @table @gnupgtabopt @item --gen-key @opindex gen-key Generate a new key pair. This command is normally only used interactively. There is an experimental feature which allows you to create keys in batch mode. See the file @file{doc/DETAILS} in the source distribution on how to use this. @item --gen-revoke @code{name} @opindex gen-revoke Generate a revocation certificate for the complete key. To revoke a subkey or a signature, use the @option{--edit} command. @item --desig-revoke @code{name} @opindex desig-revoke Generate a designated revocation certificate for a key. This allows a user (with the permission of the keyholder) to revoke someone else's key. @item --edit-key @opindex edit-key Present a menu which enables you to do most of the key management related tasks. It expects the specification of a key on the command line. @c ******** Begin Edit-key Options ********** @table @asis @item uid @code{n} @opindex keyedit:uid Toggle selection of user ID or photographic user ID with index @code{n}. Use @code{*} to select all and @code{0} to deselect all. @item key @code{n} @opindex keyedit:key Toggle selection of subkey with index @code{n}. Use @code{*} to select all and @code{0} to deselect all. @item sign @opindex keyedit:sign Make a signature on key of user @code{name} If the key is not yet signed by the default user (or the users given with -u), the program displays the information of the key again, together with its fingerprint and asks whether it should be signed. This question is repeated for all users specified with -u. @item lsign @opindex keyedit:lsign Same as "sign" but the signature is marked as non-exportable and will therefore never be used by others. This may be used to make keys valid only in the local environment. @item nrsign @opindex keyedit:nrsign Same as "sign" but the signature is marked as non-revocable and can therefore never be revoked. @item tsign @opindex keyedit:tsign Make a trust signature. This is a signature that combines the notions of certification (like a regular signature), and trust (like the "trust" command). It is generally only useful in distinct communities or groups. @end table @c man:.RS Note that "l" (for local / non-exportable), "nr" (for non-revocable, and "t" (for trust) may be freely mixed and prefixed to "sign" to create a signature of any type desired. @c man:.RE @table @asis @item delsig @opindex keyedit:delsig Delete a signature. Note that it is not possible to retract a signature, once it has been send to the public (i.e. to a keyserver). In that case you better use @code{revsig}. @item revsig @opindex keyedit:revsig Revoke a signature. For every signature which has been generated by one of the secret keys, GnuPG asks whether a revocation certificate should be generated. @item check @opindex keyedit:check Check the signatures on all selected user IDs. @item adduid @opindex keyedit:adduid Create an additional user ID. @item addphoto @opindex keyedit:addphoto Create a photographic user ID. This will prompt for a JPEG file that will be embedded into the user ID. Note that a very large JPEG will make for a very large key. Also note that some programs will display your JPEG unchanged (GnuPG), and some programs will scale it to fit in a dialog box (PGP). @item showphoto @opindex keyedit:showphoto Display the selected photographic user ID. @item deluid @opindex keyedit:deluid Delete a user ID or photographic user ID. Note that it is not possible to retract a user id, once it has been send to the public (i.e. to a keyserver). In that case you better use @code{revuid}. @item revuid @opindex keyedit:revuid Revoke a user ID or photographic user ID. @item primary @opindex keyedit:primary Flag the current user id as the primary one, removes the primary user id flag from all other user ids and sets the timestamp of all affected self-signatures one second ahead. Note that setting a photo user ID as primary makes it primary over other photo user IDs, and setting a regular user ID as primary makes it primary over other regular user IDs. @item keyserver @opindex keyedit:keyserver Set a preferred keyserver for the specified user ID(s). This allows other users to know where you prefer they get your key from. See @option{--keyserver-options honor-keyserver-url} for more on how this works. Setting a value of "none" removes an existing preferred keyserver. @item notation @opindex keyedit:notation Set a name=value notation for the specified user ID(s). See @option{--cert-notation} for more on how this works. Setting a value of "none" removes all notations, setting a notation prefixed with a minus sign (-) removes that notation, and setting a notation name (without the =value) prefixed with a minus sign removes all notations with that name. @item pref @opindex keyedit:pref List preferences from the selected user ID. This shows the actual preferences, without including any implied preferences. @item showpref @opindex keyedit:showpref More verbose preferences listing for the selected user ID. This shows the preferences in effect by including the implied preferences of 3DES (cipher), SHA-1 (digest), and Uncompressed (compression) if they are not already included in the preference list. In addition, the preferred keyserver and signature notations (if any) are shown. @item setpref @code{string} @opindex keyedit:setpref Set the list of user ID preferences to @code{string} for all (or just the selected) user IDs. Calling setpref with no arguments sets the preference list to the default (either built-in or set via @option{--default-preference-list}), and calling setpref with "none" as the argument sets an empty preference list. Use @command{@gpgname --version} to get a list of available algorithms. Note that while you can change the preferences on an attribute user ID (aka "photo ID"), GnuPG does not select keys via attribute user IDs so these preferences will not be used by GnuPG. When setting preferences, you should list the algorithms in the order which you'd like to see them used by someone else when encrypting a message to your key. If you don't include 3DES, it will be automatically added at the end. Note that there are many factors that go into choosing an algorithm (for example, your key may not be the only recipient), and so the remote OpenPGP application being used to send to you may or may not follow your exact chosen order for a given message. It will, however, only choose an algorithm that is present on the preference list of every recipient key. See also the INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS section below. @item addkey @opindex keyedit:addkey Add a subkey to this key. @item addcardkey @opindex keyedit:addcardkey Generate a subkey on a card and add it to this key. @item keytocard @opindex keyedit:keytocard Transfer the selected secret subkey (or the primary key if no subkey has been selected) to a smartcard. The secret key in the keyring will be replaced by a stub if the key could be stored successfully on the card and you use the save command later. Only certain key types may be transferred to the card. A sub menu allows you to select on what card to store the key. Note that it is not possible to get that key back from the card - if the card gets broken your secret key will be lost unless you have a backup somewhere. @item bkuptocard @code{file} @opindex keyedit:bkuptocard Restore the given file to a card. This command may be used to restore a backup key (as generated during card initialization) to a new card. In almost all cases this will be the encryption key. You should use this command only with the corresponding public key and make sure that the file given as argument is indeed the backup to restore. You should then select 2 to restore as encryption key. You will first be asked to enter the passphrase of the backup key and then for the Admin PIN of the card. @item delkey @opindex keyedit:delkey Remove a subkey (secondart key). Note that it is not possible to retract a subkey, once it has been send to the public (i.e. to a keyserver). In that case you better use @code{revkey}. @item revkey @opindex keyedit:revkey Revoke a subkey. @item expire @opindex keyedit:expire Change the key or subkey expiration time. If a subkey is selected, the expiration time of this subkey will be changed. With no selection, the key expiration of the primary key is changed. @item trust @opindex keyedit:trust Change the owner trust value for the key. This updates the trust-db immediately and no save is required. @item disable @itemx enable @opindex keyedit:disable @opindex keyedit:enable Disable or enable an entire key. A disabled key can not normally be used for encryption. @item addrevoker @opindex keyedit:addrevoker Add a designated revoker to the key. This takes one optional argument: "sensitive". If a designated revoker is marked as sensitive, it will not be exported by default (see export-options). @item passwd @opindex keyedit:passwd Change the passphrase of the secret key. @item toggle @opindex keyedit:toggle Toggle between public and secret key listing. @item clean @opindex keyedit:clean Compact (by removing all signatures except the selfsig) any user ID that is no longer usable (e.g. revoked, or expired). Then, remove any signatures that are not usable by the trust calculations. Specifically, this removes any signature that does not validate, any signature that is superseded by a later signature, revoked signatures, and signatures issued by keys that are not present on the keyring. @item minimize @opindex keyedit:minimize Make the key as small as possible. This removes all signatures from each user ID except for the most recent self-signature. @item cross-certify @opindex keyedit:cross-certify Add cross-certification signatures to signing subkeys that may not currently have them. Cross-certification signatures protect against a subtle attack against signing subkeys. See @option{--require-cross-certification}. All new keys generated have this signature by default, so this option is only useful to bring older keys up to date. @item save @opindex keyedit:save Save all changes to the key rings and quit. @item quit @opindex keyedit:quit Quit the program without updating the key rings. @end table @c man:.RS The listing shows you the key with its secondary keys and all user ids. The primary user id is indicated by a dot, and selected keys or user ids are indicated by an asterisk. The trust value is displayed with the primary key: the first is the assigned owner trust and the second is the calculated trust value. Letters are used for the values: @c man:.RE @table @asis @item - No ownertrust assigned / not yet calculated. @item e Trust calculation has failed; probably due to an expired key. @item q Not enough information for calculation. @item n Never trust this key. @item m Marginally trusted. @item f Fully trusted. @item u Ultimately trusted. @end table @c ******** End Edit-key Options ********** @item --sign-key @code{name} @opindex sign-key Signs a public key with your secret key. This is a shortcut version of the subcommand "sign" from @option{--edit}. @item --lsign-key @code{name} @opindex lsign-key Signs a public key with your secret key but marks it as non-exportable. This is a shortcut version of the subcommand "lsign" from @option{--edit-key}. @ifclear gpgone @item --passwd @var{user_id} @opindex passwd Change the passphrase of the secret key belonging to the certificate specified as @var{user_id}. This is a shortcut for the sub-command @code{passwd} of the edit key menu. @end ifclear @end table @c ******************************************* @c *************** **************** @c *************** OPTIONS **************** @c *************** **************** @c ******************************************* @mansect options @node GPG Options @section Option Summary @command{@gpgname} features a bunch of options to control the exact behaviour and to change the default configuration. @menu * GPG Configuration Options:: How to change the configuration. * GPG Key related Options:: Key related options. * GPG Input and Output:: Input and Output. * OpenPGP Options:: OpenPGP protocol specific options. * GPG Esoteric Options:: Doing things one usually don't want to do. @end menu Long options can be put in an options file (default "~/.gnupg/gpg.conf"). Short option names will not work - for example, "armor" is a valid option for the options file, while "a" is not. Do not write the 2 dashes, but simply the name of the option and any required arguments. Lines with a hash ('#') as the first non-white-space character are ignored. Commands may be put in this file too, but that is not generally useful as the command will execute automatically with every execution of gpg. Please remember that option parsing stops as soon as a non-option is encountered, you can explicitly stop parsing by using the special option @option{--}. @c ******************************************* @c ******** CONFIGURATION OPTIONS ********** @c ******************************************* @node GPG Configuration Options @subsection How to change the configuration These options are used to change the configuration and are usually found in the option file. @table @gnupgtabopt @item --default-key @var{name} @opindex default-key Use @var{name} as the default key to sign with. If this option is not used, the default key is the first key found in the secret keyring. Note that @option{-u} or @option{--local-user} overrides this option. @item --default-recipient @var{name} @opindex default-recipient Use @var{name} as default recipient if option @option{--recipient} is not used and don't ask if this is a valid one. @var{name} must be non-empty. @item --default-recipient-self @opindex default-recipient-self Use the default key as default recipient if option @option{--recipient} is not used and don't ask if this is a valid one. The default key is the first one from the secret keyring or the one set with @option{--default-key}. @item --no-default-recipient @opindex no-default-recipient Reset @option{--default-recipient} and @option{--default-recipient-self}. @item -v, --verbose @opindex verbose Give more information during processing. If used twice, the input data is listed in detail. @item --no-verbose @opindex no-verbose Reset verbose level to 0. @item -q, --quiet @opindex quiet Try to be as quiet as possible. @item --batch @itemx --no-batch @opindex batch @opindex no-batch Use batch mode. Never ask, do not allow interactive commands. @option{--no-batch} disables this option. Note that even with a filename given on the command line, gpg might still need to read from STDIN (in particular if gpg figures that the input is a detached signature and no data file has been specified). Thus if you do not want to feed data via STDIN, you should connect STDIN to @file{/dev/null}. @item --no-tty @opindex no-tty Make sure that the TTY (terminal) is never used for any output. This option is needed in some cases because GnuPG sometimes prints warnings to the TTY even if @option{--batch} is used. @item --yes @opindex yes Assume "yes" on most questions. @item --no @opindex no Assume "no" on most questions. @item --list-options @code{parameters} @opindex list-options This is a space or comma delimited string that gives options used when listing keys and signatures (that is, @option{--list-keys}, @option{--list-sigs}, @option{--list-public-keys}, @option{--list-secret-keys}, and the @option{--edit-key} functions). Options can be prepended with a @option{no-} (after the two dashes) to give the opposite meaning. The options are: @table @asis @item show-photos @opindex list-options:show-photos Causes @option{--list-keys}, @option{--list-sigs}, @option{--list-public-keys}, and @option{--list-secret-keys} to display any photo IDs attached to the key. Defaults to no. See also @option{--photo-viewer}. @item show-policy-urls @opindex list-options:show-policy-urls Show policy URLs in the @option{--list-sigs} or @option{--check-sigs} listings. Defaults to no. @item show-notations @itemx show-std-notations @itemx show-user-notations @opindex list-options:show-notations @opindex list-options:show-std-notations @opindex list-options:show-user-notations Show all, IETF standard, or user-defined signature notations in the @option{--list-sigs} or @option{--check-sigs} listings. Defaults to no. @item show-keyserver-urls Show any preferred keyserver URL in the @option{--list-sigs} or @option{--check-sigs} listings. Defaults to no. @item show-uid-validity Display the calculated validity of user IDs during key listings. Defaults to no. @item show-unusable-uids Show revoked and expired user IDs in key listings. Defaults to no. @item show-unusable-subkeys Show revoked and expired subkeys in key listings. Defaults to no. @item show-keyring Display the keyring name at the head of key listings to show which keyring a given key resides on. Defaults to no. @item show-sig-expire Show signature expiration dates (if any) during @option{--list-sigs} or @option{--check-sigs} listings. Defaults to no. @item show-sig-subpackets Include signature subpackets in the key listing. This option can take an optional argument list of the subpackets to list. If no argument is passed, list all subpackets. Defaults to no. This option is only meaningful when using @option{--with-colons} along with @option{--list-sigs} or @option{--check-sigs}. @end table @item --verify-options @code{parameters} This is a space or comma delimited string that gives options used when verifying signatures. Options can be prepended with a `no-' to give the opposite meaning. The options are: @table @asis @item show-photos Display any photo IDs present on the key that issued the signature. Defaults to no. See also @option{--photo-viewer}. @item show-policy-urls Show policy URLs in the signature being verified. Defaults to no. @item show-notations @itemx show-std-notations @itemx show-user-notations Show all, IETF standard, or user-defined signature notations in the signature being verified. Defaults to IETF standard. @item show-keyserver-urls Show any preferred keyserver URL in the signature being verified. Defaults to no. @item show-uid-validity Display the calculated validity of the user IDs on the key that issued the signature. Defaults to no. @item show-unusable-uids Show revoked and expired user IDs during signature verification. Defaults to no. @item show-primary-uid-only Show only the primary user ID during signature verification. That is all the AKA lines as well as photo Ids are not shown with the signature verification status. @item pka-lookups Enable PKA lookups to verify sender addresses. Note that PKA is based on DNS, and so enabling this option may disclose information on when and what signatures are verified or to whom data is encrypted. This is similar to the "web bug" described for the auto-key-retrieve feature. @item pka-trust-increase Raise the trust in a signature to full if the signature passes PKA validation. This option is only meaningful if pka-lookups is set. @end table @item --enable-dsa2 @itemx --disable-dsa2 Enable hash truncation for all DSA keys even for old DSA Keys up to 1024 bit. This is also the default with @option{--openpgp}. Note that older versions of GnuPG also required this flag to allow the generation of DSA larger than 1024 bit. @item --photo-viewer @code{string} This is the command line that should be run to view a photo ID. "%i" will be expanded to a filename containing the photo. "%I" does the same, except the file will not be deleted once the viewer exits. Other flags are "%k" for the key ID, "%K" for the long key ID, "%f" for the key fingerprint, "%t" for the extension of the image type (e.g. "jpg"), "%T" for the MIME type of the image (e.g. "image/jpeg"), and "%%" for an actual percent sign. If neither %i or %I are present, then the photo will be supplied to the viewer on standard input. The default viewer is "xloadimage -fork -quiet -title 'KeyID 0x%k' STDIN". Note that if your image viewer program is not secure, then executing it from GnuPG does not make it secure. @item --exec-path @code{string} Sets a list of directories to search for photo viewers and keyserver helpers. If not provided, keyserver helpers use the compiled-in default directory, and photo viewers use the $PATH environment variable. Note, that on W32 system this value is ignored when searching for keyserver helpers. @item --keyring @code{file} Add @code{file} to the current list of keyrings. If @code{file} begins with a tilde and a slash, these are replaced by the $HOME directory. If the filename does not contain a slash, it is assumed to be in the GnuPG home directory ("~/.gnupg" if @option{--homedir} or $GNUPGHOME is not used). Note that this adds a keyring to the current list. If the intent is to use the specified keyring alone, use @option{--keyring} along with @option{--no-default-keyring}. @item --secret-keyring @code{file} Same as @option{--keyring} but for the secret keyrings. @item --primary-keyring @code{file} Designate @code{file} as the primary public keyring. This means that newly imported keys (via @option{--import} or keyserver @option{--recv-from}) will go to this keyring. @item --trustdb-name @code{file} Use @code{file} instead of the default trustdb. If @code{file} begins with a tilde and a slash, these are replaced by the $HOME directory. If the filename does not contain a slash, it is assumed to be in the GnuPG home directory (@file{~/.gnupg} if @option{--homedir} or $GNUPGHOME is not used). @ifset gpgone @anchor{option --homedir} @end ifset @include opt-homedir.texi @ifset gpgone @item --pcsc-driver @code{file} Use @code{file} to access the smartcard reader. The current default is `libpcsclite.so.1' for GLIBC based systems, `/System/Library/Frameworks/PCSC.framework/PCSC' for MAC OS X, `winscard.dll' for Windows and `libpcsclite.so' for other systems. @end ifset @ifset gpgone @item --disable-ccid Disable the integrated support for CCID compliant readers. This allows to fall back to one of the other drivers even if the internal CCID driver can handle the reader. Note, that CCID support is only available if libusb was available at build time. @end ifset @ifset gpgone @item --reader-port @code{number_or_string} This option may be used to specify the port of the card terminal. A value of 0 refers to the first serial device; add 32768 to access USB devices. The default is 32768 (first USB device). PC/SC or CCID readers might need a string here; run the program in verbose mode to get a list of available readers. The default is then the first reader found. @end ifset @item --display-charset @code{name} Set the name of the native character set. This is used to convert some informational strings like user IDs to the proper UTF-8 encoding. Note that this has nothing to do with the character set of data to be encrypted or signed; GnuPG does not recode user-supplied data. If this option is not used, the default character set is determined from the current locale. A verbosity level of 3 shows the chosen set. Valid values for @code{name} are: @table @asis @item iso-8859-1 This is the Latin 1 set. @item iso-8859-2 The Latin 2 set. @item iso-8859-15 This is currently an alias for the Latin 1 set. @item koi8-r The usual Russian set (rfc1489). @item utf-8 Bypass all translations and assume that the OS uses native UTF-8 encoding. @end table @item --utf8-strings @itemx --no-utf8-strings Assume that command line arguments are given as UTF8 strings. The default (@option{--no-utf8-strings}) is to assume that arguments are encoded in the character set as specified by @option{--display-charset}. These options affect all following arguments. Both options may be used multiple times. @ifset gpgone @anchor{option --options} @end ifset @item --options @code{file} Read options from @code{file} and do not try to read them from the default options file in the homedir (see @option{--homedir}). This option is ignored if used in an options file. @item --no-options Shortcut for @option{--options /dev/null}. This option is detected before an attempt to open an option file. Using this option will also prevent the creation of a @file{~/.gnupg} homedir. @item -z @code{n} @itemx --compress-level @code{n} @itemx --bzip2-compress-level @code{n} Set compression level to @code{n} for the ZIP and ZLIB compression algorithms. The default is to use the default compression level of zlib (normally 6). @option{--bzip2-compress-level} sets the compression level for the BZIP2 compression algorithm (defaulting to 6 as well). This is a different option from @option{--compress-level} since BZIP2 uses a significant amount of memory for each additional compression level. @option{-z} sets both. A value of 0 for @code{n} disables compression. @item --bzip2-decompress-lowmem Use a different decompression method for BZIP2 compressed files. This alternate method uses a bit more than half the memory, but also runs at half the speed. This is useful under extreme low memory circumstances when the file was originally compressed at a high @option{--bzip2-compress-level}. @item --mangle-dos-filenames @itemx --no-mangle-dos-filenames @opindex mangle-dos-filenames @opindex no-mangle-dos-filenames Older version of Windows cannot handle filenames with more than one dot. @option{--mangle-dos-filenames} causes GnuPG to replace (rather than add to) the extension of an output filename to avoid this problem. This option is off by default and has no effect on non-Windows platforms. @item --ask-cert-level @itemx --no-ask-cert-level When making a key signature, prompt for a certification level. If this option is not specified, the certification level used is set via @option{--default-cert-level}. See @option{--default-cert-level} for information on the specific levels and how they are used. @option{--no-ask-cert-level} disables this option. This option defaults to no. @item --default-cert-level @code{n} The default to use for the check level when signing a key. 0 means you make no particular claim as to how carefully you verified the key. 1 means you believe the key is owned by the person who claims to own it but you could not, or did not verify the key at all. This is useful for a "persona" verification, where you sign the key of a pseudonymous user. 2 means you did casual verification of the key. For example, this could mean that you verified that the key fingerprint and checked the user ID on the key against a photo ID. 3 means you did extensive verification of the key. For example, this could mean that you verified the key fingerprint with the owner of the key in person, and that you checked, by means of a hard to forge document with a photo ID (such as a passport) that the name of the key owner matches the name in the user ID on the key, and finally that you verified (by exchange of email) that the email address on the key belongs to the key owner. Note that the examples given above for levels 2 and 3 are just that: examples. In the end, it is up to you to decide just what "casual" and "extensive" mean to you. This option defaults to 0 (no particular claim). @item --min-cert-level When building the trust database, treat any signatures with a certification level below this as invalid. Defaults to 2, which disregards level 1 signatures. Note that level 0 "no particular claim" signatures are always accepted. @item --trusted-key @code{long key ID} Assume that the specified key (which must be given as a full 8 byte key ID) is as trustworthy as one of your own secret keys. This option is useful if you don't want to keep your secret keys (or one of them) online but still want to be able to check the validity of a given recipient's or signator's key. @item --trust-model @code{pgp|classic|direct|always|auto} Set what trust model GnuPG should follow. The models are: @table @asis @item pgp This is the Web of Trust combined with trust signatures as used in PGP 5.x and later. This is the default trust model when creating a new trust database. @item classic This is the standard Web of Trust as used in PGP 2.x and earlier. @item direct Key validity is set directly by the user and not calculated via the Web of Trust. @item always Skip key validation and assume that used keys are always fully trusted. You generally won't use this unless you are using some external validation scheme. This option also suppresses the "[uncertain]" tag printed with signature checks when there is no evidence that the user ID is bound to the key. @item auto Select the trust model depending on whatever the internal trust database says. This is the default model if such a database already exists. @end table @item --auto-key-locate @code{parameters} @itemx --no-auto-key-locate GnuPG can automatically locate and retrieve keys as needed using this option. This happens when encrypting to an email address (in the "user@@example.com" form), and there are no user@@example.com keys on the local keyring. This option takes any number of the following mechanisms, in the order they are to be tried: @table @asis @item cert Locate a key using DNS CERT, as specified in rfc4398. @item pka Locate a key using DNS PKA. @item ldap Using DNS Service Discovery, check the domain in question for any LDAP keyservers to use. If this fails, attempt to locate the key using the PGP Universal method of checking @samp{ldap://keys.(thedomain)}. @item keyserver Locate a key using whatever keyserver is defined using the @option{--keyserver} option. @item keyserver-URL In addition, a keyserver URL as used in the @option{--keyserver} option may be used here to query that particular keyserver. @item local Locate the key using the local keyrings. This mechanism allows to select the order a local key lookup is done. Thus using @samp{--auto-key-locate local} is identical to @option{--no-auto-key-locate}. @item nodefault This flag disables the standard local key lookup, done before any of the mechanisms defined by the @option{--auto-key-locate} are tried. The position of this mechanism in the list does not matter. It is not required if @code{local} is also used. @end table @item --keyid-format @code{short|0xshort|long|0xlong} Select how to display key IDs. "short" is the traditional 8-character key ID. "long" is the more accurate (but less convenient) 16-character key ID. Add an "0x" to either to include an "0x" at the beginning of the key ID, as in 0x99242560. @item --keyserver @code{name} Use @code{name} as your keyserver. This is the server that @option{--recv-keys}, @option{--send-keys}, and @option{--search-keys} will communicate with to receive keys from, send keys to, and search for keys on. The format of the @code{name} is a URI: `scheme:[//]keyservername[:port]' The scheme is the type of keyserver: "hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP keyservers, or "mailto" for the Graff email keyserver. Note that your particular installation of GnuPG may have other keyserver types available as well. Keyserver schemes are case-insensitive. After the keyserver name, optional keyserver configuration options may be provided. These are the same as the global @option{--keyserver-options} from below, but apply only to this particular keyserver. Most keyservers synchronize with each other, so there is generally no need to send keys to more than one server. The keyserver @code{hkp://keys.gnupg.net} uses round robin DNS to give a different keyserver each time you use it. @item --keyserver-options @code{name=value1 } This is a space or comma delimited string that gives options for the keyserver. Options can be prefixed with a `no-' to give the opposite meaning. Valid import-options or export-options may be used here as well to apply to importing (@option{--recv-key}) or exporting (@option{--send-key}) a key from a keyserver. While not all options are available for all keyserver types, some common options are: @table @asis @item include-revoked When searching for a key with @option{--search-keys}, include keys that are marked on the keyserver as revoked. Note that not all keyservers differentiate between revoked and unrevoked keys, and for such keyservers this option is meaningless. Note also that most keyservers do not have cryptographic verification of key revocations, and so turning this option off may result in skipping keys that are incorrectly marked as revoked. @item include-disabled When searching for a key with @option{--search-keys}, include keys that are marked on the keyserver as disabled. Note that this option is not used with HKP keyservers. @item auto-key-retrieve This option enables the automatic retrieving of keys from a keyserver when verifying signatures made by keys that are not on the local keyring. Note that this option makes a "web bug" like behavior possible. Keyserver operators can see which keys you request, so by sending you a message signed by a brand new key (which you naturally will not have on your local keyring), the operator can tell both your IP address and the time when you verified the signature. @item honor-keyserver-url When using @option{--refresh-keys}, if the key in question has a preferred keyserver URL, then use that preferred keyserver to refresh the key from. In addition, if auto-key-retrieve is set, and the signature being verified has a preferred keyserver URL, then use that preferred keyserver to fetch the key from. Defaults to yes. @item honor-pka-record If auto-key-retrieve is set, and the signature being verified has a PKA record, then use the PKA information to fetch the key. Defaults to yes. @item include-subkeys When receiving a key, include subkeys as potential targets. Note that this option is not used with HKP keyservers, as they do not support retrieving keys by subkey id. @item use-temp-files On most Unix-like platforms, GnuPG communicates with the keyserver helper program via pipes, which is the most efficient method. This option forces GnuPG to use temporary files to communicate. On some platforms (such as Win32 and RISC OS), this option is always enabled. @item keep-temp-files If using `use-temp-files', do not delete the temp files after using them. This option is useful to learn the keyserver communication protocol by reading the temporary files. @item verbose Tell the keyserver helper program to be more verbose. This option can be repeated multiple times to increase the verbosity level. @item timeout Tell the keyserver helper program how long (in seconds) to try and perform a keyserver action before giving up. Note that performing multiple actions at the same time uses this timeout value per action. For example, when retrieving multiple keys via @option{--recv-keys}, the timeout applies separately to each key retrieval, and not to the @option{--recv-keys} command as a whole. Defaults to 30 seconds. @item http-proxy=@code{value} Set the proxy to use for HTTP and HKP keyservers. This overrides the "http_proxy" environment variable, if any. @item max-cert-size When retrieving a key via DNS CERT, only accept keys up to this size. Defaults to 16384 bytes. @item debug Turn on debug output in the keyserver helper program. Note that the details of debug output depends on which keyserver helper program is being used, and in turn, on any libraries that the keyserver helper program uses internally (libcurl, openldap, etc). @item check-cert Enable certificate checking if the keyserver presents one (for hkps or ldaps). Defaults to on. @item ca-cert-file Provide a certificate store to override the system default. Only necessary if check-cert is enabled, and the keyserver is using a certificate that is not present in a system default certificate list. Note that depending on the SSL library that the keyserver helper is built with, this may actually be a directory or a file. @end table @item --completes-needed @code{n} Number of completely trusted users to introduce a new key signer (defaults to 1). @item --marginals-needed @code{n} Number of marginally trusted users to introduce a new key signer (defaults to 3) @item --max-cert-depth @code{n} Maximum depth of a certification chain (default is 5). @item --simple-sk-checksum Secret keys are integrity protected by using a SHA-1 checksum. This method is part of the upcoming enhanced OpenPGP specification but GnuPG already uses it as a countermeasure against certain attacks. Old applications don't understand this new format, so this option may be used to switch back to the old behaviour. Using this option bears a security risk. Note that using this option only takes effect when the secret key is encrypted - the simplest way to make this happen is to change the passphrase on the key (even changing it to the same value is acceptable). @item --no-sig-cache Do not cache the verification status of key signatures. Caching gives a much better performance in key listings. However, if you suspect that your public keyring is not save against write modifications, you can use this option to disable the caching. It probably does not make sense to disable it because all kind of damage can be done if someone else has write access to your public keyring. @item --no-sig-create-check GnuPG normally verifies each signature right after creation to protect against bugs and hardware malfunctions which could leak out bits from the secret key. This extra verification needs some time (about 115% for DSA keys), and so this option can be used to disable it. However, due to the fact that the signature creation needs manual interaction, this performance penalty does not matter in most settings. @item --auto-check-trustdb @itemx --no-auto-check-trustdb If GnuPG feels that its information about the Web of Trust has to be updated, it automatically runs the @option{--check-trustdb} command internally. This may be a time consuming process. @option{--no-auto-check-trustdb} disables this option. @item --use-agent @itemx --no-use-agent @ifclear gpgone This is dummy option. @command{@gpgname} always requires the agent. @end ifclear @ifset gpgone Try to use the GnuPG-Agent. With this option, GnuPG first tries to connect to the agent before it asks for a passphrase. @option{--no-use-agent} disables this option. @end ifset @item --gpg-agent-info @ifclear gpgone This is dummy option. It has no effect when used with @command{gpg2}. @end ifclear @ifset gpgone Override the value of the environment variable @samp{GPG_AGENT_INFO}. This is only used when @option{--use-agent} has been given. Given that this option is not anymore used by @command{gpg2}, it should be avoided if possible. @end ifset @item --lock-once Lock the databases the first time a lock is requested and do not release the lock until the process terminates. @item --lock-multiple Release the locks every time a lock is no longer needed. Use this to override a previous @option{--lock-once} from a config file. @item --lock-never Disable locking entirely. This option should be used only in very special environments, where it can be assured that only one process is accessing those files. A bootable floppy with a stand-alone encryption system will probably use this. Improper usage of this option may lead to data and key corruption. @item --exit-on-status-write-error This option will cause write errors on the status FD to immediately terminate the process. That should in fact be the default but it never worked this way and thus we need an option to enable this, so that the change won't break applications which close their end of a status fd connected pipe too early. Using this option along with @option{--enable-progress-filter} may be used to cleanly cancel long running gpg operations. @item --limit-card-insert-tries @code{n} With @code{n} greater than 0 the number of prompts asking to insert a smartcard gets limited to N-1. Thus with a value of 1 gpg won't at all ask to insert a card if none has been inserted at startup. This option is useful in the configuration file in case an application does not know about the smartcard support and waits ad infinitum for an inserted card. @item --no-random-seed-file GnuPG uses a file to store its internal random pool over invocations. This makes random generation faster; however sometimes write operations are not desired. This option can be used to achieve that with the cost of slower random generation. @item --no-greeting Suppress the initial copyright message. @item --no-secmem-warning Suppress the warning about "using insecure memory". @item --no-permission-warning Suppress the warning about unsafe file and home directory (@option{--homedir}) permissions. Note that the permission checks that GnuPG performs are not intended to be authoritative, but rather they simply warn about certain common permission problems. Do not assume that the lack of a warning means that your system is secure. Note that the warning for unsafe @option{--homedir} permissions cannot be suppressed in the gpg.conf file, as this would allow an attacker to place an unsafe gpg.conf file in place, and use this file to suppress warnings about itself. The @option{--homedir} permissions warning may only be suppressed on the command line. @item --no-mdc-warning Suppress the warning about missing MDC integrity protection. @item --require-secmem @itemx --no-require-secmem Refuse to run if GnuPG cannot get secure memory. Defaults to no (i.e. run, but give a warning). @item --require-cross-certification @itemx --no-require-cross-certification When verifying a signature made from a subkey, ensure that the cross certification "back signature" on the subkey is present and valid. This protects against a subtle attack against subkeys that can sign. Defaults to @option{--require-cross-certification} for @command{@gpgname}. @item --expert @itemx --no-expert Allow the user to do certain nonsensical or "silly" things like signing an expired or revoked key, or certain potentially incompatible things like generating unusual key types. This also disables certain warning messages about potentially incompatible actions. As the name implies, this option is for experts only. If you don't fully understand the implications of what it allows you to do, leave this off. @option{--no-expert} disables this option. @end table @c ******************************************* @c ******** KEY RELATED OPTIONS ************ @c ******************************************* @node GPG Key related Options @subsection Key related options @table @gnupgtabopt @item --recipient @var{name} @itemx -r @opindex recipient Encrypt for user id @var{name}. If this option or @option{--hidden-recipient} is not specified, GnuPG asks for the user-id unless @option{--default-recipient} is given. @item --hidden-recipient @var{name} @itemx -R @opindex hidden-recipient Encrypt for user ID @var{name}, but hide the key ID of this user's key. This option helps to hide the receiver of the message and is a limited countermeasure against traffic analysis. If this option or @option{--recipient} is not specified, GnuPG asks for the user ID unless @option{--default-recipient} is given. @item --encrypt-to @code{name} Same as @option{--recipient} but this one is intended for use in the options file and may be used with your own user-id as an "encrypt-to-self". These keys are only used when there are other recipients given either by use of @option{--recipient} or by the asked user id. No trust checking is performed for these user ids and even disabled keys can be used. @item --hidden-encrypt-to @code{name} Same as @option{--hidden-recipient} but this one is intended for use in the options file and may be used with your own user-id as a hidden "encrypt-to-self". These keys are only used when there are other recipients given either by use of @option{--recipient} or by the asked user id. No trust checking is performed for these user ids and even disabled keys can be used. @item --no-encrypt-to Disable the use of all @option{--encrypt-to} and @option{--hidden-encrypt-to} keys. @item --group @code{name=value1 } Sets up a named group, which is similar to aliases in email programs. Any time the group name is a recipient (@option{-r} or @option{--recipient}), it will be expanded to the values specified. Multiple groups with the same name are automatically merged into a single group. The values are @code{key IDs} or fingerprints, but any key description is accepted. Note that a value with spaces in it will be treated as two different values. Note also there is only one level of expansion --- you cannot make an group that points to another group. When used from the command line, it may be necessary to quote the argument to this option to prevent the shell from treating it as multiple arguments. @item --ungroup @code{name} Remove a given entry from the @option{--group} list. @item --no-groups Remove all entries from the @option{--group} list. @item --local-user @var{name} @itemx -u @opindex local-user Use @var{name} as the key to sign with. Note that this option overrides @option{--default-key}. @item --try-all-secrets @opindex try-all-secrets Don't look at the key ID as stored in the message but try all secret keys in turn to find the right decryption key. This option forces the behaviour as used by anonymous recipients (created by using @option{--throw-keyids}) and might come handy in case where an encrypted message contains a bogus key ID. @item --skip-hidden-recipients @itemx --no-skip-hidden-recipients @opindex skip-hidden-recipients @opindex no-skip-hidden-recipients During decryption skip all anonymous recipients. This option helps in the case that people use the hidden recipients feature to hide there own encrypt-to key from others. If oneself has many secret keys this may lead to a major annoyance because all keys are tried in turn to decrypt soemthing which was not really intended for it. The drawback of this option is that it is currently not possible to decrypt a message which includes real anonymous recipients. @end table @c ******************************************* @c ******** INPUT AND OUTPUT *************** @c ******************************************* @node GPG Input and Output @subsection Input and Output @table @gnupgtabopt @item --armor @itemx -a @opindex armor Create ASCII armored output. The default is to create the binary OpenPGP format. @item --no-armor Assume the input data is not in ASCII armored format. @item --output @var{file} @itemx -o @var{file} @opindex output Write output to @var{file}. @item --max-output @code{n} @opindex max-output This option sets a limit on the number of bytes that will be generated when processing a file. Since OpenPGP supports various levels of compression, it is possible that the plaintext of a given message may be significantly larger than the original OpenPGP message. While GnuPG works properly with such messages, there is often a desire to set a maximum file size that will be generated before processing is forced to stop by the OS limits. Defaults to 0, which means "no limit". @item --import-options @code{parameters} This is a space or comma delimited string that gives options for importing keys. Options can be prepended with a `no-' to give the opposite meaning. The options are: @table @asis @item import-local-sigs Allow importing key signatures marked as "local". This is not generally useful unless a shared keyring scheme is being used. Defaults to no. @item repair-pks-subkey-bug During import, attempt to repair the damage caused by the PKS keyserver bug (pre version 0.9.6) that mangles keys with multiple subkeys. Note that this cannot completely repair the damaged key as some crucial data is removed by the keyserver, but it does at least give you back one subkey. Defaults to no for regular @option{--import} and to yes for keyserver @option{--recv-keys}. @item merge-only During import, allow key updates to existing keys, but do not allow any new keys to be imported. Defaults to no. @item import-clean After import, compact (remove all signatures except the self-signature) any user IDs from the new key that are not usable. Then, remove any signatures from the new key that are not usable. This includes signatures that were issued by keys that are not present on the keyring. This option is the same as running the @option{--edit-key} command "clean" after import. Defaults to no. @item import-minimal Import the smallest key possible. This removes all signatures except the most recent self-signature on each user ID. This option is the same as running the @option{--edit-key} command "minimize" after import. Defaults to no. @end table @item --export-options @code{parameters} This is a space or comma delimited string that gives options for exporting keys. Options can be prepended with a `no-' to give the opposite meaning. The options are: @table @asis @item export-local-sigs Allow exporting key signatures marked as "local". This is not generally useful unless a shared keyring scheme is being used. Defaults to no. @item export-attributes Include attribute user IDs (photo IDs) while exporting. This is useful to export keys if they are going to be used by an OpenPGP program that does not accept attribute user IDs. Defaults to yes. @item export-sensitive-revkeys Include designated revoker information that was marked as "sensitive". Defaults to no. @item export-reset-subkey-passwd When using the @option{--export-secret-subkeys} command, this option resets the passphrases for all exported subkeys to empty. This is useful when the exported subkey is to be used on an unattended machine where a passphrase doesn't necessarily make sense. Defaults to no. @item export-clean Compact (remove all signatures from) user IDs on the key being exported if the user IDs are not usable. Also, do not export any signatures that are not usable. This includes signatures that were issued by keys that are not present on the keyring. This option is the same as running the @option{--edit-key} command "clean" before export except that the local copy of the key is not modified. Defaults to no. @item export-minimal Export the smallest key possible. This removes all signatures except the most recent self-signature on each user ID. This option is the same as running the @option{--edit-key} command "minimize" before export except that the local copy of the key is not modified. Defaults to no. @end table @item --with-colons @opindex with-colons Print key listings delimited by colons. Note that the output will be encoded in UTF-8 regardless of any @option{--display-charset} setting. This format is useful when GnuPG is called from scripts and other programs as it is easily machine parsed. The details of this format are documented in the file @file{doc/DETAILS}, which is included in the GnuPG source distribution. @item --fixed-list-mode @opindex fixed-list-mode Do not merge primary user ID and primary key in @option{--with-colon} listing mode and print all timestamps as seconds since 1970-01-01. @ifclear gpgone Since GnuPG 2.0.10, this mode is always used and thus this option is obsolete; it does not harm to use it though. @end ifclear @item --with-fingerprint @opindex with-fingerprint Same as the command @option{--fingerprint} but changes only the format of the output and may be used together with another command. @end table @c ******************************************* @c ******** OPENPGP OPTIONS **************** @c ******************************************* @node OpenPGP Options @subsection OpenPGP protocol specific options. @table @gnupgtabopt @item -t, --textmode @itemx --no-textmode Treat input files as text and store them in the OpenPGP canonical text form with standard "CRLF" line endings. This also sets the necessary flags to inform the recipient that the encrypted or signed data is text and may need its line endings converted back to whatever the local system uses. This option is useful when communicating between two platforms that have different line ending conventions (UNIX-like to Mac, Mac to Windows, etc). @option{--no-textmode} disables this option, and is the default. @ifset gpgone If @option{-t} (but not @option{--textmode}) is used together with armoring and signing, this enables clearsigned messages. This kludge is needed for command-line compatibility with command-line versions of PGP; normally you would use @option{--sign} or @option{--clearsign} to select the type of the signature. @end ifset @item --force-v3-sigs @itemx --no-force-v3-sigs OpenPGP states that an implementation should generate v4 signatures but PGP versions 5 through 7 only recognize v4 signatures on key material. This option forces v3 signatures for signatures on data. Note that this option implies @option{--ask-sig-expire}, @option{--sig-policy-url}, @option{--sig-notation}, and @option{--sig-keyserver-url}, as these features cannot be used with v3 signatures. @option{--no-force-v3-sigs} disables this option. @item --force-v4-certs @itemx --no-force-v4-certs Always use v4 key signatures even on v3 keys. This option also changes the default hash algorithm for v3 RSA keys from MD5 to SHA-1. @option{--no-force-v4-certs} disables this option. @item --force-mdc Force the use of encryption with a modification detection code. This is always used with the newer ciphers (those with a blocksize greater than 64 bits), or if all of the recipient keys indicate MDC support in their feature flags. @item --disable-mdc Disable the use of the modification detection code. Note that by using this option, the encrypted message becomes vulnerable to a message modification attack. @item --personal-cipher-preferences @code{string} Set the list of personal cipher preferences to @code{string}. Use @command{@gpgname --version} to get a list of available algorithms, and use @code{none} to set no preference at all. This allows the user to safely override the algorithm chosen by the recipient key preferences, as GPG will only select an algorithm that is usable by all recipients. The most highly ranked cipher in this list is also used for the @option{--symmetric} encryption command. @item --personal-digest-preferences @code{string} Set the list of personal digest preferences to @code{string}. Use @command{@gpgname --version} to get a list of available algorithms, and use @code{none} to set no preference at all. This allows the user to safely override the algorithm chosen by the recipient key preferences, as GPG will only select an algorithm that is usable by all recipients. The most highly ranked digest algorithm in this list is also used when signing without encryption (e.g. @option{--clearsign} or @option{--sign}). The default value is SHA-1. @item --personal-compress-preferences @code{string} Set the list of personal compression preferences to @code{string}. Use @command{@gpgname --version} to get a list of available algorithms, and use @code{none} to set no preference at all. This allows the user to safely override the algorithm chosen by the recipient key preferences, as GPG will only select an algorithm that is usable by all recipients. The most highly ranked compression algorithm in this list is also used when there are no recipient keys to consider (e.g. @option{--symmetric}). @item --s2k-cipher-algo @code{name} Use @code{name} as the cipher algorithm used to protect secret keys. The default cipher is CAST5. This cipher is also used for conventional encryption if @option{--personal-cipher-preferences} and @option{--cipher-algo} is not given. @item --s2k-digest-algo @code{name} Use @code{name} as the digest algorithm used to mangle the passphrases. The default algorithm is SHA-1. @item --s2k-mode @code{n} Selects how passphrases are mangled. If @code{n} is 0 a plain passphrase (which is not recommended) will be used, a 1 adds a salt to the passphrase and a 3 (the default) iterates the whole process a number of times (see --s2k-count). Unless @option{--rfc1991} is used, this mode is also used for conventional encryption. @item --s2k-count @code{n} Specify how many times the passphrase mangling is repeated. This value may range between 1024 and 65011712 inclusive, and the default is 65536. Note that not all values in the 1024-65011712 range are legal and if an illegal value is selected, GnuPG will round up to the nearest legal value. This option is only meaningful if @option{--s2k-mode} is 3. @end table @c *************************** @c ******* Compliance ******** @c *************************** @subsection Compliance options These options control what GnuPG is compliant to. Only one of these options may be active at a time. Note that the default setting of this is nearly always the correct one. See the INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS section below before using one of these options. @table @gnupgtabopt @item --gnupg @opindex gnupg Use standard GnuPG behavior. This is essentially OpenPGP behavior (see @option{--openpgp}), but with some additional workarounds for common compatibility problems in different versions of PGP. This is the default option, so it is not generally needed, but it may be useful to override a different compliance option in the gpg.conf file. @item --openpgp @opindex openpgp Reset all packet, cipher and digest options to strict OpenPGP behavior. Use this option to reset all previous options like @option{--s2k-*}, @option{--cipher-algo}, @option{--digest-algo} and @option{--compress-algo} to OpenPGP compliant values. All PGP workarounds are disabled. @item --rfc4880 @opindex rfc4880 Reset all packet, cipher and digest options to strict RFC-4880 behavior. Note that this is currently the same thing as @option{--openpgp}. @item --rfc2440 @opindex rfc2440 Reset all packet, cipher and digest options to strict RFC-2440 behavior. @item --rfc1991 @opindex rfc1991 Try to be more RFC-1991 (PGP 2.x) compliant. @item --pgp2 @opindex pgp2 Set up all options to be as PGP 2.x compliant as possible, and warn if an action is taken (e.g. encrypting to a non-RSA key) that will create a message that PGP 2.x will not be able to handle. Note that `PGP 2.x' here means `MIT PGP 2.6.2'. There are other versions of PGP 2.x available, but the MIT release is a good common baseline. This option implies @option{--rfc1991 --disable-mdc --no-force-v4-certs --escape-from-lines --force-v3-sigs --cipher-algo IDEA --digest-algo MD5 --compress-algo ZIP}. It also disables @option{--textmode} when encrypting. @item --pgp6 @opindex pgp6 Set up all options to be as PGP 6 compliant as possible. This restricts you to the ciphers IDEA (if the IDEA plugin is installed), 3DES, and CAST5, the hashes MD5, SHA1 and RIPEMD160, and the compression algorithms none and ZIP. This also disables --throw-keyids, and making signatures with signing subkeys as PGP 6 does not understand signatures made by signing subkeys. This option implies @option{--disable-mdc --escape-from-lines --force-v3-sigs}. @item --pgp7 @opindex pgp7 Set up all options to be as PGP 7 compliant as possible. This is identical to @option{--pgp6} except that MDCs are not disabled, and the list of allowable ciphers is expanded to add AES128, AES192, AES256, and TWOFISH. @item --pgp8 @opindex pgp8 Set up all options to be as PGP 8 compliant as possible. PGP 8 is a lot closer to the OpenPGP standard than previous versions of PGP, so all this does is disable @option{--throw-keyids} and set @option{--escape-from-lines}. All algorithms are allowed except for the SHA224, SHA384, and SHA512 digests. @end table @c ******************************************* @c ******** ESOTERIC OPTIONS *************** @c ******************************************* @node GPG Esoteric Options @subsection Doing things one usually doesn't want to do. @table @gnupgtabopt @item -n @itemx --dry-run @opindex dry-run Don't make any changes (this is not completely implemented). @item --list-only Changes the behaviour of some commands. This is like @option{--dry-run} but different in some cases. The semantic of this command may be extended in the future. Currently it only skips the actual decryption pass and therefore enables a fast listing of the encryption keys. @item -i @itemx --interactive @opindex interactive Prompt before overwriting any files. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or by a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @item --debug @var{flags} @opindex debug Set debugging flags. All flags are or-ed and @var{flags} may be given in C syntax (e.g. 0x0042). @item --debug-all Set all useful debugging flags. @ifset gpgone @item --debug-ccid-driver Enable debug output from the included CCID driver for smartcards. Note that this option is only available on some system. @end ifset @item --faked-system-time @var{epoch} @opindex faked-system-time This option is only useful for testing; it sets the system time back or forth to @var{epoch} which is the number of seconds elapsed since the year 1970. Alternatively @var{epoch} may be given as a full ISO time string (e.g. "20070924T154812"). @item --enable-progress-filter Enable certain PROGRESS status outputs. This option allows frontends to display a progress indicator while gpg is processing larger files. There is a slight performance overhead using it. @item --status-fd @code{n} Write special status strings to the file descriptor @code{n}. See the file DETAILS in the documentation for a listing of them. @item --status-file @code{file} Same as @option{--status-fd}, except the status data is written to file @code{file}. @item --logger-fd @code{n} Write log output to file descriptor @code{n} and not to STDERR. @item --log-file @code{file} @itemx --logger-file @code{file} Same as @option{--logger-fd}, except the logger data is written to file @code{file}. Note that @option{--log-file} is only implemented for GnuPG-2. @item --attribute-fd @code{n} Write attribute subpackets to the file descriptor @code{n}. This is most useful for use with @option{--status-fd}, since the status messages are needed to separate out the various subpackets from the stream delivered to the file descriptor. @item --attribute-file @code{file} Same as @option{--attribute-fd}, except the attribute data is written to file @code{file}. @item --comment @code{string} @itemx --no-comments Use @code{string} as a comment string in clear text signatures and ASCII armored messages or keys (see @option{--armor}). The default behavior is not to use a comment string. @option{--comment} may be repeated multiple times to get multiple comment strings. @option{--no-comments} removes all comments. It is a good idea to keep the length of a single comment below 60 characters to avoid problems with mail programs wrapping such lines. Note that comment lines, like all other header lines, are not protected by the signature. @item --emit-version @itemx --no-emit-version Force inclusion of the version string in ASCII armored output. @option{--no-emit-version} disables this option. @item --sig-notation @code{name=value} @itemx --cert-notation @code{name=value} @itemx -N, --set-notation @code{name=value} Put the name value pair into the signature as notation data. @code{name} must consist only of printable characters or spaces, and must contain a '@@' character in the form keyname@@domain.example.com (substituting the appropriate keyname and domain name, of course). This is to help prevent pollution of the IETF reserved notation namespace. The @option{--expert} flag overrides the '@@' check. @code{value} may be any printable string; it will be encoded in UTF8, so you should check that your @option{--display-charset} is set correctly. If you prefix @code{name} with an exclamation mark (!), the notation data will be flagged as critical (rfc2440:5.2.3.15). @option{--sig-notation} sets a notation for data signatures. @option{--cert-notation} sets a notation for key signatures (certifications). @option{--set-notation} sets both. There are special codes that may be used in notation names. "%k" will be expanded into the key ID of the key being signed, "%K" into the long key ID of the key being signed, "%f" into the fingerprint of the key being signed, "%s" into the key ID of the key making the signature, "%S" into the long key ID of the key making the signature, "%g" into the fingerprint of the key making the signature (which might be a subkey), "%p" into the fingerprint of the primary key of the key making the signature, "%c" into the signature count from the OpenPGP smartcard, and "%%" results in a single "%". %k, %K, and %f are only meaningful when making a key signature (certification), and %c is only meaningful when using the OpenPGP smartcard. @item --sig-policy-url @code{string} @itemx --cert-policy-url @code{string} @itemx --set-policy-url @code{string} Use @code{string} as a Policy URL for signatures (rfc2440:5.2.3.19). If you prefix it with an exclamation mark (!), the policy URL packet will be flagged as critical. @option{--sig-policy-url} sets a policy url for data signatures. @option{--cert-policy-url} sets a policy url for key signatures (certifications). @option{--set-policy-url} sets both. The same %-expandos used for notation data are available here as well. @item --sig-keyserver-url @code{string} Use @code{string} as a preferred keyserver URL for data signatures. If you prefix it with an exclamation mark (!), the keyserver URL packet will be flagged as critical. The same %-expandos used for notation data are available here as well. @item --set-filename @code{string} Use @code{string} as the filename which is stored inside messages. This overrides the default, which is to use the actual filename of the file being encrypted. @item --for-your-eyes-only @itemx --no-for-your-eyes-only Set the `for your eyes only' flag in the message. This causes GnuPG to refuse to save the file unless the @option{--output} option is given, and PGP to use a "secure viewer" with a claimed Tempest-resistant font to display the message. This option overrides @option{--set-filename}. @option{--no-for-your-eyes-only} disables this option. @item --use-embedded-filename @itemx --no-use-embedded-filename Try to create a file with a name as embedded in the data. This can be a dangerous option as it allows to overwrite files. Defaults to no. @item --cipher-algo @code{name} Use @code{name} as cipher algorithm. Running the program with the command @option{--version} yields a list of supported algorithms. If this is not used the cipher algorithm is selected from the preferences stored with the key. In general, you do not want to use this option as it allows you to violate the OpenPGP standard. @option{--personal-cipher-preferences} is the safe way to accomplish the same thing. @item --digest-algo @code{name} Use @code{name} as the message digest algorithm. Running the program with the command @option{--version} yields a list of supported algorithms. In general, you do not want to use this option as it allows you to violate the OpenPGP standard. @option{--personal-digest-preferences} is the safe way to accomplish the same thing. @item --compress-algo @code{name} Use compression algorithm @code{name}. "zlib" is RFC-1950 ZLIB compression. "zip" is RFC-1951 ZIP compression which is used by PGP. "bzip2" is a more modern compression scheme that can compress some things better than zip or zlib, but at the cost of more memory used during compression and decompression. "uncompressed" or "none" disables compression. If this option is not used, the default behavior is to examine the recipient key preferences to see which algorithms the recipient supports. If all else fails, ZIP is used for maximum compatibility. ZLIB may give better compression results than ZIP, as the compression window size is not limited to 8k. BZIP2 may give even better compression results than that, but will use a significantly larger amount of memory while compressing and decompressing. This may be significant in low memory situations. Note, however, that PGP (all versions) only supports ZIP compression. Using any algorithm other than ZIP or "none" will make the message unreadable with PGP. In general, you do not want to use this option as it allows you to violate the OpenPGP standard. @option{--personal-compress-preferences} is the safe way to accomplish the same thing. @item --cert-digest-algo @code{name} Use @code{name} as the message digest algorithm used when signing a key. Running the program with the command @option{--version} yields a list of supported algorithms. Be aware that if you choose an algorithm that GnuPG supports but other OpenPGP implementations do not, then some users will not be able to use the key signatures you make, or quite possibly your entire key. @item --disable-cipher-algo @code{name} Never allow the use of @code{name} as cipher algorithm. The given name will not be checked so that a later loaded algorithm will still get disabled. @item --disable-pubkey-algo @code{name} Never allow the use of @code{name} as public key algorithm. The given name will not be checked so that a later loaded algorithm will still get disabled. @item --throw-keyids @itemx --no-throw-keyids Do not put the recipient key IDs into encrypted messages. This helps to hide the receivers of the message and is a limited countermeasure against traffic analysis.@footnote{Using a little social engineering anyone who is able to decrypt the message can check whether one of the other recipients is the one he suspects.} On the receiving side, it may slow down the decryption process because all available secret keys must be tried. @option{--no-throw-keyids} disables this option. This option is essentially the same as using @option{--hidden-recipient} for all recipients. @item --not-dash-escaped This option changes the behavior of cleartext signatures so that they can be used for patch files. You should not send such an armored file via email because all spaces and line endings are hashed too. You can not use this option for data which has 5 dashes at the beginning of a line, patch files don't have this. A special armor header line tells GnuPG about this cleartext signature option. @item --escape-from-lines @itemx --no-escape-from-lines Because some mailers change lines starting with "From " to ">From " it is good to handle such lines in a special way when creating cleartext signatures to prevent the mail system from breaking the signature. Note that all other PGP versions do it this way too. Enabled by default. @option{--no-escape-from-lines} disables this option. @item --passphrase-repeat @code{n} Specify how many times @command{@gpgname} will request a new passphrase be repeated. This is useful for helping memorize a passphrase. Defaults to 1 repetition. @item --passphrase-fd @code{n} Read the passphrase from file descriptor @code{n}. Only the first line will be read from file descriptor @code{n}. If you use 0 for @code{n}, the passphrase will be read from STDIN. This can only be used if only one passphrase is supplied. @ifclear gpgone Note that this passphrase is only used if the option @option{--batch} has also been given. This is different from @command{gpg}. @end ifclear @item --passphrase-file @code{file} Read the passphrase from file @code{file}. Only the first line will be read from file @code{file}. This can only be used if only one passphrase is supplied. Obviously, a passphrase stored in a file is of questionable security if other users can read this file. Don't use this option if you can avoid it. @ifclear gpgone Note that this passphrase is only used if the option @option{--batch} has also been given. This is different from @command{gpg}. @end ifclear @item --passphrase @code{string} Use @code{string} as the passphrase. This can only be used if only one passphrase is supplied. Obviously, this is of very questionable security on a multi-user system. Don't use this option if you can avoid it. @ifclear gpgone Note that this passphrase is only used if the option @option{--batch} has also been given. This is different from @command{gpg}. @end ifclear @item --command-fd @code{n} This is a replacement for the deprecated shared-memory IPC mode. If this option is enabled, user input on questions is not expected from the TTY but from the given file descriptor. It should be used together with @option{--status-fd}. See the file doc/DETAILS in the source distribution for details on how to use it. @item --command-file @code{file} Same as @option{--command-fd}, except the commands are read out of file @code{file} @item --allow-non-selfsigned-uid @itemx --no-allow-non-selfsigned-uid Allow the import and use of keys with user IDs which are not self-signed. This is not recommended, as a non self-signed user ID is trivial to forge. @option{--no-allow-non-selfsigned-uid} disables. @item --allow-freeform-uid Disable all checks on the form of the user ID while generating a new one. This option should only be used in very special environments as it does not ensure the de-facto standard format of user IDs. @item --ignore-time-conflict GnuPG normally checks that the timestamps associated with keys and signatures have plausible values. However, sometimes a signature seems to be older than the key due to clock problems. This option makes these checks just a warning. See also @option{--ignore-valid-from} for timestamp issues on subkeys. @item --ignore-valid-from GnuPG normally does not select and use subkeys created in the future. This option allows the use of such keys and thus exhibits the pre-1.0.7 behaviour. You should not use this option unless you there is some clock problem. See also @option{--ignore-time-conflict} for timestamp issues with signatures. @item --ignore-crc-error The ASCII armor used by OpenPGP is protected by a CRC checksum against transmission errors. Occasionally the CRC gets mangled somewhere on the transmission channel but the actual content (which is protected by the OpenPGP protocol anyway) is still okay. This option allows GnuPG to ignore CRC errors. @item --ignore-mdc-error This option changes a MDC integrity protection failure into a warning. This can be useful if a message is partially corrupt, but it is necessary to get as much data as possible out of the corrupt message. However, be aware that a MDC protection failure may also mean that the message was tampered with intentionally by an attacker. @item --no-default-keyring Do not add the default keyrings to the list of keyrings. Note that GnuPG will not operate without any keyrings, so if you use this option and do not provide alternate keyrings via @option{--keyring} or @option{--secret-keyring}, then GnuPG will still use the default public or secret keyrings. @item --skip-verify Skip the signature verification step. This may be used to make the decryption faster if the signature verification is not needed. @item --with-key-data Print key listings delimited by colons (like @option{--with-colons}) and print the public key data. @item --fast-list-mode Changes the output of the list commands to work faster; this is achieved by leaving some parts empty. Some applications don't need the user ID and the trust information given in the listings. By using this options they can get a faster listing. The exact behaviour of this option may change in future versions. If you are missing some information, don't use this option. @item --no-literal This is not for normal use. Use the source to see for what it might be useful. @item --set-filesize This is not for normal use. Use the source to see for what it might be useful. @item --show-session-key Display the session key used for one message. See @option{--override-session-key} for the counterpart of this option. We think that Key Escrow is a Bad Thing; however the user should have the freedom to decide whether to go to prison or to reveal the content of one specific message without compromising all messages ever encrypted for one secret key. DON'T USE IT UNLESS YOU ARE REALLY FORCED TO DO SO. @item --override-session-key @code{string} Don't use the public key but the session key @code{string}. The format of this string is the same as the one printed by @option{--show-session-key}. This option is normally not used but comes handy in case someone forces you to reveal the content of an encrypted message; using this option you can do this without handing out the secret key. @item --ask-sig-expire @itemx --no-ask-sig-expire When making a data signature, prompt for an expiration time. If this option is not specified, the expiration time set via @option{--default-sig-expire} is used. @option{--no-ask-sig-expire} disables this option. Note that by default, @option{--force-v3-sigs} is set which also disables this option. If you want signature expiration, you must set @option{--no-force-v3-sigs} as well as turning @option{--ask-sig-expire} on. @item --default-sig-expire The default expiration time to use for signature expiration. Valid values are "0" for no expiration, a number followed by the letter d (for days), w (for weeks), m (for months), or y (for years) (for example "2m" for two months, or "5y" for five years), or an absolute date in the form YYYY-MM-DD. Defaults to "0". @item --ask-cert-expire @itemx --no-ask-cert-expire When making a key signature, prompt for an expiration time. If this option is not specified, the expiration time set via @option{--default-cert-expire} is used. @option{--no-ask-cert-expire} disables this option. @item --default-cert-expire The default expiration time to use for key signature expiration. Valid values are "0" for no expiration, a number followed by the letter d (for days), w (for weeks), m (for months), or y (for years) (for example "2m" for two months, or "5y" for five years), or an absolute date in the form YYYY-MM-DD. Defaults to "0". @item --allow-secret-key-import This is an obsolete option and is not used anywhere. @item --allow-multiple-messages @item --no-allow-multiple-messages Allow processing of multiple OpenPGP messages contained in a single file or stream. Some programs that call GPG are not prepared to deal with multiple messages being processed together, so this option defaults to no. Note that versions of GPG prior to 1.4.7 always allowed multiple messages. Warning: Do not use this option unless you need it as a temporary workaround! @item --enable-special-filenames This options enables a mode in which filenames of the form @file{-&n}, where n is a non-negative decimal number, refer to the file descriptor n and not to a file with that name. @item --no-expensive-trust-checks Experimental use only. @item --preserve-permissions Don't change the permissions of a secret keyring back to user read/write only. Use this option only if you really know what you are doing. @item --default-preference-list @code{string} @opindex default-preference-list Set the list of default preferences to @code{string}. This preference list is used for new keys and becomes the default for "setpref" in the edit menu. @item --default-keyserver-url @code{name} @opindex default-keyserver-url Set the default keyserver URL to @code{name}. This keyserver will be used as the keyserver URL when writing a new self-signature on a key, which includes key generation and changing preferences. @item --list-config @opindex list-config Display various internal configuration parameters of GnuPG. This option is intended for external programs that call GnuPG to perform tasks, and is thus not generally useful. See the file @file{doc/DETAILS} in the source distribution for the details of which configuration items may be listed. @option{--list-config} is only usable with @option{--with-colons} set. @item --gpgconf-list @opindex gpgconf-list This command is similar to @option{--list-config} but in general only internally used by the @command{gpgconf} tool. @item --gpgconf-test @opindex gpgconf-test This is more or less dummy action. However it parses the configuration file and returns with failure if the configuration file would prevent @command{gpg} from startup. Thus it may be used to run a syntax check on the configuration file. @end table @c ******************************* @c ******* Deprecated ************ @c ******************************* @subsection Deprecated options @table @gnupgtabopt @ifset gpgone @item --load-extension @code{name} Load an extension module. If @code{name} does not contain a slash it is searched for in the directory configured when GnuPG was built (generally "/usr/local/lib/gnupg"). Extensions are not generally useful anymore, and the use of this option is deprecated. @end ifset @item --show-photos @itemx --no-show-photos Causes @option{--list-keys}, @option{--list-sigs}, @option{--list-public-keys}, @option{--list-secret-keys}, and verifying a signature to also display the photo ID attached to the key, if any. See also @option{--photo-viewer}. These options are deprecated. Use @option{--list-options [no-]show-photos} and/or @option{--verify-options [no-]show-photos} instead. @item --show-keyring Display the keyring name at the head of key listings to show which keyring a given key resides on. This option is deprecated: use @option{--list-options [no-]show-keyring} instead. @ifset gpgone @item --ctapi-driver @code{file} Use @code{file} to access the smartcard reader. The current default is `libtowitoko.so'. Note that the use of this interface is deprecated; it may be removed in future releases. @end ifset @item --always-trust Identical to @option{--trust-model always}. This option is deprecated. @item --show-notation @itemx --no-show-notation Show signature notations in the @option{--list-sigs} or @option{--check-sigs} listings as well as when verifying a signature with a notation in it. These options are deprecated. Use @option{--list-options [no-]show-notation} and/or @option{--verify-options [no-]show-notation} instead. @item --show-policy-url @itemx --no-show-policy-url Show policy URLs in the @option{--list-sigs} or @option{--check-sigs} listings as well as when verifying a signature with a policy URL in it. These options are deprecated. Use @option{--list-options [no-]show-policy-url} and/or @option{--verify-options [no-]show-policy-url} instead. @end table @c ******************************************* @c *************** **************** @c *************** FILES **************** @c *************** **************** @c ******************************************* @mansect files @node GPG Configuration @section Configuration files There are a few configuration files to control certain aspects of @command{@gpgname}'s operation. Unless noted, they are expected in the current home directory (@pxref{option --homedir}). @table @file @item gpg.conf @cindex gpg.conf This is the standard configuration file read by @command{@gpgname} on startup. It may contain any valid long option; the leading two dashes may not be entered and the option may not be abbreviated. This default name may be changed on the command line (@pxref{option --options}). You should backup this file. @end table @c man:.RE Note that on larger installations, it is useful to put predefined files into the directory @file{/etc/skel/.gnupg/} so that newly created users start up with a working configuration. @ifclear gpgone For existing users the a small helper script is provided to create these files (@pxref{addgnupghome}). @end ifclear For internal purposes @command{@gpgname} creates and maintains a few other files; They all live in in the current home directory (@pxref{option --homedir}). Only the @command{@gpgname} may modify these files. @table @file @item ~/.gnupg/secring.gpg The secret keyring. You should backup this file. @item ~/.gnupg/secring.gpg.lock The lock file for the secret keyring. @item ~/.gnupg/pubring.gpg The public keyring. You should backup this file. @item ~/.gnupg/pubring.gpg.lock The lock file for the public keyring. @item ~/.gnupg/trustdb.gpg The trust database. There is no need to backup this file; it is better to backup the ownertrust values (@pxref{option --export-ownertrust}). @item ~/.gnupg/trustdb.gpg.lock The lock file for the trust database. @item ~/.gnupg/random_seed A file used to preserve the state of the internal random pool. @item /usr[/local]/share/gnupg/options.skel The skeleton options file. @item /usr[/local]/lib/gnupg/ Default location for extensions. @end table @c man:.RE Operation is further controlled by a few environment variables: @table @asis @item HOME Used to locate the default home directory. @item GNUPGHOME If set directory used instead of "~/.gnupg". @item GPG_AGENT_INFO Used to locate the gpg-agent. @ifset gpgone This is only honored when @option{--use-agent} is set. @end ifset The value consists of 3 colon delimited fields: The first is the path to the Unix Domain Socket, the second the PID of the gpg-agent and the protocol version which should be set to 1. When starting the gpg-agent as described in its documentation, this variable is set to the correct value. The option @option{--gpg-agent-info} can be used to override it. @item PINENTRY_USER_DATA This value is passed via gpg-agent to pinentry. It is useful to convey extra information to a custom pinentry. @item COLUMNS @itemx LINES Used to size some displays to the full size of the screen. @item LANGUAGE Apart from its use by GNU, it is used in the W32 version to override the language selection done through the Registry. If used and set to a valid and available language name (@var{langid}), the file with the translation is loaded from @code{@var{gpgdir}/gnupg.nls/@var{langid}.mo}. Here @var{gpgdir} is the directory out of which the gpg binary has been loaded. If it can't be loaded the Registry is tried and as last resort the native Windows locale system is used. @end table @c ******************************************* @c *************** **************** @c *************** EXAMPLES **************** @c *************** **************** @c ******************************************* @mansect examples @node GPG Examples @section Examples @table @asis @item gpg -se -r @code{Bob} @code{file} sign and encrypt for user Bob @item gpg --clearsign @code{file} make a clear text signature @item gpg -sb @code{file} make a detached signature @item gpg -u 0x12345678 -sb @code{file} make a detached signature with the key 0x12345678 @item gpg --list-keys @code{user_ID} show keys @item gpg --fingerprint @code{user_ID} show fingerprint @item gpg --verify @code{pgpfile} @itemx gpg --verify @code{sigfile} Verify the signature of the file but do not output the data. The second form is used for detached signatures, where @code{sigfile} is the detached signature (either ASCII armored or binary) and are the signed data; if this is not given, the name of the file holding the signed data is constructed by cutting off the extension (".asc" or ".sig") of @code{sigfile} or by asking the user for the filename. @end table @c ******************************************* @c *************** **************** @c *************** USER ID **************** @c *************** **************** @c ******************************************* @mansect how to specify a user id @ifset isman @include specify-user-id.texi @end ifset @mansect return value @chapheading RETURN VALUE The program returns 0 if everything was fine, 1 if at least a signature was bad, and other error codes for fatal errors. @mansect warnings @chapheading WARNINGS Use a *good* password for your user account and a *good* passphrase to protect your secret key. This passphrase is the weakest part of the whole system. Programs to do dictionary attacks on your secret keyring are very easy to write and so you should protect your "~/.gnupg/" directory very well. Keep in mind that, if this program is used over a network (telnet), it is *very* easy to spy out your passphrase! If you are going to verify detached signatures, make sure that the program knows about it; either give both filenames on the command line or use @samp{-} to specify STDIN. @mansect interoperability @chapheading INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS GnuPG tries to be a very flexible implementation of the OpenPGP standard. In particular, GnuPG implements many of the optional parts of the standard, such as the SHA-512 hash, and the ZLIB and BZIP2 compression algorithms. It is important to be aware that not all OpenPGP programs implement these optional algorithms and that by forcing their use via the @option{--cipher-algo}, @option{--digest-algo}, @option{--cert-digest-algo}, or @option{--compress-algo} options in GnuPG, it is possible to create a perfectly valid OpenPGP message, but one that cannot be read by the intended recipient. There are dozens of variations of OpenPGP programs available, and each supports a slightly different subset of these optional algorithms. For example, until recently, no (unhacked) version of PGP supported the BLOWFISH cipher algorithm. A message using BLOWFISH simply could not be read by a PGP user. By default, GnuPG uses the standard OpenPGP preferences system that will always do the right thing and create messages that are usable by all recipients, regardless of which OpenPGP program they use. Only override this safe default if you really know what you are doing. If you absolutely must override the safe default, or if the preferences on a given key are invalid for some reason, you are far better off using the @option{--pgp6}, @option{--pgp7}, or @option{--pgp8} options. These options are safe as they do not force any particular algorithms in violation of OpenPGP, but rather reduce the available algorithms to a "PGP-safe" list. @mansect bugs @chapheading BUGS On older systems this program should be installed as setuid(root). This is necessary to lock memory pages. Locking memory pages prevents the operating system from writing memory pages (which may contain passphrases or other sensitive material) to disk. If you get no warning message about insecure memory your operating system supports locking without being root. The program drops root privileges as soon as locked memory is allocated. Note also that some systems (especially laptops) have the ability to ``suspend to disk'' (also known as ``safe sleep'' or ``hibernate''). This writes all memory to disk before going into a low power or even powered off mode. Unless measures are taken in the operating system to protect the saved memory, passphrases or other sensitive material may be recoverable from it later. Before you report a bug you should first search the mailing list archives for similar problems and second check whether such a bug has already been reported to our bug tracker at http://bugs.gnupg.org . @mansect see also @ifset isman @command{gpgv}(1), @ifclear gpgone @command{gpgsm}(1), @command{gpg-agent}(1) @end ifclear @end ifset @include see-also-note.texi diff --git a/scripts/gpg-w32-dev/GNUmakefile b/scripts/gpg-w32-dev/GNUmakefile index f6502c279..10ba92f62 100644 --- a/scripts/gpg-w32-dev/GNUmakefile +++ b/scripts/gpg-w32-dev/GNUmakefile @@ -1,142 +1,142 @@ # Makefile - Makefile for speedo # Copyright (C) 2008 g10 Code GmbH # # This file is part of speedo. # # speedo is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # speedo is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # speedo builds gnupg-related packages from SVN and installs them in a # user directory, thereby providing a non-obstrusive test environment. # The build system is similar to that of gpg4win. The following commands # are supported: # # make all or make # # Builds all packages and installs them under playground/install. # At the end, speedo prints commands that can be executed in the local shell # to make use of the installed packages. # # make clean or make clean-PACKAGE # # Removes all packages or the package PACKAGE from the installation # and build tree. A subsequent make will rebuild these (and only # these) packages. # # make report or make report-PACKAGE # # Lists packages and versions. # # make dist # # Create a development ZIP archive. # -------- # The packages that should be built. The order is also the build order. speedo_spkgs = libgpg-error libgcrypt libassuan libksba gpgme # For each package, the following variables can be defined: # # speedo_pkg_PACKAGE_svn: The SVN repository that should be built. # Branches or specific revisions can also be specified. # # speedo_pkg_PACKAGE_tar: URL to the tar file that should be built. # # Exactly one of the above variables is required. Note that this # version of speedo does not cache repositories or tar files, and does # not test the integrity of the downloaded software. If you care # about this, you can also specify filenames to locally verified files. # Filenames are differentiated from URLs by starting with a slash '/'. # # speedo_pkg_PACKAGE_configure: Extra arguments to configure. # # speedo_pkg_PACKAGE_make_args: Extra arguments to make. # # speedo_pkg_PACKAGE_make_args_inst: Extra arguments to make install. # # Note that you can override the defaults in this file in a local file # "config.mk" # Set this to "svn" or "release". WHAT=release ifeq ($(WHAT),svn) svnrep = svn://cvs.gnupg.org/ speedo_pkg_libgpg_error_svn = $(svnrep)/libgpg-error/trunk speedo_pkg_libassuan_svn = $(svnrep)/libassuan/trunk speedo_pkg_libgcrypt_svn = $(svnrep)/libgcrypt/trunk speedo_pkg_libksba_svn = $(svnrep)/libksba/trunk speedo_pkg_gpgme_svn = $(svnrep)/gpgme/trunk else pkgrep = ftp://ftp.gnupg.org/gcrypt speedo_pkg_libgpg_error_tar = $(pkgrep)/libgpg-error/libgpg-error-1.8.tar.bz2 speedo_pkg_libassuan_tar = $(pkgrep)/libassuan/libassuan-2.0.0.tar.bz2 - speedo_pkg_libgcrypt_tar = $(pkgrep)/libgcrypt/libgcrypt-1.4.5.tar.bz2 + speedo_pkg_libgcrypt_tar = $(pkgrep)/libgcrypt/libgcrypt-1.4.6.tar.bz2 speedo_pkg_libksba_tar = $(pkgrep)/libksba/libksba-1.0.7.tar.bz2 speedo_pkg_gpgme_tar = $(pkgrep)/gpgme/gpgme-1.3.0.tar.bz2 endif speedo_pkg_gnupg_configure = --with-pinentry-pgm=$(idir)/bin/pinentry \ --with-dirmngr-pgm=$(idir)/bin/dirmngr speedo_pkg_dirmngr_configure = --localstatedir=/var --sysconfdir=/etc \ --with-libgcrypt-prefix=$(idir) --with-gpg-error-prefix=$(idir) \ --with-libassuan-prefix=$(idir) --with-libksba-prefix=$(idir) speedo_pkg_gpgme_configure = --with-gpg=$(idir)/bin/gpg2 \ --with-gpgsm=$(idir)/bin/gpgsm \ --with-gpgconf=$(idir)/bin/gpgconf speedo_pkg_pinentry_configure = --disable-pinentry-qt4 speedo_pkg_libgcrypt_configure = --disable-static speedo_pkg_libksba_configure = --disable-static # --------- all: all-speedo @echo export PATH=\"$(idir)/bin\":\$$PATH @echo export LD_LIBRARY_PATH=\"$(idir)/lib\":\$$LD_LIBRARY_PATH @echo hash -r report: report-speedo clean: clean-speedo dist: all set -e; date=$$(date -u +%Y%m%d); pkgname=gpg-w32-dev-$$date; \ rm -rf $$pkgname $${pkgname}.zip || true; \ cp -rL playground/install $${pkgname}; \ rm -r $${pkgname}/share/info || true; \ mkdir -p $${pkgname}/share/doc/gpg-w32-dev ;\ echo "Included versions:" > $${pkgname}/README.txt ; \ echo "" >> $${pkgname}/README.txt ; \ $(MAKE) --no-print-directory report \ | awk '{print $$2}' >> $${pkgname}/README.txt ; \ cp GNUmakefile speedo.mk $${pkgname}/README.txt \ $${pkgname}/share/doc/gpg-w32-dev/ ; \ i586-mingw32msvc-strip $${pkgname}/bin/*.dll ; \ zip -r9 $${pkgname}.zip $${pkgname} >/dev/null ; \ rm -rf $$pkgname; \ echo "$$pkgname.zip ready for distribution" >&2 speedo_build_list = $(speedo_spkgs) -include config.mk include speedo.mk diff --git a/tools/ChangeLog b/tools/ChangeLog index d3238b28c..5599ff578 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,1129 +1,1137 @@ +2010-07-16 Werner Koch + + * gpgtar-create.c: Rewrite to better support W32. + +2010-07-01 Werner Koch + + * gpgtar.c: Add option --set-filename. + 2010-06-24 Werner Koch * gpgconf-comp.c (gpg_agent_runtime_change) (scdaemon_runtime_change, retrieve_options_from_program): Use HANG option for gnupg_wait_progress. Fixes regression from 2010-06-09. 2010-06-07 Werner Koch * gpgtar.c, gpgtar.h, gpgtar-list.c, gpgtar-create.c * gpgtar-extract.c: New. * Makefile.am (commonpth_libs): New. (gpgtar_SOURCES, gpgtar_CFLAGS, gpgtar_LDADD): New. (bin_PROGRAMS) [!W32CE]: Add gpgtar. 2010-04-20 Marcus Brinkmann * gpgconf-comp.c (option_check_validity): Use dummy variables to silence gcc warning. 2010-04-14 Werner Koch * Makefile.am (bin_PROGRAMS) [W32CE]: Exclude gpgkey2ssh. (noinst_PROGRAMS) [W32CE]: Don't build them. (pwquery_libs) [W32CE]: Set to empty. 2010-03-25 Werner Koch * Makefile.am (opt_libassuan_libs) [W32CE]: New. (gpgconf_LDADD): Use it. * gpgconf-comp.c: Include signal.h only if available. Use gpg_err_set_errno. (key_matches_user_or_group) [W32CE]: Do not match any user. 2010-03-15 Werner Koch * gpgconf-comp.c (my_dgettext): s/gettext_select_utf8/gettext_use_utf8/. 2010-03-10 Werner Koch * Makefile.am (common_libs): Remove libjnlib.a. 2010-03-08 Werner Koch * no-libgcrypt.c (gcry_create_nonce): New. 2010-02-26 Werner Koch * gpg-connect-agent.c (main): New option --tcp-socket. 2010-01-10 Werner Koch * symcryptrun.c (utmp.h): Remove header; it is not used. 2009-12-18 Werner Koch * applygnupgdefaults (errorfile): Use mktemp. Fixes bug#1146. 2009-12-08 Marcus Brinkmann * gpg-connect-agent.c (main): Convert posix fd to assuan fd. 2009-12-07 Werner Koch * no-libgcrypt.c (gcry_strdup): Actually copy the string. 2009-11-23 Werner Koch * gpgconf-comp.c (gc_options_gpg): Add default_pubkey_algo. 2009-11-05 Marcus Brinkmann * gpg-connect-agent.c (start_agent): Update use of assuan_socket_connect and assuan_pipe_connect. 2009-11-04 Werner Koch * gpg-connect-agent.c (read_and_print_response): Add arg WITHHASH. (main): Pass true for WITHHASH for the HELP command. 2009-09-23 Marcus Brinkmann * gpg-connect-agent.c (getinfo_pid_cb, read_and_print_response) (main): Update to new Assuan API. 2009-07-21 Werner Koch * gpgsplit.c (my_strusage): Remove i18n stuff. 2009-07-07 Werner Koch * gpg-connect-agent.c (start_agent): Adjust for changed args of send_pinentry_environment. 2009-06-30 Werner Koch * ccidmon.c (parse_line_sniffusb): Take also TAB as delimiter. 2009-06-29 Werner Koch * ccidmon.c (parse_line_sniffusb): New. (main): Add option --sniffusb. 2009-06-08 Werner Koch * gpgconf.c (main): Call gnupg_reopen_std. Should fix bug#1072. 2009-05-19 Werner Koch * watchgnupg.c: Include jnlib/mischelp.h if required. (main): Use SUN_LEN. 2009-04-17 Werner Koch * ccidmon.c: New. 2009-03-03 Werner Koch * gpgconf.c: New command --reload. * gpgconf-comp.c (gc_component_reload): New. 2009-03-02 Werner Koch * gpgconf-comp.c (scdaemon_runtime_change): Killsc d only if it is not running. 2009-02-27 Werner Koch * gpgconf-comp.c (gpg_agent_runtime_change): Declare static. (scdaemon_runtime_change): New. (gc_backend_scdaemon): Register new function. (gc_options_scdaemon): Make most options runtime changable. 2009-01-20 Werner Koch * gpgconf.c (main): Print more directories. 2008-12-09 Werner Koch * gpg-check-pattern.c (main): Call i18n_init before init_common_subsystems. * gpg-connect-agent.c (main): Ditto. * gpgconf.c (main): Ditto. * symcryptrun.c (main): Ditto. 2008-12-08 Werner Koch * gpgkey2ssh.c (main): Change order of output for RSA. Change name of DSA identifier. Reported by Daniel Kahn Gillmor. This is bug#901. 2008-12-05 Werner Koch * gpg-connect-agent.c (opts): Use ARGPARSE_ macros. (start_agent) [W32]: Start agent if not running. 2008-12-03 Werner Koch * gpgconf-comp.c : Add option --card-timeout. Remove unused option --disable-opensc. 2008-10-20 Werner Koch * gpgsplit.c (write_part): Remove unused arg FNAME. Change caller. (do_split): Ditto. * no-libgcrypt.c (gcry_control): Mark unused arg. * gpg-connect-agent.c (do_recvfd): Ditto. * gpgparsemail.c (mime_signed_begin, mime_encrypted_begin): Ditto. (pkcs7_begin): Ditto. 2008-10-01 Werner Koch * gpg-connect-agent.c (main): New command datafile. (read_and_print_response): Print to the defined datafile. 2008-09-30 Werner Koch * gpgconf.c (main) : Print the bindir. 2008-08-06 Marcus Brinkmann * gpgconf-comp.c (gc_options_gpgsm): Change type of keyserver option to GC_ARG_TYPE_LDAP_SERVER. * gpgconf-comp.c (retrieve_options_from_file): Transfer the NO_CHANGE flag from the file name option to the list option. 2008-06-19 Werner Koch * gpgconf-comp.c (GC_ARG_TYPE_ALIAS_LIST): New. (gc_arg_type): Add fallback type. (gc_options_gpg): Add option "group". 2008-06-12 Marcus Brinkmann * gpgconf-comp.c (gc_options_gpgsm): Add option keyserver. 2008-05-26 Marcus Brinkmann * gpgconf-comp.c: Replace pathname by filename everywhere. * gpgconf.c (enum cmd_and_opt_values): Add aListDirs. (opts): Add aListDirs option. (main): Handle aListDirs. * gpgconf.h (gc_percent_escape): New declaration. * gpgconf-comp.c (my_percent_escape): Make non-static and rename to ... (gc_percent_escape): ... this. Change all callers. 2008-05-26 Werner Koch * gpgconf-comp.c (gpg_agent_runtime_change) [W32]: Issue "reloadagent" command to gpg-agent. * gpg-connect-agent.c (main): Allow server command on the command line. 2008-05-20 Marcus Brinkmann * gpgconf.h (gc_component_check_programs): Rename to ... (gc_check_programs): ... this. (gc_component_change_options): Add argument OUT. (gc_component_check_options): New function. * gpgconf.c (enum cmd_and_opt_values): New option aCheckOptions. (opts): Add new option aCheckOptions (aka --check-options). (main): Handle new option aCheckOptions. * gpgconf-comp.c (gc_component_check_programs): Rename to ... (gc_check_programs): ... this. Refactor core of it to ... (gc_component_check_options): ... this new function. (gc_component_change_options): Add new argument OUT. Externally verify all changes. Implement option --dry-run. 2008-05-09 Werner Koch * gpgconf-comp.c (my_dgettext) [USE_SIMPLE_GETTEXT]: Hack to parly support translations. 2008-04-08 Werner Koch * gpgconf-comp.c (gc_options_gpg): Add --auto-key-locate. 2008-03-26 Werner Koch * make-dns-cert.c: Include unistd.h. Use config.h if requested. (cert_key): Protect read against EINTR. (main): Print SVN revision for standalone version. 2008-03-05 Werner Koch * gpg-connect-agent.c (arithmetic_op): Add logical not, or and and. (get_var_ext): Add functions errcode, errsource and errstring. (read_and_print_response): Store server reply in $? variable. (main): Implement IF command. 2008-02-27 Marcus Brinkmann * gpgconf-comp.c (option_check_validity): For now, error out on empty strings. (enum): Add GC_ARG_TYPE_PUB_KEY and GC_ARG_TYPE_SEC_KEY. 2008-02-01 Marcus Brinkmann * gpgconf-comp.c (gc_component_list_options): Fix memcpy. Reported by Marc Mutz. 2008-01-22 Werner Koch * gpgconf-comp.c: Use gnupg domain for honor-http-proxy. Make "LDAP server list" group title translatable. 2008-01-17 Marcus Brinkmann * gpgconf-comp.c (change_options_program): Strip duplicated utf8-strings entries for gnupg backend. Don't create them either. 2007-12-10 Marcus Brinkmann * gpgconf-comp.c (gc_component_list_options): Fix up expert level of group. 2007-12-04 Marcus Brinkmann * gpgconf-comp.c (gc_component_list_components): Do not print a trailing semi-colon to ensure forward compatibility, as this would indicate another empty field. (gc_process_gpgconf_conf): Likewise. 2007-11-15 Werner Koch * gpg-connect-agent.c (start_agent): Adjust changed send_pinentry_environment. 2007-10-24 Werner Koch * gpg-connect-agent.c (substitute_line): Restore temporary nul marker. (main): Add /while command. 2007-10-23 Werner Koch * gpgconf-comp.c (gc_process_gpgconf_conf): Add arg LISTFP. Changed all callers. * gpgconf.h: Add gc_error. * gpgconf.c: Add command --list-config. (get_outfp): New. (main): Make --output work. * gpgconf-comp.c (gc_options_gpg_agent): Replace accidently used GC_BACKEND_SCDAEMON. We should consider to create these tables from plain files. 2007-10-22 Werner Koch * gpgconf-comp.c (retrieve_options_from_program): Replace use of popen by our gnupg_spawn_process_fd. This is required because popen under Windows can't handle long filenames. 2007-10-19 Werner Koch * symcryptrun.c (confucius_get_pass): Use utf8 switching functions. * gpg-connect-agent.c (get_var_ext): New. (substitute_line): Use it. (assign_variable): Implement /slet in terms of get_var_ext. (main): New option -s/--subst. (add_definq): Add arg IS_VAR. Change all callers. (main): Add command /definq. (handle_inquire): Implement new command. (substitute_line_copy): New. (unescape_string, unpercent_string): New. * no-libgcrypt.c (gcry_set_outofcore_handler) (gcry_set_fatalerror_handler, gcry_set_log_handler): New. * Makefile.am (gpg_connect_agent_LDADD): Link to libreadline. * gpgconf-comp.c (retrieve_options_from_file): Don't call fclose with NULL. Fixes bug 842. 2007-10-12 Werner Koch * gpg-connect-agent.c (substitute_line): Allow ${foo} syntax. 2007-10-11 Werner Koch * gpg-connect-agent.c (get_var): Expand environment variables. Suggested by Marc Mutz. (set_var): Return the value. (assign_variable): Add arg syslet. (main): New command /slet. (gnu_getcwd): New. (assign_variable): Add tag cwd, and *dir. 2007-10-02 Werner Koch * no-libgcrypt.c (gcry_malloc_secure): New. * gpg-connect-agent.c (set_var, set_int_var, get_var) (substitute_line, show_variables, assign_variable) (do_open, do_close, do_showopen): New. (main): Add new commands /nosubst, /subst, /let, /showvar, /open, /close and /showopen. (main): New commands /run and /bye. 2007-10-01 Werner Koch * gpg-connect-agent.c (do_sendfd): Use INT2FD for assuan_sendfd. 2007-09-26 Werner Koch * gpg-connect-agent.c (main): Print the first response from the server. 2007-09-14 Werner Koch * gpgconf-comp.c: Make a string translatable. 2007-09-04 Moritz Schulte * gpgsm-gencert.sh: Use printf instead of echo. 2007-09-04 Moritz Schulte * gpgkey2ssh.c: Include sysutils.h so that gnupg_tmpfile() is declared. 2007-08-31 Werner Koch * gpgparsemail.c: Support PGP/MIME signed messages. * gpgconf-comp.c (gc_component_list_components): List the programs names. 2007-08-29 Werner Koch * gpgconf.c: New command --check-programs. * gpgconf-comp.c (gc_component_check_programs): New. (gc_backend): Add member MODULE_NAME and add these module names. (retrieve_options_from_program): Use module name so that we use an absolute file name and don't rely on $PATH. (collect_error_output): New. * no-libgcrypt.c (gcry_control): New. 2007-08-28 Werner Koch * gpgconf-comp.c : Add options --max-passphrase-days and --enable-passphrase-history. 2007-08-27 Werner Koch * gpg-check-pattern.c: New * Makefile.am (libexec_PROGRAMS): Add unless DISABLE_REGEX. 2007-08-24 Werner Koch * gpgconf-comp.c : Add options --check-passphrase-pattern, --min-passphrase-nonalpha and --enforce-passphrase-constraints and move them into a new "passphrase policy" group. (gc_component) [W32]: Enable dirmngr. 2007-08-21 Werner Koch * gpgkey2ssh.c (key_to_blob): Use gnupg_tmpfile(). 2007-08-02 Werner Koch * gpgconf-comp.c: Factor the public GC_OPT_FLAG constants out and include gc-opt-flags.h. 2007-07-17 Werner Koch * gpgconf-comp.c: Add --encrypt-to and --default-key to gpg and gpgsm. 2007-07-16 Marcus Brinkmann * gpg-connect-agent.c (main): Bail out if write fails. 2007-07-05 Marcus Brinkmann * symcryptrun.c (confucius_get_pass): Define orig_codeset if [ENABLE_NLS], not [HAVE_LANGINFO_CODESET]. 2007-06-26 Werner Koch * gpgconf-comp.c (key_matches_user_or_group) [W32]: Implement user name matching. (GPGNAME): New. Use it instead of "gpg". (gc_component) [W32]: Disable dirmngr for now. (gc_component_retrieve_options): Ignore components without options. (gc_component_change_options): Ditto. (gc_component_list_options): Ditto. (gc_component_find, gc_component_list_components): Ditto. 2007-06-19 Werner Koch * gpgconf-comp.c (percent_escape): Rename to my_percent_escape. Changed all callers. 2007-06-18 Marcus Brinkmann * gpgconf-comp.c (retrieve_options_from_file): Close LIST_FILE. (copy_file): In error case, save/restore errno. Close SRC and DST. (gc_component_change_options): Catch error from unlink(). Remove target backup file before rename(). 2007-06-15 Marcus Brinkmann * gpgconf-comp.c (copy_file) [HAVE_W32_SYSTEM]: New function. (change_options_file, change_options_program) [HAVE_W32_SYSTEM]: Copy backup file. (gc_component_change_options) [HAVE_W32_SYSTEM]: Non-atomic replace. (gc_process_gpgconf_conf): Rename fname to fname_arg and fname_buffer to fname, initialize fname with fname_arg, discarding const qualifier. 2007-06-15 Werner Koch * Makefile.am (symcryptrun_LDADD): It is LIBICONV and not LIBINCONV. (gpgconf_LDADD, symcryptrun_LDADD): Add W32SOCKLIBS. 2007-06-14 Werner Koch * symcryptrun.c (main): Setup default socket name for simple-pwquery. (MAP_SPWQ_ERROR_IMPL): New. Use it for all spwq error returns. 2007-06-12 Werner Koch * gpgconf-comp.c (gc_process_gpgconf_conf): Replace GNUPG_SYSCONFDIR by a function call. * gpg-connect-agent.c (main): Replace some calls by init_common_subsystems. * gpgconf.c (main): Ditto. * symcryptrun.c (main): Ditto. 2007-06-11 Werner Koch * symcryptrun.c (main) [W32]: Call pth_init. * gpgconf.c (main) [W32]: Call pth_init * gpg-connect-agent.c (main) [W32]: Call pth_init. 2007-06-06 Werner Koch * Makefile.am (bin_PROGRAMS) [W32]: Do not build gpgparsemail. * gpgconf-comp.c [W32]: Do not include pwd.h and grp.h. (key_matches_user_or_group) [W32]: For now always return false. * symcryptrun.c (i18n_init): Remove. * gpgconf.c (i18n_init): Remove. * gpg-connect-agent.c (i18n_init): Remove. 2007-05-19 Marcus Brinkmann * symcryptrun.c (confucius_get_pass): Free ORIG_CODESET on error. 2007-05-08 Werner Koch * sockprox.c: New. It needs to be build manually. By Moritz Schulte. 2007-04-20 Werner Koch * symcryptrun.c (my_gcry_logger): Removed. (main): Call setup_libgcrypt_logging. 2007-04-03 Werner Koch * gpgconf-comp.c: Allow changing of --allow-mark-trusted. * gpg-connect-agent.c (main): New option --decode and commands decode and undecode. (read_and_print_response): Implement option. 2007-03-20 Werner Koch * gpgconf-comp.c (gc_options_gpgsm): Add p12-charset. 2007-03-07 Werner Koch * applygnupgdefaults: New. * Makefile.am (sbin_SCRIPTS): Add it 2007-03-06 Werner Koch * gpgconf-comp.c: Include pwd.h and grp.h. (GC_OPT_FLAG_NO_CHANGE): New. (gc_component_change_options): Implement it. (gc_options_gpg_agent): Add options for all ttl values and min-passphrase-length. Apply new flag to some of them. (gc_process_gpgconf_conf, key_matches_user_or_group): New. (gc_component_change_options): Factor some code out to .. (change_one_value): .. new. (gc_component_retrieve_options): Allow -1 for COMPONENT to iterate over al components. * gpgconf.c (main): New commands --check-config and --apply-defaults. Call gc_process_gpgconf_conf. 2007-01-31 Werner Koch * Makefile.am (symcryptrun_LDADD): Add LIBICONV. (gpgkey2ssh_LDADD): Ditto. 2006-12-13 David Shaw * Makefile.am (gpgsplit_LDADD): Link to LIBINTL if we're using the built-in code. 2006-12-07 David Shaw * Makefile.am: Link to iconv for jnlib dependency. 2006-11-23 Werner Koch * Makefile.am (gpg_connect_agent_LDADD): Add NETLIBS. 2006-11-21 Werner Koch * gpgconf-comp.c (list_one_option): Cast print size_t arg. 2006-11-17 Werner Koch * gpgconf-comp.c: Made disable-keypad a basic option. 2006-11-03 Werner Koch * symcryptrun.c: Include signal.h and include pth.h only if test asserts that it exists. 2006-10-23 Werner Koch * gpgconf-comp.c : Add --cipher-algo. 2006-10-20 Werner Koch * gpgsm-gencert.sh: Enhanced the main menu. 2006-10-12 Werner Koch * Makefile.am (gpg-zip, gpgsplit): Do not install due to a conflict with gpg1. 2006-10-11 Werner Koch * gpgsm-gencert.sh: Allow generation of card keys. 2006-10-08 Werner Koch * Makefile.am (gpgkey2ssh_LDADD): Add LIBINTL. Suggested by Andreas Metzler. 2006-09-22 Werner Koch * no-libgcrypt.c: Changed license to a simple all permissive one. 2006-09-20 Werner Koch * Makefile.am: Changes to allow parallel make runs. 2006-09-12 Werner Koch Replaced all call gpg_error_from_errno(errno) by gpg_error_from_syserror(). * gpg-connect-agent.c (read_and_print_response): With verbosity level 2 also print comment lines. 2006-09-06 Werner Koch * gpg-connect-agent.c: Switch everything to new Assuan error code style. * no-libgcrypt.c (out_of_core): Reanmed to ... (out_of_memory): .. this to avoid name clash with util.h. 2006-08-21 Werner Koch * gpgsplit.c: New. Taken from 1.4. Adjusted to GnuPG2. * Makefile.am (noinst_PROGRAMS): New. 2006-06-09 Marcus Brinkmann * Makefile.am (gpgconf_LDADD): Add $(GPG_ERROR_LIBS). (gpgkey2ssh_LDADD): Add ../jnlib/libjnlib.a. 2006-05-23 Werner Koch * gpgparsemail.c: Include config.h if available (stpcpy): Conditional include it. * gpgconf-comp.c (hextobyte): Removed as it is now availble in jnlib. 2005-12-20 Werner Koch * gpgconf-comp.c (gc_options_gpg): Add allow-pka-lookup. 2005-12-14 Werner Koch * Makefile.am (bin_PROGRAMS): Build gpgparsemail. * gpgparsemail.c (pkcs7_begin): New. (parse_message, message_cb): Add support of direct pkcs signatures. 2005-10-19 Werner Koch * gpgconf-comp.c (gc_options_scdaemon): New option --disable-keypad. 2005-09-22 Werner Koch * rfc822parse.c (parse_field): Tread Content-Disposition special. 2005-10-08 Marcus Brinkmann * Makefile.am (watchgnupg_LDADD): New variable. * Makefile.am (gpgconf_LDADD): Add ../gl/libgnu.a after ../common/libcommon.a. (symcryptrun_LDADD, gpg_connect_agent_LDADD, gpgkey2ssh_LDADD): Likewise. 2005-09-29 Marcus Brinkmann * Makefile.am (AM_CFLAGS): Add $(LIBGCRYPT_CFLAGS). 2005-09-06 Werner Koch * rfc822parse.c, rfc822parse.h: Changed license to LGPL. 2005-08-01 Werner Koch * gpgsm-gencert.sh: Allow entering a keygrip to generate a CSR from an existing key. 2005-07-21 Werner Koch * gpgsm-gencert.sh: Reworked to allow for multiple email addresses as well as DNsanmes and URi. Present the parameter file before creating the certificate. 2005-07-04 Marcus Brinkmann * symcryptrun.c (SYMC_BAD_PASSPHRASE, SYMC_CANCELED): New symbols, use instead constants. (hash_string): New function copied from simple-gettext.c. (confucius_get_pass): Take new argument CACHEID. (confucius_process): Calculate cacheid and pass it to confucius_get_pass. Clear passphrase from cache if necessary. 2005-06-16 Werner Koch * gpg-connect-agent.c (read_and_print_response): Made LINELEN a size_t. 2005-06-04 Marcus Brinkmann * symcryptrun.c (main): Allow any number of arguments, don't use first argument as input file name. Pass extra arguments to confucius_main. (confucius_main): Accept new arguments argc and argv and pass them to confucius_process. (confucius_process): Accept new arguments argc and argv and pass them to the confucius process. 2005-06-01 Werner Koch * symcryptrun.c: Include mkdtemp.h. 2005-05-31 Werner Koch * watchgnupg.c: Make sure that PF_LCOAL and AF_LOCAL are defines. Noted by Ray Link. 2005-05-28 Moritz Schulte * gpgkey2ssh.c: New file. * Makefile.am (bin_PROGRAMS): Added gpgkey2ssh. 2005-05-20 Werner Koch * gpg-connect-agent.c (add_definq, show_definq, clear_definq) (handle_inquire): New. (read_and_print_response): Handle INQUIRE command. (main): Implement control commands. 2005-04-21 Werner Koch * symcryptrun.c (main): Optionally allow the input file as command line argument. * gpgconf-comp.c: Add gpgsm option disable-trusted-cert-crl-check. 2005-04-20 Werner Koch * gpgconf-comp.c: Add gpg-agent:disable-scdaemon. 2005-04-19 Marcus Brinkmann * symcryptrun.c: Add --input option. 2005-04-15 Marcus Brinkmann * symcryptrun.c (TEMP_FAILURE_RETRY): Define if not defined. * symcryptrun.c (remove_file): New function. (confucius_copy_file): Accept new argument PLAIN and shred the file if it is set on error. * Makefile.am: Define symcryptrun make variable depending on BUILD_SYMCRYPTUN. (bin_PROGRAMS): Add ${symcryptrun} instead symcryptrun. (symcryptrun_LDADD): Use $(LIBUTIL_LIBS) instead of -lutil. 2005-04-11 Werner Koch * symcryptrun.c (confucius_mktmpdir): Changed to use mkdtmp(3). 2005-04-11 Marcus Brinkmann * symcryptrun.c: Implement config file parsing. * Makefile.am (bin_PROGRAMS): Add symcryptrun. (symcryptrun_SOURCES, symcryptrun_LDADD): New variables. * symcryptrun.c: New file. 2005-03-31 Werner Koch * gpg-connect-agent.c (start_agent): Use PATHSEP_C instead of ':'. 2005-03-09 Werner Koch * gpgconf-comp.c : Add honor-http-proxy. 2005-02-25 Werner Koch * no-libgcrypt.c (gcry_strdup): New. 2005-02-24 Werner Koch * gpg-connect-agent.c: New. * Makefile.am: Add it. 2004-12-21 Werner Koch * gpgconf-comp.c (get_config_pathname) [DOSISH]: Detect absolute pathnames with a drive letter. 2004-12-15 Werner Koch * Makefile.am (bin_PROGRAMS) [W32]: Do not build watchgnupg. * gpgconf-comp.c (gpg_agent_runtime_change) [W32]: No way yet to send a signal. Disable. (change_options_file, change_options_program) [W32]: No link(2), so we disable it. (gc_component_change_options): Use rename instead of link. 2004-12-13 Werner Koch * gpgconf-comp.c : Fixed typo. 2004-11-24 Werner Koch * gpgconf-comp.c : Add --ignore-http-dp, --ignore-ldap-dp and --ignore-ocsp-service-url. 2004-11-23 Werner Koch * gpgconf-comp.c : Add the proxy options. : Add --prefer-system-daemon. 2004-11-11 Werner Koch * watchgnupg.c (main): Fixed test for read error. 2004-10-22 Werner Koch * Makefile.am (bin_SCRIPTS): Add gpgsm-gencert.sh * gpgsm-gencert.sh: Fixed copyright; its part of GnuPG thus FSF. 2004-10-01 Werner Koch * gpgconf-comp.c: Made all strings for --log-file read the same. 2004-10-01 Werner Koch * gpgconf-comp.c (my_dgettext): Also switch codeset and directory for the other used domains (i.e. dirmngr). * gpgconf.c (main): Fixed translation markers. 2004-09-30 Werner Koch * gpgconf.c (i18n_init): Always use LC_ALL. * Makefile.am: Adjusted for gettext 0.14. 2004-09-29 Werner Koch * gpgconf-comp.c: Made the entries fro GROUPs translatable. Include i18n.h. (my_dgettext): Hack to use the gnupg2 domain. 2004-08-09 Moritz Schulte * gpgsm-gencert.sh: New file. 2004-06-16 Werner Koch * rfc822parse.c (rfc822parse_get_field): Add arg VALUEOFF. 2004-06-14 Werner Koch * no-libgcrypt.c (gcry_realloc, gcry_xmalloc, gcry_xcalloc): New. * gpgconf-comp.c (retrieve_options_from_program) (retrieve_options_from_file, change_options_file) (change_options_program, gc_component_change_options): Replaced getline by read_line and test for allocation failure. 2004-05-21 Marcus Brinkmann * gpgconf-comp.c (gc_options_dirmngr): Remove CRL group, put its only option "max-replies" into LDAP group. (gc_component): Change description of dirmngr to "Directory Manager". * gpgconf-comp.c (gc_component_change_options): Move the per-process backup file into a standard location. 2004-05-03 Werner Koch * gpgconf-comp.c: Add --allow-mark-trusted for the gpg-agent. 2004-04-30 Werner Koch * gpgconf-comp.c: Added more runtime flags for the gpg-agent backend. 2004-04-29 Marcus Brinkmann * gpgconf-comp.c (change_options_program): Turn on utf8-strings in the gpgconf specific part of the config file for the GnuPG backend. 2004-04-28 Werner Koch * gpgconf-comp.c: Add --ocsp-signer for the dirmngr backend. 2004-04-20 Marcus Brinkmann * gpgconf-comp.c (gc_options_gpg_agent): Change type of ignore-cache-for-signing option to GC_ARG_TYPE_NONE. 2004-04-07 Werner Koch * gpgconf-comp.c (my_dgettext): Switch the codeset once to utf-8. Allow building with out NLS. 2004-03-23 Marcus Brinkmann * gpgconf-comp.c (gc_options_dirmngr): Set GC_OPT_FLAG_ARG_OPT for "LDAP Server". (change_options_file): Remove assertion that tests that this flag is not present. Handle an empty string in OPTION->new_value. * gpgconf.c (main): Remove obsolete warning. 2004-03-23 Werner Koch * gpgconf-comp.c (gc_options_gpg): New. (gc_component_t, gc_component): Add GC_BACKEND_GPG. (gc_options_dirmngr): Add allow-ocsp. 2004-03-23 Marcus Brinkmann * gpgconf-comp.c (gc_flag): Add missing flags. * gpgconf-comp.c: Include . (gc_backend): Add new member runtime_change. (gpg_agent_runtime_change): New function. (gc_component_change_options): New variable runtime. Initialize it. If an option is changed that has the GC_OPT_FLAG_RUNTIME bit set, also set the corresponding runtime variable. Finally, call the runtime_change callback of the backend if needed. 2004-03-16 Werner Koch * gpgconf-comp.c (gc_options_gpg_agent): Implemented. (gc_options_gpgsm, gc_options_scdaemon): Implemented. (gc_backend_t): Add GC_BACKEND_SCDAEMON. 2004-03-12 Marcus Brinkmann * gpgconf-comp.c (gc_component_change_options): Set the filenames of the option's backend, not of the component. Also use GC_BACKEND_NR, not GC_COMPONENT_NR. 2004-03-09 Werner Koch * gpgconf-comp.c [_riscos_]: Removed special code for RISC OS; we don't want to clutter our code with system dependent stuff. 2004-03-08 Marcus Brinkmann * gpgconf-comp.c (retrieve_options_from_file): Quote each string in the list, not only the first. 2004-02-26 Marcus Brinkmann * gpgconf-comp.c (gc_component_list_options): Do not print empty groups. * gpgconf-comp.c (option_check_validity): Check if option is active. (change_options_file): Implement. * gpgconf-comp.c (retrieve_options_from_program): Remove broken string handling. * gpgconf-comp.c (change_options_program): Support all types of options, including list types. * README.gpgconf: Fix description of arguments. * gpgconf-comp.c (option_check_validity): Rewritten to properly support optional arguments in lists. * README.gpgconf: Add info about optional arg and arg type 0. * gpgconf-comp.c (gc_component_change_options): Parse list of arg type 0 options. (option_check_validity): Add new argument NEW_VALUE_NR. Perform rigorous validity checks. (change_options_program): Disable an option also if we have a new value for it. 2004-02-25 Marcus Brinkmann * gpgconf-comp.c (gc_component_list_options): Correct output for lists of arg type none. (struct gc_option): Add new member new_flags. (option_check_validity): Check OPTION->new_flags beside OPTION->new_value. Add new argument FLAGS. (gc_component_change_options): Support default flag correctly. (change_options_program): Likewise. 2004-02-24 Marcus Brinkmann * README.gpgconf: Revert last change. Add new flags "default", "default desc" and "no arg desc". Add new field ARGDEF. Add new field FLAG to backend interface. * gpgconf-comp.c (struct gc_option): Make flags of type unsigned long. (gc_component_list_options): Adjust type for flags. Add default argument field. (retrieve_options_from_program): Use "1" as value for non-option arguments, not "Y". (gc_component_change_options): Read in flags from input. 2004-02-23 Marcus Brinkmann * README.gpgconf: Change meaning of type 0 options value if it is the empty string or "0". * gpgconf.h (struct): Add member runtime. * gpgconf.c: Add new option oRuntime. (main): Same here. * gpgconf-comp.c (hextobyte): New function. (percent_deescape): New function. (get_config_pathname): Percent deescape pathname if taken from option (default) value. Use default value only if it exists and is not empty. Use empty string otherwise. Don't include leading quote in pathname. (change_options_program): Percent deescape string before writing it out. * gpgconf-comp.c (gc_component_list_options): Do not skip groups on output. 2004-02-18 Werner Koch * gpgconf-comp.c: Added empty components for gpgsm and scdaemon. 2004-02-12 Werner Koch * watchgnupg.c (main): Implement option "--". (print_version): New. * Makefile.am: Include cmacros.am for common flags. 2004-02-03 Werner Koch * addgnupghome: Try to use getent, so that it also works for NIS setups. 2004-01-31 Marcus Brinkmann * gpgconf-comp.c: Some bug fixes, parse only defaults from the program, and read the current values from the configuration file directly. 2004-01-30 Marcus Brinkmann * gpgconf-comp.c (gc_error): New function, use it instead of error() throughout. * gpgconf-comp.c: Use xmalloc, libcommon's asctimestamp and gnupg_get_time, fix error() invocation and use getline() consistently. 2004-01-30 Werner Koch * addgnupghome: Also set the group of copied files. 2004-01-30 Werner Koch * Makefile.am (sbin_SCRIPTS): New, to install addgnupghome. (EXTRA_DIST): Added rfc822parse.c rfc822parse.h gpgparsemail.c which might be useful for debugging. 2004-01-29 Werner Koch * addgnupghome: New. 2004-01-29 Marcus Brinkmann * gpgconf-list.c: File removed. * README.gpgconf: New file. * gpgconf-comp.c: New file. * Makefile.am (gpgconf_SOURCES): Remove gpgconf-list.c, add gpgconf-comp.c. 2004-01-16 Werner Koch * watchgnupg.c (main): Need to use FD_ISSET for the client descriptors too; aiiih. Set the listening socket to non-blocking. 2004-01-10 Werner Koch * Makefile.am: Use GPG_ERROR_CFLAGS 2004-01-05 Werner Koch * Manifest: New. * gpgconf.c, gpgconf.h, gpgconf-list.c: New. A skeleton for now. * no-libgcrypt.c: New. * Makefile.am: Add above. 2003-12-23 Werner Koch * Makefile.am: New. * watchgnupg.c: New. Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without modifications, as long as this notice is preserved. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, to the extent permitted by law; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/tools/Makefile.am b/tools/Makefile.am index fc9725a72..73913de44 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,137 +1,138 @@ # Makefile.am - Tools directory # Copyright (C) 2003, 2007 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GnuPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . EXTRA_DIST = \ Manifest watchgnupg.c \ addgnupghome applygnupgdefaults gpgsm-gencert.sh \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ ccidmon.c 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 applygnupgdefaults bin_SCRIPTS = gpgsm-gencert.sh if HAVE_USTAR # bin_SCRIPTS += gpg-zip noinst_SCRIPTS = gpg-zip endif if BUILD_SYMCRYPTRUN symcryptrun = symcryptrun else symcryptrun = endif if BUILD_GPGTAR gpgtar = gpgtar else gpgtar = endif # Fixme: We should remove the gpgkey2ssh tool. bin_PROGRAMS = gpgconf gpg-connect-agent ${symcryptrun} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail endif if !HAVE_W32CE_SYSTEM bin_PROGRAMS += gpgkey2ssh ${gpgtar} endif if !DISABLE_REGEX libexec_PROGRAMS = gpg-check-pattern endif if !HAVE_W32CE_SYSTEM noinst_PROGRAMS = clean-sat mk-tdata make-dns-cert gpgsplit endif common_libs = $(libcommon) ../gl/libgnu.a commonpth_libs = $(libcommonpth) ../gl/libgnu.a if HAVE_W32CE_SYSTEM pwquery_libs = else pwquery_libs = ../common/libsimple-pwquery.a endif if HAVE_W32CE_SYSTEM opt_libassuan_libs = $(LIBASSUAN_LIBS) endif gpgsplit_LDADD = $(common_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(ZLIBS) $(LIBINTL) $(LIBICONV) gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c no-libgcrypt.c # common sucks in gpg-error, will they, nil they (some compilers # do not eliminate the supposed-to-be-unused-inline-functions). gpgconf_LDADD = $(common_libs) $(opt_libassuan_libs) \ $(LIBINTL) $(GPG_ERROR_LIBS) $(LIBICONV) $(W32SOCKLIBS) gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h gpgparsemail_LDADD = symcryptrun_SOURCES = symcryptrun.c symcryptrun_LDADD = $(LIBUTIL_LIBS) $(common_libs) $(pwquery_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) \ $(LIBICONV) $(W32SOCKLIBS) watchgnupg_SOURCES = watchgnupg.c watchgnupg_LDADD = $(NETLIBS) gpg_connect_agent_SOURCES = gpg-connect-agent.c no-libgcrypt.c # FIXME: remove PTH_LIBS (why do we need them at all?) gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(PTH_LIBS) $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) if !HAVE_W32CE_SYSTEM gpgkey2ssh_SOURCES = gpgkey2ssh.c gpgkey2ssh_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) # common via use of BUG() in an inline function, which # some compilers do not eliminate. gpgkey2ssh_LDADD = $(common_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) endif if !DISABLE_REGEX gpg_check_pattern_SOURCES = gpg-check-pattern.c gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) gpg_check_pattern_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) endif gpgtar_SOURCES = \ gpgtar.c gpgtar.h \ gpgtar-create.c \ gpgtar-extract.c \ gpgtar-list.c \ no-libgcrypt.c gpgtar_CFLAGS = $(GPG_ERROR_CFLAGS) $(PTH_CFLAGS) -gpgtar_LDADD = $(commonpth_libs) $(PTH_LIBS) $(GPG_ERROR_LIBS) +#gpgtar_LDADD = $(commonpth_libs) $(PTH_LIBS) $(GPG_ERROR_LIBS) +gpgtar_LDADD = $(common_libs) $(GPG_ERROR_LIBS) $(W32SOCKLIBS) # Make sure that all libs are build before we use them. This is # important for things like make -j2. $(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index c7a75f90f..2b2c441fe 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,643 +1,831 @@ /* gpgtar-create.c - Create a TAR archive * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include -/* #ifdef HAVE_W32_SYSTEM */ -/* # define WIN32_LEAN_AND_MEAN */ -/* # include */ -/* #else /\*!HAVE_W32_SYSTEM*\/ */ +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#else /*!HAVE_W32_SYSTEM*/ # include # include # include -/* #endif /\*!HAVE_W32_SYSTEM*\/ */ +#endif /*!HAVE_W32_SYSTEM*/ #include #include "i18n.h" #include "../common/sysutils.h" #include "gpgtar.h" #ifndef HAVE_LSTAT #define lstat(a,b) stat ((a), (b)) #endif /* Object to control the file scanning. */ struct scanctrl_s; typedef struct scanctrl_s *scanctrl_t; struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; int nestlevel; }; /* Given a fresh header object HDR with only the name field set, try - to gather all available info. */ + to gather all available info. This is the W32 version. */ +#ifdef HAVE_W32_SYSTEM static gpg_error_t -fillup_entry (tar_header_t hdr) +fillup_entry_w32 (tar_header_t hdr) +{ + char *p; + wchar_t *wfname; + WIN32_FILE_ATTRIBUTE_DATA fad; + DWORD attr; + + for (p=hdr->name; *p; p++) + if (*p == '/') + *p = '\\'; + wfname = utf8_to_wchar (hdr->name); + for (p=hdr->name; *p; p++) + if (*p == '\\') + *p = '/'; + if (!wfname) + { + log_error ("error utf8-ing `%s': %s\n", hdr->name, w32_strerror (-1)); + return gpg_error_from_syserror (); + } + if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) + { + log_error ("error stat-ing `%s': %s\n", hdr->name, w32_strerror (-1)); + xfree (wfname); + return gpg_error_from_syserror (); + } + xfree (wfname); + + attr = fad.dwFileAttributes; + + if ((attr & FILE_ATTRIBUTE_NORMAL)) + hdr->typeflag = TF_REGULAR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) + hdr->typeflag = TF_DIRECTORY; + else if ((attr & FILE_ATTRIBUTE_DEVICE)) + hdr->typeflag = TF_NOTSUP; + else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) + hdr->typeflag = TF_NOTSUP; + else + hdr->typeflag = TF_REGULAR; + + /* Map some attributes to USTAR defined mode bits. */ + hdr->mode = 0640; /* User may read and write, group only read. */ + if ((attr & FILE_ATTRIBUTE_DIRECTORY)) + hdr->mode |= 0110; /* Dirs are user and group executable. */ + if ((attr & FILE_ATTRIBUTE_READONLY)) + hdr->mode &= ~0200; /* Clear the user write bit. */ + if ((attr & FILE_ATTRIBUTE_HIDDEN)) + hdr->mode &= ~0707; /* Clear all user and other bits. */ + if ((attr & FILE_ATTRIBUTE_SYSTEM)) + hdr->mode |= 0004; /* Make it readable by other. */ + + /* Only set the size for a regular file. */ + if (hdr->typeflag == TF_REGULAR) + hdr->size = (fad.nFileSizeHigh * (unsigned long long)(MAXDWORD+1) + + fad.nFileSizeLow); + + hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) + | fad.ftLastWriteTime.dwLowDateTime); + if (!hdr->mtime) + hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) + | fad.ftCreationTime.dwLowDateTime); + hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ + hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ + + return 0; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Given a fresh header obje`name, &sbuf)) { err = gpg_error_from_syserror (); log_error ("error stat-ing `%s': %s\n", hdr->name, gpg_strerror (err)); return err; } if (S_ISREG (sbuf.st_mode)) hdr->typeflag = TF_REGULAR; else if (S_ISDIR (sbuf.st_mode)) hdr->typeflag = TF_DIRECTORY; else if (S_ISCHR (sbuf.st_mode)) hdr->typeflag = TF_CHARDEV; else if (S_ISBLK (sbuf.st_mode)) hdr->typeflag = TF_BLOCKDEV; else if (S_ISFIFO (sbuf.st_mode)) hdr->typeflag = TF_FIFO; else if (S_ISLNK (sbuf.st_mode)) hdr->typeflag = TF_SYMLINK; else hdr->typeflag = TF_NOTSUP; /* FIXME: Save DEV and INO? */ /* Set the USTAR defined mode bits using the system macros. */ if (sbuf.st_mode & S_IRUSR) hdr->mode |= 0400; if (sbuf.st_mode & S_IWUSR) hdr->mode |= 0200; if (sbuf.st_mode & S_IXUSR) hdr->mode |= 0100; if (sbuf.st_mode & S_IRGRP) hdr->mode |= 0040; if (sbuf.st_mode & S_IWGRP) hdr->mode |= 0020; if (sbuf.st_mode & S_IXGRP) hdr->mode |= 0010; if (sbuf.st_mode & S_IROTH) hdr->mode |= 0004; if (sbuf.st_mode & S_IWOTH) hdr->mode |= 0002; if (sbuf.st_mode & S_IXOTH) hdr->mode |= 0001; #ifdef S_IXUID if (sbuf.st_mode & S_IXUID) hdr->mode |= 04000; #endif #ifdef S_IXGID if (sbuf.st_mode & S_IXGID) hdr->mode |= 02000; #endif #ifdef S_ISVTX if (sbuf.st_mode & S_ISVTX) hdr->mode |= 01000; #endif hdr->nlink = sbuf.st_nlink; hdr->uid = sbuf.st_uid; hdr->gid = sbuf.st_gid; /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = sbuf.st_size; hdr->mtime = sbuf.st_mtime; - return 0; } +#endif /*!HAVE_W32_SYSTEM*/ - +/* Add a new entry. The name of a director entry is ENTRYNAME; if + that is NULL, DNAME is the name of the directory itself. Under + Windows ENTRYNAME shall have backslashes replaced by standard + slashes. */ static gpg_error_t -add_entry (const char *dname, size_t dnamelen, struct dirent *de, - scanctrl_t scanctrl) +add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) { gpg_error_t err; tar_header_t hdr; char *p; + size_t dnamelen = strlen (dname); assert (dnamelen); hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 - + (de? strlen (de->d_name) : 0)); + + (entryname? strlen (entryname) : 0) + 1); if (!hdr) - { - err = gpg_error_from_syserror (); - log_error (_("error reading directory `%s': %s\n"), - dname, gpg_strerror (err)); - return err; - } + return gpg_error_from_syserror (); p = stpcpy (hdr->name, dname); - if (de) + if (entryname) { if (dname[dnamelen-1] != '/') *p++ = '/'; - strcpy (p, de->d_name); + strcpy (p, entryname); } else { if (hdr->name[dnamelen-1] == '/') hdr->name[dnamelen-1] = 0; } #ifdef HAVE_DOSISH_SYSTEM - for (p=hdr->name; *p; p++) - if (*p == '\\') - *p = '/'; + err = fillup_entry_w32 (hdr); +#else + err = fillup_entry_posix (hdr); #endif - err = fillup_entry (hdr); if (err) xfree (hdr); else { if (opt.verbose) gpgtar_print_header (hdr, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; } return 0; } static gpg_error_t scan_directory (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; - size_t dnamelen; + +#ifdef HAVE_W32_SYSTEM + WIN32_FIND_DATAW fi; + HANDLE hd = INVALID_HANDLE_VALUE; + char *p; + + if (!*dname) + return 0; /* An empty directory name has no entries. */ + + { + char *fname; + wchar_t *wfname; + + fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (!strcmp (dname, "/")) + strcpy (fname, "/*"); /* Trailing slash is not allowed. */ + else if (!strcmp (dname, ".")) + strcpy (fname, "*"); + else if (*dname && dname[strlen (dname)-1] == '/') + strcpy (stpcpy (fname, dname), "*"); + else if (*dname && dname[strlen (dname)-1] != '*') + strcpy (stpcpy (fname, dname), "/*"); + else + strcpy (fname, dname); + + for (p=fname; *p; p++) + if (*p == '/') + *p = '\\'; + wfname = utf8_to_wchar (fname); + xfree (fname); + if (!wfname) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory `%s': %s\n"), + dname, gpg_strerror (err)); + goto leave; + } + hd = FindFirstFileW (wfname, &fi); + if (hd == INVALID_HANDLE_VALUE) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory `%s': %s\n"), + dname, w32_strerror (-1)); + xfree (wfname); + goto leave; + } + xfree (wfname); + } + + do + { + char *fname = wchar_to_utf8 (fi.cFileName); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("error utf8-ing filename: %s\n", w32_strerror (-1)); + break; + } + for (p=fname; *p; p++) + if (*p == '\\') + *p = '/'; + if (!strcmp (fname, "." ) || !strcmp (fname, "..")) + err = 0; /* Skip self and parent dir entry. */ + else if (!strncmp (dname, "./", 2) && dname[2]) + err = add_entry (dname+2, fname, scanctrl); + else + err = add_entry (dname, fname, scanctrl); + xfree (fname); + } + while (!err && FindNextFileW (hd, &fi)); + if (err) + ; + else if (GetLastError () == ERROR_NO_MORE_FILES) + err = 0; + else + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory `%s': %s\n"), + dname, w32_strerror (-1)); + } + + leave: + if (hd != INVALID_HANDLE_VALUE) + FindClose (hd); + +#else /*!HAVE_W32_SYSTEM*/ DIR *dir; struct dirent *de; - dnamelen = strlen (dname); - if (!dnamelen) + if (!*dname) return 0; /* An empty directory name has no entries. */ dir = opendir (dname); if (!dir) { err = gpg_error_from_syserror (); log_error (_("error reading directory `%s': %s\n"), dname, gpg_strerror (err)); return err; } while ((de = readdir (dir))) { if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) continue; /* Skip self and parent dir entry. */ - err = add_entry (dname, dnamelen, de, scanctrl); + err = add_entry (dname, de->d_name, scanctrl); if (err) goto leave; } leave: closedir (dir); +#endif /*!HAVE_W32_SYSTEM*/ return err; } static gpg_error_t scan_recursive (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; tar_header_t hdr, *start_tail, *stop_tail; if (scanctrl->nestlevel > 200) { log_error ("directories too deeply nested\n"); return gpg_error (GPG_ERR_RESOURCE_LIMIT); } scanctrl->nestlevel++; assert (scanctrl->flist_tail); start_tail = scanctrl->flist_tail; scan_directory (dname, scanctrl); stop_tail = scanctrl->flist_tail; hdr = *start_tail; for (; hdr && hdr != *stop_tail; hdr = hdr->next) if (hdr->typeflag == TF_DIRECTORY) { if (opt.verbose > 1) log_info ("scanning directory `%s'\n", hdr->name); scan_recursive (hdr->name, scanctrl); } scanctrl->nestlevel--; return err; } /* Returns true if PATTERN is acceptable. */ static int pattern_valid_p (const char *pattern) { if (!*pattern) return 0; if (*pattern == '.' && pattern[1] == '.') return 0; if (*pattern == '/' || *pattern == DIRSEP_C) return 0; /* Absolute filenames are not supported. */ #ifdef HAVE_DRIVE_LETTERS if (((*pattern >= 'a' && *pattern <= 'z') || (*pattern >= 'A' && *pattern <= 'Z')) && pattern[1] == ':') return 0; /* Drive letter are not allowed either. */ #endif /*HAVE_DRIVE_LETTERS*/ return 1; /* Okay. */ } static void store_xoctal (char *buffer, size_t length, unsigned long long value) { char *p, *pend; size_t n; unsigned long long v; assert (length > 1); v = value; n = length; p = pend = buffer + length; *--p = 0; /* Nul byte. */ n--; do { *--p = '0' + (v % 8); v /= 8; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = '0'; } else /* Does not fit into the field. Store as binary number. */ { v = value; n = length; p = pend = buffer + length; do { *--p = v; v /= 256; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = 0; if (*p & 0x80) BUG (); *p |= 0x80; /* Set binary flag. */ } else BUG (); } } static void store_uname (char *buffer, size_t length, unsigned long uid) { static int initialized; static unsigned long lastuid; static char lastuname[32]; if (!initialized || uid != lastuid) { +#ifdef HAVE_W32_SYSTEM + mem2str (lastuname, uid? "user":"root", sizeof lastuname); +#else struct passwd *pw = getpwuid (uid); lastuid = uid; initialized = 1; if (pw) mem2str (lastuname, pw->pw_name, sizeof lastuname); else { log_info ("failed to get name for uid %lu\n", uid); *lastuname = 0; } +#endif } mem2str (buffer, lastuname, length); } static void store_gname (char *buffer, size_t length, unsigned long gid) { static int initialized; static unsigned long lastgid; static char lastgname[32]; if (!initialized || gid != lastgid) { +#ifdef HAVE_W32_SYSTEM + mem2str (lastgname, gid? "users":"root", sizeof lastgname); +#else struct group *gr = getgrgid (gid); lastgid = gid; initialized = 1; if (gr) mem2str (lastgname, gr->gr_name, sizeof lastgname); else { log_info ("failed to get name for gid %lu\n", gid); *lastgname = 0; } +#endif } mem2str (buffer, lastgname, length); } static gpg_error_t build_header (void *record, tar_header_t hdr) { gpg_error_t err; struct ustar_raw_header *raw = record; size_t namelen, n; unsigned long chksum; unsigned char *p; memset (record, 0, RECORDSIZE); /* Store name and prefix. */ namelen = strlen (hdr->name); if (namelen < sizeof raw->name) memcpy (raw->name, hdr->name, namelen); else { n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; for (n--; n ; n--) if (hdr->name[n] == '/') break; if (namelen - n < sizeof raw->name) { /* Note that the N is < sizeof prefix and that the delimiting slash is not stored. */ memcpy (raw->prefix, hdr->name, n); memcpy (raw->name, hdr->name+n+1, namelen - n); } else { err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("error storing file `%s': %s\n", hdr->name, gpg_strerror (err)); return err; } } store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); store_xoctal (raw->size, sizeof raw->size, hdr->size); store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); switch (hdr->typeflag) { case TF_REGULAR: raw->typeflag[0] = '0'; break; case TF_HARDLINK: raw->typeflag[0] = '1'; break; case TF_SYMLINK: raw->typeflag[0] = '2'; break; case TF_CHARDEV: raw->typeflag[0] = '3'; break; case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; case TF_DIRECTORY: raw->typeflag[0] = '5'; break; case TF_FIFO: raw->typeflag[0] = '6'; break; default: return gpg_error (GPG_ERR_NOT_SUPPORTED); } memcpy (raw->magic, "ustar", 6); raw->version[0] = '0'; raw->version[1] = '0'; store_uname (raw->uname, sizeof raw->uname, hdr->uid); store_gname (raw->gname, sizeof raw->gname, hdr->gid); +#ifndef HAVE_W32_SYSTEM if (hdr->typeflag == TF_SYMLINK) { int nread; nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); if (nread < 0) { err = gpg_error_from_syserror (); log_error ("error reading symlink `%s': %s\n", hdr->name, gpg_strerror (err)); return err; } raw->linkname[nread] = 0; } - +#endif /*HAVE_W32_SYSTEM*/ /* Compute the checksum. */ memset (raw->checksum, ' ', sizeof raw->checksum); chksum = 0; p = record; for (n=0; n < RECORDSIZE; n++) chksum += *p++; store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); raw->checksum[7] = ' '; return 0; } static gpg_error_t write_file (estream_t stream, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; estream_t infp; size_t nread, nbytes; int any; err = build_header (record, hdr); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { log_info ("skipping unsupported file `%s'\n", hdr->name); err = 0; } return err; } if (hdr->typeflag == TF_REGULAR) { infp = es_fopen (hdr->name, "rb"); if (!infp) { err = gpg_error_from_syserror (); log_error ("can't open `%s': %s - skipped\n", hdr->name, gpg_strerror (err)); return err; } } else infp = NULL; err = write_record (stream, record); if (err) goto leave; if (hdr->typeflag == TF_REGULAR) { hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; any = 0; while (hdr->nrecords--) { nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); + if (!nbytes) + nbytes = RECORDSIZE; nread = es_fread (record, 1, nbytes, infp); if (nread != nbytes) { err = gpg_error_from_syserror (); log_error ("error reading file `%s': %s%s\n", hdr->name, gpg_strerror (err), any? " (file shrunk?)":""); goto leave; } any = 1; err = write_record (stream, record); if (err) goto leave; } nread = es_fread (record, 1, 1, infp); if (nread) log_info ("note: file `%s' has grown\n", hdr->name); } leave: if (err) es_fclose (infp); else if ((err = es_fclose (infp))) log_error ("error closing file `%s': %s\n", hdr->name, gpg_strerror (err)); return err; } static gpg_error_t write_eof_mark (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; memset (record, 0, sizeof record); err = write_record (stream, record); if (!err) err = write_record (stream, record); return err; } void gpgtar_create (char **inpattern) { gpg_error_t err = 0; const char *pattern; struct scanctrl_s scanctrl_buffer; scanctrl_t scanctrl = &scanctrl_buffer; tar_header_t hdr, *start_tail; - estream_t outstream; + estream_t outstream = NULL; memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; for (; (pattern = *inpattern); inpattern++) { + char *pat, *p; + if (!*pattern) continue; + + pat = xtrystrdup (pattern); + if (!pat) + { + err = gpg_error_from_syserror (); + log_error ("memory allocation problem: %s\n", gpg_strerror (err)); + goto leave; + } + for (p=pat; *p; p++) + if (*p == '\\') + *p = '/'; + if (opt.verbose > 1) - log_info ("scanning `%s'\n", pattern); + log_info ("scanning `%s'\n", pat); start_tail = scanctrl->flist_tail; - if (!pattern_valid_p (pattern)) - log_error ("skipping invalid name `%s'\n", pattern); - else if (!add_entry (pattern, strlen (pattern), NULL, scanctrl) + if (!pattern_valid_p (pat)) + log_error ("skipping invalid name `%s'\n", pat); + else if (!add_entry (pat, NULL, scanctrl) && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) - scan_recursive (pattern, scanctrl); + scan_recursive (pat, scanctrl); + + xfree (pat); } if (opt.outfile) { outstream = es_fopen (opt.outfile, "wb"); if (!outstream) { err = gpg_error_from_syserror (); log_error (_("can't create `%s': %s\n"), opt.outfile, gpg_strerror (err)); goto leave; } } else { outstream = es_stdout; } for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { err = write_file (outstream, hdr); if (err) goto leave; } err = write_eof_mark (outstream); leave: if (!err) { if (outstream != es_stdout) err = es_fclose (outstream); else err = es_fflush (outstream); outstream = NULL; } if (err) { log_error ("creating tarball `%s' failed: %s\n", es_fname_get (outstream), gpg_strerror (err)); if (outstream && outstream != es_stdout) es_fclose (outstream); if (opt.outfile) gnupg_remove (opt.outfile); } scanctrl->flist_tail = NULL; while ( (hdr = scanctrl->flist) ) { scanctrl->flist = hdr->next; xfree (hdr); } } diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index 002215c5d..028ac0df4 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -1,266 +1,330 @@ /* gpgtar-extract.c - Extract from a TAR archive * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "i18n.h" #include "../common/sysutils.h" #include "gpgtar.h" - static gpg_error_t extract_regular (estream_t stream, const char *dirname, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; size_t n, nbytes, nwritten; char *fname; estream_t outfp = NULL; fname = strconcat (dirname, "/", hdr->name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } else err = 0; outfp = es_fopen (fname, "wb"); if (!outfp) { err = gpg_error_from_syserror (); log_error ("error creating `%s': %s\n", fname, gpg_strerror (err)); goto leave; } for (n=0; n < hdr->nrecords;) { err = read_record (stream, record); if (err) goto leave; n++; nbytes = (n < hdr->nrecords)? RECORDSIZE : (hdr->size % RECORDSIZE); nwritten = es_fwrite (record, 1, nbytes, outfp); if (nwritten != nbytes) { err = gpg_error_from_syserror (); log_error ("error writing `%s': %s\n", fname, gpg_strerror (err)); goto leave; } } /* Fixme: Set permissions etc. */ leave: + if (!err && opt.verbose) + log_info ("extracted `%s/'\n", fname); es_fclose (outfp); if (err && fname && outfp) { if (gnupg_remove (fname)) log_error ("error removing incomplete file `%s': %s\n", fname, gpg_strerror (gpg_error_from_syserror ())); } xfree (fname); return err; } static gpg_error_t extract_directory (const char *dirname, tar_header_t hdr) { gpg_error_t err; char *fname; - + size_t prefixlen; + + prefixlen = strlen (dirname) + 1; fname = strconcat (dirname, "/", hdr->name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } else err = 0; + if (fname[strlen (fname)-1] == '/') + fname[strlen (fname)-1] = 0; + + /* Note that we don't need to care about EEXIST because we always + extract into a new hierarchy. */ if (gnupg_mkdir (fname, "-rwx------")) { err = gpg_error_from_syserror (); - log_error ("error creating directory `%s': %s\n", - fname, gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + /* Try to create the directory with parents but keep the + original error code in case of a failure. */ + char *p; + int rc = 0; + + for (p = fname+prefixlen; (p = strchr (p, '/')); p++) + { + *p = 0; + rc = gnupg_mkdir (fname, "-rwx------"); + *p = '/'; + if (rc) + break; + } + if (!rc && !gnupg_mkdir (fname, "-rwx------")) + err = 0; + } + if (err) + log_error ("error creating directory `%s': %s\n", + fname, gpg_strerror (err)); } leave: + if (!err && opt.verbose) + log_info ("created `%s/'\n", fname); xfree (fname); return err; } static gpg_error_t extract (estream_t stream, const char *dirname, tar_header_t hdr) { gpg_error_t err; size_t n; n = strlen (hdr->name); #ifdef HAVE_DOSISH_SYSTEM if (strchr (hdr->name, '\\')) { log_error ("filename `%s' contains a backslash - " "can't extract on this system\n", hdr->name); return gpg_error (GPG_ERR_INV_NAME); } #endif /*HAVE_DOSISH_SYSTEM*/ if (!n || strstr (hdr->name, "//") || strstr (hdr->name, "/../") || !strncmp (hdr->name, "../", 3) || (n >= 3 && !strcmp (hdr->name+n-3, "/.." ))) { log_error ("filename `%s' as suspicious parts - not extracting\n", hdr->name); return gpg_error (GPG_ERR_INV_NAME); } if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) err = extract_regular (stream, dirname, hdr); else if (hdr->typeflag == TF_DIRECTORY) err = extract_directory (dirname, hdr); else { char record[RECORDSIZE]; - log_info ("unsupported file type for `%s' - skipped\n", hdr->name); + log_info ("unsupported file type %d for `%s' - skipped\n", + (int)hdr->typeflag, hdr->name); for (err = 0, n=0; !err && n < hdr->nrecords; n++) err = read_record (stream, record); } return err; } /* Create a new directory to be used for extracting the tarball. Returns the name of the directory which must be freed by the caller. In case of an error a diagnostic is printed and NULL returned. */ static char * create_directory (const char *dirprefix) { gpg_error_t err = 0; + char *prefix_buffer = NULL; char *dirname = NULL; + size_t n; int idx; + /* Remove common suffixes. */ + n = strlen (dirprefix); + if (n > 4 && (!compare_filenames (dirprefix + n - 4, EXTSEP_S "gpg") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e"))) + { + prefix_buffer = xtrystrdup (dirprefix); + if (!prefix_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + prefix_buffer[n-4] = 0; + dirprefix = prefix_buffer; + } + + + for (idx=1; idx < 5000; idx++) { xfree (dirname); dirname = xtryasprintf ("%s_%d_", dirprefix, idx); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } if (!gnupg_mkdir (dirname, "-rwx------")) - goto leave; + goto leave; /* Ready. */ if (errno != EEXIST && errno != ENOTDIR) { err = gpg_error_from_syserror (); goto leave; } } - err = gpg_error_from_syserror (); + err = gpg_error (GPG_ERR_LIMIT_REACHED); leave: if (err) { log_error ("error creating an extract directory: %s\n", gpg_strerror (err)); xfree (dirname); dirname = NULL; } + xfree (prefix_buffer); return dirname; } void gpgtar_extract (const char *filename) { gpg_error_t err; estream_t stream; tar_header_t header = NULL; const char *dirprefix = NULL; char *dirname = NULL; if (filename) { - dirprefix = strrchr (filename, '/'); - if (dirprefix) - dirprefix++; stream = es_fopen (filename, "rb"); if (!stream) { err = gpg_error_from_syserror (); log_error ("error opening `%s': %s\n", filename, gpg_strerror (err)); return; } } else stream = es_stdin; /* FIXME: How can we enforce binary mode? */ + + if (filename) + { + dirprefix = strrchr (filename, '/'); + if (dirprefix) + dirprefix++; + } + else if (opt.filename) + { + dirprefix = strrchr (opt.filename, '/'); + if (dirprefix) + dirprefix++; + } + if (!dirprefix || !*dirprefix) dirprefix = "GPGARCH"; dirname = create_directory (dirprefix); if (!dirname) { err = gpg_error (GPG_ERR_GENERAL); goto leave; } if (opt.verbose) log_info ("extracting to `%s/'\n", dirname); for (;;) { header = gpgtar_read_header (stream); if (!header) goto leave; if (extract (stream, dirname, header)) goto leave; xfree (header); header = NULL; } leave: xfree (header); xfree (dirname); if (stream != es_stdin) es_fclose (stream); return; } diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c index 82711c19e..1de15b58c 100644 --- a/tools/gpgtar-list.c +++ b/tools/gpgtar-list.c @@ -1,324 +1,323 @@ /* gpgtar-list.c - List a TAR archive * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include "i18n.h" #include "gpgtar.h" static unsigned long long parse_xoctal (const void *data, size_t length, const char *filename) { const unsigned char *p = data; unsigned long long value; if (!length) value = 0; else if ( (*p & 0x80)) { /* Binary format. */ value = (*p++ & 0x7f); while (--length) { value <<= 8; value |= *p++; } } else { /* Octal format */ value = 0; /* Skip leading spaces and zeroes. */ for (; length && (*p == ' ' || *p == '0'); length--, p++) ; for (; length && *p; length--, p++) { if (*p >= '0' && *p <= '7') { value <<= 3; value += (*p - '0'); } else { log_error ("%s: invalid octal number encountered - assuming 0\n", filename); value = 0; break; } } } return value; } static tar_header_t parse_header (const void *record, const char *filename) { const struct ustar_raw_header *raw = record; size_t n, namelen, prefixlen; tar_header_t header; int use_prefix; use_prefix = (!memcmp (raw->magic, "ustar", 5) && (raw->magic[5] == ' ' || !raw->magic[5])); for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++) ; if (namelen == sizeof raw->name) - log_info ("%s: warning: name not terminated by a nul byte\n", - filename); + log_info ("%s: warning: name not terminated by a nul byte\n", filename); for (n=namelen+1; n < sizeof raw->name; n++) if (raw->name[n]) { log_info ("%s: warning: garbage after name\n", filename); break; } if (use_prefix && raw->prefix[0]) { for (prefixlen=0; (prefixlen < sizeof raw->prefix && raw->prefix[prefixlen]); prefixlen++) ; if (prefixlen == sizeof raw->prefix) log_info ("%s: warning: prefix not terminated by a nul byte\n", filename); for (n=prefixlen+1; n < sizeof raw->prefix; n++) if (raw->prefix[n]) { log_info ("%s: warning: garbage after prefix\n", filename); break; } } else prefixlen = 0; header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen); if (!header) { log_error ("%s: error allocating header: %s\n", filename, gpg_strerror (gpg_error_from_syserror ())); return NULL; } if (prefixlen) { n = prefixlen; memcpy (header->name, raw->prefix, n); if (raw->prefix[n-1] != '/') header->name[n++] = '/'; } else n = 0; memcpy (header->name+n, raw->name, namelen); header->name[n+namelen] = 0; header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename); header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename); header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename); header->size = parse_xoctal (raw->size, sizeof raw->size, filename); header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename); /* checksum = */ switch (raw->typeflag[0]) { case '0': header->typeflag = TF_REGULAR; break; case '1': header->typeflag = TF_HARDLINK; break; case '2': header->typeflag = TF_SYMLINK; break; case '3': header->typeflag = TF_CHARDEV; break; case '4': header->typeflag = TF_BLOCKDEV; break; case '5': header->typeflag = TF_DIRECTORY; break; case '6': header->typeflag = TF_FIFO; break; case '7': header->typeflag = TF_RESERVED; break; default: header->typeflag = TF_UNKNOWN; break; } /* Compute the number of data records following this header. */ if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN) header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; else header->nrecords = 0; return header; } /* Read the next block, assming it is a tar header. Returns a header object on success or NULL one error. In case of an error an error message has been printed. */ static tar_header_t read_header (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; int i; err = read_record (stream, record); if (err) return NULL; for (i=0; i < RECORDSIZE && !record[i]; i++) ; if (i == RECORDSIZE) { /* All zero header - check whether it is the first part of an end of archive mark. */ err = read_record (stream, record); if (err) return NULL; for (i=0; i < RECORDSIZE && !record[i]; i++) ; if (i != RECORDSIZE) log_info ("%s: warning: skipping empty header\n", es_fname_get (stream)); else { /* End of archive - FIXME: we might want to check for garbage. */ return NULL; } } return parse_header (record, es_fname_get (stream)); } /* Skip the data records according to HEADER. Prints an error message on error and return -1. */ static int skip_data (estream_t stream, tar_header_t header) { char record[RECORDSIZE]; unsigned long long n; for (n=0; n < header->nrecords; n++) { if (read_record (stream, record)) return -1; } return 0; } static void print_header (tar_header_t header, estream_t out) { unsigned long mask; char modestr[10+1]; int i; *modestr = '?'; switch (header->typeflag) { case TF_REGULAR: *modestr = '-'; break; case TF_HARDLINK: *modestr = 'h'; break; case TF_SYMLINK: *modestr = 'l'; break; case TF_CHARDEV: *modestr = 'c'; break; case TF_BLOCKDEV: *modestr = 'b'; break; case TF_DIRECTORY:*modestr = 'd'; break; case TF_FIFO: *modestr = 'f'; break; case TF_RESERVED: *modestr = '='; break; case TF_UNKNOWN: break; case TF_NOTSUP: break; } for (mask = 0400, i = 0; i < 9; i++, mask >>= 1) modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-'; if ((header->typeflag & 04000)) modestr[3] = modestr[3] == 'x'? 's':'S'; if ((header->typeflag & 02000)) modestr[6] = modestr[6] == 'x'? 's':'S'; if ((header->typeflag & 01000)) modestr[9] = modestr[9] == 'x'? 't':'T'; modestr[10] = 0; es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s\n", modestr, header->nlink, header->uid, header->gid, header->size, isotimestamp (header->mtime), header->name); } /* List the tarball FILENAME or, if FILENAME is NULL, the tarball read from stdin. */ void gpgtar_list (const char *filename) { gpg_error_t err; estream_t stream; tar_header_t header; if (filename) { stream = es_fopen (filename, "rb"); if (!stream) { err = gpg_error_from_syserror (); log_error ("error opening `%s': %s\n", filename, gpg_strerror (err)); return; } } else stream = es_stdin; /* FIXME: How can we enforce binary mode? */ for (;;) { header = read_header (stream); if (!header) goto leave; print_header (header, es_stdout); if (skip_data (stream, header)) goto leave; xfree (header); header = NULL; } leave: xfree (header); if (filename) es_fclose (stream); return; } tar_header_t gpgtar_read_header (estream_t stream) { /*FIXME: Change to return an error code. */ return read_header (stream); } void gpgtar_print_header (tar_header_t header, estream_t out) { if (header && out) print_header (header, out); } diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 555fe39dc..8a9aaaf9b 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -1,361 +1,371 @@ /* gpgtar.c - A simple TAR implementation mainly useful for Windows. * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* GnuPG comes with a shell script gpg-zip which creates archive files in the same format as PGP Zip, which is actually a USTAR format. That is fine and works nicely on all Unices but for Windows we don't have a compatible shell and the supply of tar programs is limited. Given that we need just a few tar option and it is an open question how many Unix concepts are to be mapped to Windows, we might as well write our own little tar customized for use with gpg. So here we go. */ #include #include #include #include #include #include #include "util.h" #include "i18n.h" #include "sysutils.h" #include "../common/openpgpdefs.h" #include "gpgtar.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, aEncrypt = 'e', aDecrypt = 'd', aSign = 's', oSymmetric = 'c', oRecipient = 'r', oUser = 'u', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oNoVerbose = 500, aSignEncrypt, oSkipCrypto, + oSetFilename, aList }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aEncrypt, "encrypt", N_("create an archive")), ARGPARSE_c (aDecrypt, "decrypt", N_("extract an archive")), ARGPARSE_c (aSign, "sign", N_("create a signed archive")), ARGPARSE_c (aList, "list-archive", N_("list an archive")), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")), + ARGPARSE_s_s (oSetFilename, "set-filename", "@"), ARGPARSE_end () }; static void tar_and_encrypt (char **inpattern); static void decrypt_and_untar (const char *fname); static void decrypt_and_list (const char *fname); /* Print usage information and and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpgtar (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpgtar [options] [files] [directories] (-h for help)"); break; case 41: p = _("Syntax: gpgtar [options] [files] [directories]\n" "Encrypt or sign files into an archive\n"); break; default: p = NULL; break; } return p; } 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 == aEncrypt) cmd = aSignEncrypt; else if (cmd == aEncrypt && new_cmd == aSign) cmd = aSignEncrypt; else { log_error (_("conflicting commands\n")); exit (2); } *ret_cmd = cmd; } /* gpgtar 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; int skip_crypto = 0; assert (sizeof (struct ustar_raw_header) == 512); gnupg_reopen_std ("gpgtar"); set_strusage (my_strusage); log_set_prefix ("gpgtar", 1); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; 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 oSetFilename: opt.filename = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case aList: case aDecrypt: case aEncrypt: case aSign: set_cmd (&cmd, pargs.r_opt); break; case oSymmetric: set_cmd (&cmd, aEncrypt); opt.symmetric = 1; break; case oSkipCrypto: skip_crypto = 1; break; default: pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); switch (cmd) { case aList: if (argc > 1) usage (1); fname = argc ? *argv : NULL; + if (opt.filename) + log_info ("note: ignoring option --set-filename\n"); if (skip_crypto) gpgtar_list (fname); else decrypt_and_list (fname); break; case aEncrypt: if (!argc) usage (1); + if (opt.filename) + log_info ("note: ignoring option --set-filename\n"); if (skip_crypto) gpgtar_create (argv); else tar_and_encrypt (argv); break; case aDecrypt: if (argc != 1) usage (1); if (opt.outfile) log_info ("note: ignoring option --output\n"); fname = argc ? *argv : NULL; if (skip_crypto) gpgtar_extract (fname); else decrypt_and_untar (fname); break; default: log_error (_("invalid command (there is no implicit command)\n")); break; } return log_get_errorcount (0)? 1:0; } /* Read the next record from STREAM. RECORD is a buffer provided by the caller and must be at leadt of size RECORDSIZE. The function return 0 on success and and error code on failure; a diagnostic printed as well. Note that there is no need for an EOF indicator because a tarball has an explicit EOF record. */ gpg_error_t read_record (estream_t stream, void *record) { gpg_error_t err; size_t nread; nread = es_fread (record, 1, RECORDSIZE, stream); if (nread != RECORDSIZE) { err = gpg_error_from_syserror (); if (es_ferror (stream)) log_error ("error reading `%s': %s\n", es_fname_get (stream), gpg_strerror (err)); else log_error ("error reading `%s': premature EOF " "(size of last record: %zu)\n", es_fname_get (stream), nread); } else err = 0; return err; } /* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the name of the file used for diagnostics. */ gpg_error_t write_record (estream_t stream, const void *record) { gpg_error_t err; size_t nwritten; nwritten = es_fwrite (record, 1, RECORDSIZE, stream); if (nwritten != RECORDSIZE) { err = gpg_error_from_syserror (); log_error ("error writing `%s': %s\n", es_fname_get (stream), gpg_strerror (err)); } else err = 0; return err; } /* Return true if FP is an unarmored OpenPGP message. Note that this fucntion reads a few bytes from FP but pushes them back. */ +#if 0 static int openpgp_message_p (estream_t fp) { int ctb; ctb = es_getc (fp); if (ctb != EOF) { if (es_ungetc (ctb, fp)) log_fatal ("error ungetting first byte: %s\n", gpg_strerror (gpg_error_from_syserror ())); if ((ctb & 0x80)) { switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf)) { case PKT_MARKER: case PKT_SYMKEY_ENC: case PKT_ONEPASS_SIG: case PKT_PUBKEY_ENC: case PKT_SIGNATURE: case PKT_COMMENT: case PKT_OLD_COMMENT: case PKT_PLAINTEXT: case PKT_COMPRESSED: case PKT_ENCRYPTED: return 1; /* Yes, this seems to be an OpenPGP message. */ default: break; } } } return 0; } - +#endif static void tar_and_encrypt (char **inpattern) { - + (void)inpattern; + log_error ("tar_and_encrypt has not yet been implemented\n"); } static void decrypt_and_untar (const char *fname) { - - + (void)fname; + log_error ("decrypt_and_untar has not yet been implemented\n"); } static void decrypt_and_list (const char *fname) { - + (void)fname; + log_error ("decrypt_and_list has not yet been implemented\n"); } diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 57fb34a48..08dfcf8b7 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -1,124 +1,125 @@ /* gpgtar.h - Global definitions for gpgtar * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef GPGTAR_H #define GPGTAR_H #include "../common/util.h" /* We keep all global options in the structure OPT. */ struct { int verbose; int quiet; - char *outfile; + const char *outfile; int symmetric; + const char *filename; } opt; /* The size of a tar record. All IO is done in chunks of this size. Note that we don't care about blocking because this version of tar is not expected to be used directly on a tape drive in fact it is used in a pipeline with GPG and thus any blocking would be useless. */ #define RECORDSIZE 512 /* Description of the USTAR header format. */ struct ustar_raw_header { char name[100]; char mode[8]; char uid[8]; char gid[8]; char size[12]; char mtime[12]; char checksum[8]; char typeflag[1]; char linkname[100]; char magic[6]; char version[2]; char uname[32]; char gname[32]; char devmajor[8]; char devminor[8]; char prefix[155]; char pad[12]; }; /* Filetypes as defined by USTAR. */ typedef enum { TF_REGULAR, TF_HARDLINK, TF_SYMLINK, TF_CHARDEV, TF_BLOCKDEV, TF_DIRECTORY, TF_FIFO, TF_RESERVED, TF_UNKNOWN, /* Needs to be treated as regular file. */ TF_NOTSUP /* Not supported (used with --create). */ } typeflag_t; /* The internal represenation of a TAR header. */ struct tar_header_s; typedef struct tar_header_s *tar_header_t; struct tar_header_s { tar_header_t next; /* Used to build a linked list iof entries. */ unsigned long mode; /* The file mode. */ unsigned long nlink; /* Number of hard links. */ unsigned long uid; /* The user id of the file. */ unsigned long gid; /* The group id of the file. */ unsigned long long size; /* The size of the file. */ unsigned long long mtime; /* Modification time since Epoch. Note that we don't use time_t here but a type which is more likely to be larger that 32 bit and thus allows to track times beyond 2106. */ typeflag_t typeflag; /* The type of the file. */ unsigned long long nrecords; /* Number of data records. */ char name[1]; /* Filename (dynamically extended). */ }; /*-- gpgtar.c --*/ gpg_error_t read_record (estream_t stream, void *record); gpg_error_t write_record (estream_t stream, const void *record); /*-- gpgtar-create.c --*/ void gpgtar_create (char **inpattern); /*-- gpgtar-extract.c --*/ void gpgtar_extract (const char *filename); /*-- gpgtar-list.c --*/ void gpgtar_list (const char *filename); tar_header_t gpgtar_read_header (estream_t stream); void gpgtar_print_header (tar_header_t header, estream_t out); #endif /*GPGTAR_H*/