diff --git a/configure.ac b/configure.ac index a1da1e3e..fb43474d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,928 +1,937 @@ # configure.ac for GPGME # Copyright (C) 2000 Werner Koch (dd9jn) # Copyright (C) 2001-2018 g10 Code GmbH # # This file is part of GPGME. # # GPGME is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # GPGME is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General # Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # (Process this file with autoconf to produce a configure script.) AC_PREREQ(2.59) min_automake_version="1.14" # To build a release you need to create a tag with the version number # (git tag -s gpgme-n.m.k) and run "./autogen.sh --force". Please # bump the version number immediately after the release and do another # commit and push so that the git magic is able to work. See below # for the LT versions. m4_define(mym4_version_major, [1]) m4_define(mym4_version_minor, [11]) m4_define(mym4_version_micro, [2]) # Below is m4 magic to extract and compute the revision number, the # decimalized short revision number, a beta version string, and a flag # indicating a development version (mym4_isgit). Note that the m4 # processing is done by autoconf and not during the configure run. m4_define(mym4_version, [mym4_version_major.mym4_version_minor.mym4_version_micro]) m4_define([mym4_revision], m4_esyscmd([git rev-parse --short HEAD | tr -d '\n\r'])) m4_define([mym4_revision_dec], m4_esyscmd_s([echo $((0x$(echo ]mym4_revision[|head -c 4)))])) m4_define([mym4_betastring], m4_esyscmd_s([git describe --match 'gpgme-[0-9].*[0-9]' --long|\ awk -F- '$3!=0{print"-beta"$3}'])) m4_define([mym4_isgit],m4_if(mym4_betastring,[],[no],[yes])) m4_define([mym4_full_version],[mym4_version[]mym4_betastring]) AC_INIT([gpgme],[mym4_full_version],[http://bugs.gnupg.org]) # LT Version numbers, remember to change them just *before* a release. # (Code changed: REVISION++) # (Interfaces added/removed/changed: CURRENT++, REVISION=0) # (Interfaces added: AGE++) # (Interfaces removed/changed: AGE=0) # LIBGPGME_LT_CURRENT=31 LIBGPGME_LT_AGE=20 LIBGPGME_LT_REVISION=1 # If there is an ABI break in gpgmepp or qgpgme also bump the # version in IMPORTED_LOCATION in the GpgmeppConfig-w32.cmake.in.in LIBGPGMEPP_LT_CURRENT=13 LIBGPGMEPP_LT_AGE=7 LIBGPGMEPP_LT_REVISION=0 LIBQGPGME_LT_CURRENT=10 LIBQGPGME_LT_AGE=3 LIBQGPGME_LT_REVISION=2 # If the API is changed in an incompatible way: increment the next counter. GPGME_CONFIG_API_VERSION=1 ############################################## NEED_GPG_ERROR_VERSION=1.24 NEED_LIBASSUAN_API=2 NEED_LIBASSUAN_VERSION=2.4.2 PACKAGE=$PACKAGE_NAME VERSION=$PACKAGE_VERSION VERSION_MAJOR=mym4_version_major VERSION_MINOR=mym4_version_minor VERSION_MICRO=mym4_version_micro AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR(src/gpgme.h.in) AC_CONFIG_HEADER(config.h) AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip]) AM_MAINTAINER_MODE AC_CANONICAL_HOST AM_SILENT_RULES AC_ARG_VAR(SYSROOT,[locate config scripts also below that directory]) # Enable GNU extensions on systems that have them. AC_GNU_SOURCE AH_VERBATIM([_REENTRANT], [/* To allow the use of GPGME in multithreaded programs we have to use special features from the library. IMPORTANT: gpgme is not yet fully reentrant and you should use it only from one thread. */ #ifndef _REENTRANT # define _REENTRANT 1 #endif]) AC_PROG_CC AC_PROG_CPP AC_PROG_CXX # Note: A suitable gitlog-to-changelog script can be found in GnuPG master. AC_CHECK_PROGS(GITLOG_TO_CHANGELOG, gitlog-to-changelog, [gitlog-to-changelog]) AC_SUBST(LIBGPGME_LT_CURRENT) AC_SUBST(LIBGPGME_LT_AGE) AC_SUBST(LIBGPGME_LT_REVISION) AC_SUBST(LIBGPGMEPP_LT_CURRENT) AC_SUBST(LIBGPGMEPP_LT_AGE) AC_SUBST(LIBGPGMEPP_LT_REVISION) AC_SUBST(LIBQGPGME_LT_CURRENT) AC_SUBST(LIBQGPGME_LT_AGE) AC_SUBST(LIBQGPGME_LT_REVISION) AC_SUBST(PACKAGE) AC_SUBST(VERSION) AC_SUBST(VERSION_MAJOR) AC_SUBST(VERSION_MINOR) AC_SUBST(VERSION_MICRO) AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package]) VERSION_NUMBER=m4_esyscmd(printf "0x%02x%02x%02x" mym4_version_major \ mym4_version_minor mym4_version_micro) AC_SUBST(VERSION_NUMBER) # We need to compile and run a program on the build machine. A # comment in libgpg-error says that the AC_PROG_CC_FOR_BUILD macro in # the AC archive is broken for autoconf 2.57. Given that there is no # newer version of that macro, we assume that it is also broken for # autoconf 2.61 and thus we use a simple but usually sufficient # approach. AC_MSG_CHECKING(for cc for build) if test "$cross_compiling" = "yes"; then CC_FOR_BUILD="${CC_FOR_BUILD-cc}" else CC_FOR_BUILD="${CC_FOR_BUILD-$CC}" fi AC_MSG_RESULT($CC_FOR_BUILD) AC_ARG_VAR(CC_FOR_BUILD,[build system C compiler]) # Don't default to build static libs. LT_PREREQ([2.2.6]) LT_INIT([win32-dll disable-static]) LT_LANG([Windows Resource]) # For now we hardcode the use of version scripts. It would be better # to write a test for this or even implement this within libtool. have_ld_version_script=no case "${host}" in *-*-linux*) have_ld_version_script=yes ;; *-*-gnu*) have_ld_version_script=yes ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 900000L, Expose all libc features (__DARWIN_C_FULL).) AC_DEFINE(_XOPEN_SOURCE, 500, Activate POSIX interface on MacOS X) ;; esac AM_CONDITIONAL(HAVE_LD_VERSION_SCRIPT, test "$have_ld_version_script" = "yes") GPG_DEFAULT=no GPGSM_DEFAULT=no GPGCONF_DEFAULT=no G13_DEFAULT=no component_system=None have_dosish_system=no have_android_system=no have_w32_system=no have_w64_system=no have_macos_system=no build_w32_glib=no build_w32_qt=no available_languages="cl cpp python python2 python3 qt" default_languages="cl cpp python qt" case "${host}" in x86_64-*mingw32*) have_w64_system=yes ;; *-mingw32ce*) have_w32ce_system=yes ;; *-linux-androideabi) have_android_system=yes ;; *-apple-darwin*) have_macos_system=yes ;; esac case "${host}" in *-mingw32ce*|*-mingw32*) have_dosish_system=yes have_w32_system=yes GPG_DEFAULT='c:\\gnupg\\gpg.exe' GPGSM_DEFAULT='c:\\gnupg\\gpgsm.exe' GPGCONF_DEFAULT='c:\\gnupg\\gpgconf.exe' G13_DEFAULT='c:\\gnupg\\g13.exe' #component_system='COM+' AM_PATH_GLIB_2_0 AC_ARG_ENABLE(w32-glib, AC_HELP_STRING([--enable-w32-glib], [build GPGME Glib for W32]), build_w32_glib=$enableval) ;; *) # XXX: Probably use exec-prefix here? # GPG_DEFAULT='/usr/bin/gpg' # GPGSM_DEFAULT='/usr/bin/gpgsm' # GPGCONF_DEFAULT='/usr/bin/gpgconf' # G13_DEFAULT='/usr/bin/g13' ;; esac if test "$have_dosish_system" = yes; then AC_DEFINE(HAVE_DOSISH_SYSTEM,1, [Defined if we run on some of the PCDOS like systems (DOS, Windoze. OS/2) with special properties like no file modes]) fi AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes) if test "$have_w32_system" = yes; then AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on any kind of W32 API based system]) fi AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes) if test "$have_w64_system" = yes; then AC_DEFINE(HAVE_W64_SYSTEM,1, [Defined if we run on a 64 bit W32 API based system]) fi AM_CONDITIONAL(HAVE_W64_SYSTEM, test "$have_w64_system" = yes) if test "$have_w32ce_system" = yes; then AC_DEFINE(HAVE_W32CE_SYSTEM,1, [Defined if we run on a W32 CE API based system]) fi AM_CONDITIONAL(HAVE_W32CE_SYSTEM, test "$have_w32ce_system" = yes) if test "$have_android_system" = yes; then AC_DEFINE(HAVE_ANDROID_SYSTEM,1, [Defined if we build for an Android system]) fi AM_CONDITIONAL(HAVE_ANDROID_SYSTEM, test "$have_android_system" = yes) if test "$have_macos_system" = yes; then AC_DEFINE(HAVE_MACOS_SYSTEM,1, [Defined if we build for an MacOS system]) fi AM_CONDITIONAL(HAVE_MACOS_SYSTEM, test "$have_macos_system" = yes) AM_CONDITIONAL(BUILD_W32_GLIB, test "$build_w32_glib" = yes) AC_ARG_ENABLE([fixed-path], AC_HELP_STRING([--enable-fixed-path=PATH], [locate binaries only via this PATH]), [fixed_search_path="$enableval"], [fixed_search_path=""]) if test x$fixed_search_path != x ; then AC_DEFINE_UNQUOTED(FIXED_SEARCH_PATH, "$fixed_search_path", [Locate binaries only via this PATH]) fi # Note: You need to declare all possible languages also in # lang/Makefile.am's DIST_SUBDIRS. AC_ARG_ENABLE([languages], AC_HELP_STRING([--enable-languages=languages], [enable only specific language bindings]), [enabled_languages=`echo $enableval | \ tr ',:' ' ' | tr '[A-Z]' '[a-z]' | \ sed 's/c++/cpp/'`], [enabled_languages="maybe"]) if test "x$enabled_languages" = "x" \ -o "$enabled_languages" = "no"; then enabled_languages= fi # If languages are explicitly set missing requirements # for the languages are treated as errors otherwise # there will be a warning. explicit_languages=1 if test "x$enabled_languages" = "xmaybe"; then explicit_languages=0 enabled_languages="$default_languages" fi for language in $enabled_languages; do LIST_MEMBER($language, $available_languages) if test "$found" = "0"; then AC_MSG_ERROR([unsupported language binding specified]) fi done # Enable C++ 11 if cpp language is requested LIST_MEMBER("cpp", $enabled_languages) if test "$found" = "1"; then AX_CXX_COMPILE_STDCXX(11, noext, optional) if test "$HAVE_CXX11" != "1"; then if test "$explicit_languages" = "1"; then AC_MSG_ERROR([[ *** *** A compiler with c++11 support is required for the c++ binding. ***]]) else enabled_languages=$(echo $enabled_languages | sed 's/cpp//') enabled_languages=$(echo $enabled_languages | sed 's/qt//') AC_MSG_WARN([[ *** *** No c++11 support detected. C++ and Qt bindings will be disabled. ***]]) fi fi fi # Check that if qt is enabled cpp also is enabled LIST_MEMBER("qt", $enabled_languages) if test "$found" = "1"; then # We need to ensure that in the language order qt comes after cpp # so we remove qt first and explicitly add it as last list member. enabled_languages=$(echo $enabled_languages | sed 's/qt//') LIST_MEMBER("cpp", $enabled_languages) if test "$found" = "0"; then AC_MSG_ERROR([[ *** *** Qt language binding depends on cpp binding. ***]]) fi FIND_QT if test "$have_qt5_libs" != "yes"; then if test "$explicit_languages" = "1"; then AC_MSG_ERROR([[ *** *** Qt5 (Qt5Core) is required for Qt binding. ***]]) else AC_MSG_WARN([[ *** *** Qt5 (Qt5Core) not found Qt Binding will be disabled. ***]]) fi else enabled_languages=`echo $enabled_languages qt` AC_CHECK_PROGS([DOXYGEN], [doxygen]) if test -z "$DOXYGEN"; # This is not highlighted becase it's not really important. then AC_MSG_WARN([Doxygen not found - Qt binding doc will not be built.]) fi AC_CHECK_PROGS([GRAPHVIZ], [dot]) if test -z "$GRAPHVIZ"; then AC_MSG_WARN([Graphviz not found - Qt binding doc will not have diagrams.]) fi fi fi AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$DOXYGEN"]) if test -n "$GRAPHVIZ"; then HAVE_DOT="YES" else HAVE_DOT="NO" fi AC_SUBST(HAVE_DOT) # Python bindings. LIST_MEMBER("python2", $enabled_languages) found_py2=$found LIST_MEMBER("python3", $enabled_languages) found_py3=$found LIST_MEMBER("python", $enabled_languages) found_py=$found if test "$found_py" = "1" -o "$found_py2" = "1" -o "$found_py3" = "1"; then AX_PKG_SWIG if test -z "$SWIG"; then if test "$explicit_languages" = "1"; then AC_MSG_ERROR([[ *** *** You need SWIG to build the Python bindings. ***]]) else enabled_languages=$(echo $enabled_languages | sed 's/python//') fi else # Reset all the stuff, just to be sure. PYTHONS= PYTHON_VERSIONS= unset PYTHON unset PYTHON_VERSION unset PYTHON_CPPFLAGS unset PYTHON_LDFLAGS unset PYTHON_SITE_PKG unset PYTHON_EXTRA_LIBS unset PYTHON_EXTRA_LDFLAGS unset ac_cv_path_PYTHON unset am_cv_pathless_PYTHON unset am_cv_python_version unset am_cv_python_platform unset am_cv_python_pythondir unset am_cv_python_pyexecdir if test "$found_py" = "1" -o "$found_py2" = "1"; then AM_PATH_PYTHON([2.7], [ AX_PYTHON_DEVEL if test "$PYTHON_VERSION"; then PYTHONS="$(echo $PYTHONS $PYTHON)" PYTHON_VERSIONS="$(echo $PYTHON_VERSIONS $PYTHON_VERSION)" fi ], :) fi if test "$found_py" = "1" -o "$found_py3" = "1"; then # Reset everything, so that we can look for another Python. unset PYTHON unset PYTHON_VERSION unset PYTHON_CPPFLAGS unset PYTHON_LDFLAGS unset PYTHON_SITE_PKG unset PYTHON_EXTRA_LIBS unset PYTHON_EXTRA_LDFLAGS unset ac_cv_path_PYTHON unset am_cv_pathless_PYTHON unset am_cv_python_version unset am_cv_python_platform unset am_cv_python_pythondir unset am_cv_python_pyexecdir AM_PATH_PYTHON([3.4], [ AX_PYTHON_DEVEL if test "$PYTHON_VERSION"; then PYTHONS="$(echo $PYTHONS $PYTHON)" PYTHON_VERSIONS="$(echo $PYTHON_VERSIONS $PYTHON_VERSION)" fi ], :) fi # Recover some values lost in the second attempt to find Python. PYTHON="$(echo $PYTHONS | cut -d ' ' -f 1)" PYTHON_VERSION="$(echo $PYTHON_VERSIONS | cut -d ' ' -f 1)" # Remove duplicates. PYTHONS="$(echo $PYTHONS | tr '[[:space:]]' '\n' | sort | uniq | tr '\n' ' ' | sed -e 's/ $//')" PYTHON_VERSIONS="$(echo $PYTHON_VERSIONS | tr '[[:space:]]' '\n' | sort | uniq | tr '\n' ' ' | sed -e 's/ $//')" if test "$PYTHON_VERSIONS"; then enabled_languages_v=$(echo $enabled_languages | sed -Ee "s/python[[23]]?/python ($PYTHON_VERSIONS)/") enabled_languages=$(echo $enabled_languages | sed -Ee "s/python[[23]]?/python/") else if test "$explicit_languages" = "1"; then AC_MSG_ERROR([[ *** *** Please install the python development packages. ***]]) else enabled_languages=$(echo $enabled_languages | sed 's/python//') fi fi AC_SUBST(PYTHONS, $PYTHONS) fi fi AC_SUBST(ENABLED_LANGUAGES, $enabled_languages) # # Provide information about the build. # BUILD_REVISION="mym4_revision" AC_SUBST(BUILD_REVISION) AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION", [GIT commit id revision used to build this package]) changequote(,)dnl BUILD_FILEVERSION=`echo "$PACKAGE_VERSION"|sed 's/\([0-9.]*\).*/\1./;s/\./,/g'` changequote([,])dnl BUILD_FILEVERSION="${BUILD_FILEVERSION}mym4_revision_dec" AC_SUBST(BUILD_FILEVERSION) AC_ARG_ENABLE([build-timestamp], AC_HELP_STRING([--enable-build-timestamp], [set an explicit build timestamp for reproducibility. (default is the current time in ISO-8601 format)]), [if test "$enableval" = "yes"; then BUILD_TIMESTAMP=`date -u +%Y-%m-%dT%H:%M+0000 2>/dev/null || date` else BUILD_TIMESTAMP="$enableval" fi], [BUILD_TIMESTAMP=""]) AC_SUBST(BUILD_TIMESTAMP) AC_DEFINE_UNQUOTED(BUILD_TIMESTAMP, "$BUILD_TIMESTAMP", [The time this package was configured for a build]) # # Options to disable some regression tests # run_gpgconf_test="yes" AC_ARG_ENABLE(gpgconf-test, AC_HELP_STRING([--disable-gpgconf-test], [disable GPGCONF regression test]), run_gpgconf_test=$enableval) AM_CONDITIONAL(RUN_GPGCONF_TESTS, test "$run_gpgconf_test" = "yes") run_gpg_test="yes" AC_ARG_ENABLE(gpg-test, AC_HELP_STRING([--disable-gpg-test], [disable GPG regression test]), run_gpg_test=$enableval) AM_CONDITIONAL(RUN_GPG_TESTS, test "$run_gpg_test" = "yes") run_gpgsm_test="yes" AC_ARG_ENABLE(gpgsm-test, AC_HELP_STRING([--disable-gpgsm-test], [disable GPGSM regression test]), run_gpgsm_test=$enableval) AM_CONDITIONAL(RUN_GPGSM_TESTS, test "$run_gpgsm_test" = "yes") run_g13_test="yes" AC_ARG_ENABLE(g13-test, AC_HELP_STRING([--disable-g13-test], [disable G13 regression test]), run_g13_test=$enableval) AM_CONDITIONAL(RUN_G13_TESTS, test "$run_g13_test" = "yes") # Checks for header files. -AC_CHECK_HEADERS_ONCE([locale.h sys/select.h sys/uio.h argp.h +AC_CHECK_HEADERS_ONCE([locale.h sys/select.h sys/uio.h argp.h stdint.h unistd.h sys/time.h sys/types.h sys/stat.h]) # Type checks. AC_C_INLINE AC_CHECK_SIZEOF(unsigned int) AC_SYS_LARGEFILE AC_TYPE_OFF_T AC_TYPE_UINTPTR_T +# We require uint64_t +if test "$ac_cv_header_stdint_h" != yes; then + AC_MSG_ERROR([[ +*** +*** No stdint.h and thus no uint64_t type. Can't build this library. +***]]) +fi + + # A simple compile time check in gpgme.h for GNU/Linux systems that # prevents a file offset bits mismatch between gpgme and the application. NEED__FILE_OFFSET_BITS=0 if test "$have_w32_system" != yes; then case "$ac_cv_sys_file_offset_bits" in "" | no | unknown) ;; *) NEED__FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits ;; esac fi AC_SUBST(NEED__FILE_OFFSET_BITS) # Figure out platform dependent typedefs for gpgme.h if test "$have_w32_system" = yes; then INSERT__TYPEDEFS_FOR_GPGME_H=" #ifdef _WIN64 # include typedef int64_t gpgme_off_t; typedef int64_t gpgme_ssize_t; #else /* _WIN32 */ typedef long gpgme_off_t; typedef long gpgme_ssize_t; #endif /* _WIN32 */" API__OFF_T="gpgme_off_t" API__SSIZE_T="gpgme_ssize_t" else INSERT__TYPEDEFS_FOR_GPGME_H=" #include typedef off_t gpgme_off_t; typedef ssize_t gpgme_ssize_t;" API__OFF_T="off_t" API__SSIZE_T="ssize_t" fi AC_SUBST(INSERT__TYPEDEFS_FOR_GPGME_H) AM_SUBST_NOTMAKE(INSERT__TYPEDEFS_FOR_GPGME_H) AC_SUBST(API__OFF_T) AM_SUBST_NOTMAKE(API__OFF_T) AC_SUBST(API__SSIZE_T) AM_SUBST_NOTMAKE(API__SSIZE_T) # Checks for compiler features. if test "$GCC" = yes; then CFLAGS="$CFLAGS -Wall -Wcast-align -Wshadow -Wstrict-prototypes" if test "$USE_MAINTAINER_MODE" = "yes"; then CFLAGS="$CFLAGS -Wformat -Wno-format-y2k -Wformat-security" # If -Wno-missing-field-initializers is supported we can enable a # a bunch of really useful warnings. AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) _gcc_cflags_save=$CFLAGS CFLAGS="-Wno-missing-field-initializers" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) CFLAGS=$_gcc_cflags_save; if test x"$_gcc_wopt" = xyes ; then CFLAGS="$CFLAGS -W -Wextra -Wbad-function-cast" CFLAGS="$CFLAGS -Wwrite-strings" CFLAGS="$CFLAGS -Wdeclaration-after-statement" CFLAGS="$CFLAGS -Wno-missing-field-initializers" CFLAGS="$CFLAGS -Wno-sign-compare" fi CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wno-shadow" AC_MSG_CHECKING([if gcc supports -Wpointer-arith]) _gcc_cflags_save=$CFLAGS CFLAGS="-Wpointer-arith" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) CFLAGS=$_gcc_cflags_save; if test x"$_gcc_wopt" = xyes ; then CFLAGS="$CFLAGS -Wpointer-arith" fi fi if test "$have_w32_system" = yes; then CFLAGS="$CFLAGS -mms-bitfields" fi fi # Only used for debugging, so no serious test needed (for actual # functionality you have to test libc as well, this only tests the # compiler). AC_CACHE_CHECK([for __thread],[gpgme_cv_tls_works], AC_COMPILE_IFELSE([AC_LANG_PROGRAM([__thread int foo;])], gpgme_cv_tls_works=yes,gpgme_cv_tls_works=no)) if test "$gpgme_cv_tls_works" = yes; then AC_DEFINE(HAVE_TLS, [1], [Define if __thread is supported]) fi # Checks for library functions. AC_MSG_NOTICE([checking for libraries]) AC_FUNC_FSEEKO # Try to find a thread-safe version of ttyname(). gnupg_REPLACE_TTYNAME_R if test "$ac_cv_func_ttyname_r" != yes; then AC_MSG_WARN([ *** *** ttyname() is not thread-safe and ttyname_r() does not exist ***]) fi # Try to find a thread-safe version of getenv(). have_thread_safe_getenv=no jm_GLIBC21 if test $GLIBC21 = yes -o $have_w32_system = yes; then have_thread_safe_getenv=yes fi if test $have_thread_safe_getenv = yes; then AC_DEFINE(HAVE_THREAD_SAFE_GETENV, [1], [Define if getenv() is thread-safe]) fi have_getenv_r=no AC_CHECK_FUNCS(getenv_r, have_getenv_r=yes) if test $have_getenv_r = no && test $have_thread_safe_getenv = no; then AC_MSG_WARN([ *** *** getenv() is not thread-safe and getenv_r() does not exist ***]) fi # For converting time strings to seconds since Epoch, we need the timegm # function. AC_CHECK_FUNCS(timegm) if test "$ac_cv_func_timegm" != yes; then AC_MSG_WARN([ *** *** timegm() not available - a non-thread-safe kludge will be used *** and the TZ variable might be changed at runtime. ***]) fi AC_CHECK_FUNCS(setlocale) # Checking for libgpg-error. have_gpg_error=no AM_PATH_GPG_ERROR("$NEED_GPG_ERROR_VERSION", have_gpg_error=yes, have_gpg_error=no) AC_DEFINE(GPG_ERR_SOURCE_DEFAULT, GPG_ERR_SOURCE_GPGME, [The default error source for GPGME.]) # And for libassuan. have_libassuan=no AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_API:$NEED_LIBASSUAN_VERSION", have_libassuan=yes, have_libassuan=no) if test "$have_libassuan" = "yes"; then AC_DEFINE_UNQUOTED(GPGME_LIBASSUAN_VERSION, "$libassuan_version", [version of the libassuan library]) fi # # Other checks # # Check for getgid etc AC_CHECK_FUNCS(getgid getegid closefrom) # Replacement functions. AC_REPLACE_FUNCS(stpcpy) AC_REPLACE_FUNCS(setenv) # Assuan check for descriptor passing. AC_CHECK_MEMBER(struct cmsghdr.cmsg_len, [supports_descriptor_passing=yes], [supports_descriptor_passing=no AC_MSG_WARN([ *** *** Data structure for sending ancillary data missing. *** Descriptor passing won't work. ***])],[ #include #include #include #include #include #include #if HAVE_SYS_UIO_H #include #endif #include ]) use_descriptor_passing=yes AC_ARG_ENABLE(fd-passing, AC_HELP_STRING([--disable-fd-passing], [do not use FD passing]), use_descriptor_passing=$enableval) if test "$supports_descriptor_passing" != "yes"; then use_descriptor_passing=no fi if test "$use_descriptor_passing" = "yes"; then AC_DEFINE(USE_DESCRIPTOR_PASSING,1, [Defined if descriptor passing is enabled and supported]) fi AM_CONDITIONAL(USE_DESCRIPTOR_PASSING, test "$use_descriptor_passing" = "yes") uiserver=no if test "$use_descriptor_passing" = "yes" && test "$have_libassuan" = "yes"; then uiserver=yes fi if test "$uiserver" != "no"; then AC_DEFINE(ENABLE_UISERVER, 1, [Defined if we are building with uiserver support.]) fi AM_CONDITIONAL(HAVE_UISERVER, test "$uiserver" != "no") # Option --disable-linux-getdents # # By default we use SYS_getdents on Linux to optimize fd closing # before an exec. This option allows to switch this optimization off. use_linux_getdents=yes AC_ARG_ENABLE(linux-getdents, AC_HELP_STRING([--disable-linux-getdents], [do not use SYS_getdents on Linux]), use_linux_getdents=$enableval) if test "$use_linux_getdents" = "yes"; then case "${host}" in *-*-linux*) AC_DEFINE(USE_LINUX_GETDENTS,1, [Defined if SYS_getdents can be used on Linux]) ;; esac fi # # Add a few constants to help porting to W32 # AH_VERBATIM([SEPCONSTANTS], [ /* Separators as used in $PATH and file name. */ #ifdef HAVE_DOSISH_SYSTEM #define PATHSEP_C ';' #define DIRSEP_C '\\' #define DIRSEP_S "\\" #else #define PATHSEP_C ':' #define DIRSEP_C '/' #define DIRSEP_S "/" #endif ]) AH_BOTTOM([ /* Definition of GCC specific attributes. */ #if __GNUC__ > 2 # define GPGME_GCC_A_PURE __attribute__ ((__pure__)) #else # define GPGME_GCC_A_PURE #endif /* Under WindowsCE we need gpg-error's strerror macro. */ #define GPG_ERR_ENABLE_ERRNO_MACROS 1 #define CRIGHTBLURB "Copyright (C) 2000 Werner Koch\n" \ "Copyright (C) 2001--2018 g10 Code GmbH\n" ]) # Substitution used for gpgme-config GPGME_CONFIG_LIBS="-lgpgme" GPGME_CONFIG_CFLAGS="" GPGME_CONFIG_HOST="$host" GPGME_CONFIG_AVAIL_LANG="$enabled_languages" AC_SUBST(GPGME_CONFIG_API_VERSION) AC_SUBST(GPGME_CONFIG_LIBS) AC_SUBST(GPGME_CONFIG_CFLAGS) AC_SUBST(GPGME_CONFIG_HOST) AC_SUBST(GPGME_CONFIG_AVAIL_LANG) # Frob'da Variables LTLIBOBJS=`echo "$LIB@&t@OBJS" | sed 's,\.[[^.]]* ,.lo ,g;s,\.[[^.]]*$,.lo,'` AC_SUBST(LTLIBOBJS) # Some checks for gpgme-tool # Done at top: AC_CHECK_HEADER([argp.h]) AC_CHECK_TYPES([error_t], [], [AC_DEFINE([error_t], [int], [Define to a type to use for `error_t' if it is not otherwise available.])], [#include ]) # A substitution to set generated files in a Emacs buffer to read-only. AC_SUBST(emacs_local_vars_begin, [['Local][ ][Variables:']]) AC_SUBST(emacs_local_vars_read_only, ['buffer-read-only: t']) AC_SUBST(emacs_local_vars_end, ['End:']) # Last check. die=no if test "$have_gpg_error" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgpg-error to build this program. ** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libgpg-error *** (at least version $NEED_GPG_ERROR_VERSION is required.) ***]]) fi if test "$have_libassuan" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libassuan to build this program. *** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libassuan/ *** (at least version $NEED_LIBASSUAN_VERSION (API $NEED_LIBASSUAN_API) is required). ***]]) fi if test "$die" = "yes"; then AC_MSG_ERROR([[ *** *** Required libraries not found. Please consult the above messages *** and install them before running configure again. ***]]) fi # # Create config files AC_CONFIG_FILES(Makefile src/Makefile tests/Makefile tests/gpg/Makefile tests/gpgsm/Makefile tests/opassuan/Makefile doc/Makefile src/versioninfo.rc src/gpgme.h) AC_CONFIG_FILES(src/gpgme-config, chmod +x src/gpgme-config) AC_CONFIG_FILES(lang/cpp/Makefile lang/cpp/src/Makefile) AC_CONFIG_FILES(lang/cpp/src/GpgmeppConfig-w32.cmake.in) AC_CONFIG_FILES(lang/cpp/src/GpgmeppConfig.cmake.in) AC_CONFIG_FILES(lang/cpp/src/GpgmeppConfigVersion.cmake) AC_CONFIG_FILES(lang/cpp/src/gpgmepp_version.h) AC_CONFIG_FILES(lang/qt/Makefile lang/qt/src/Makefile) AC_CONFIG_FILES(lang/qt/src/QGpgmeConfig-w32.cmake.in) AC_CONFIG_FILES(lang/qt/src/QGpgmeConfig.cmake.in) AC_CONFIG_FILES(lang/qt/src/QGpgmeConfigVersion.cmake) AC_CONFIG_FILES(lang/qt/tests/Makefile) AC_CONFIG_FILES(lang/qt/src/qgpgme_version.h) AC_CONFIG_FILES([lang/Makefile lang/cl/Makefile lang/cl/gpgme.asd]) AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([lang/qt/doc/Doxyfile])]) AC_CONFIG_FILES(lang/qt/doc/Makefile) AC_CONFIG_FILES([lang/python/Makefile lang/python/version.py lang/python/tests/Makefile]) AC_CONFIG_FILES([lang/python/setup.py], [chmod a+x lang/python/setup.py]) AC_OUTPUT echo " GPGME v${VERSION} has been configured as follows: Revision: mym4_revision (mym4_revision_dec) Platform: $host UI Server: $uiserver FD Passing: $use_descriptor_passing Language bindings: ${enabled_languages_v:-$enabled_languages} " if test "x${gpg_config_script_warn}" != x; then cat < #endif #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include "data.h" #include "util.h" #include "debug.h" static gpgme_ssize_t mem_read (gpgme_data_t dh, void *buffer, size_t size) { size_t amt = dh->data.mem.length - dh->data.mem.offset; const char *src; if (!amt) return 0; if (size < amt) amt = size; src = dh->data.mem.buffer ? dh->data.mem.buffer : dh->data.mem.orig_buffer; memcpy (buffer, src + dh->data.mem.offset, amt); dh->data.mem.offset += amt; return amt; } static gpgme_ssize_t mem_write (gpgme_data_t dh, const void *buffer, size_t size) { size_t unused; if (!dh->data.mem.buffer && dh->data.mem.orig_buffer) { size_t new_size = dh->data.mem.size; char *new_buffer; if (new_size < dh->data.mem.offset + size) new_size = dh->data.mem.offset + size; new_buffer = malloc (new_size); if (!new_buffer) return -1; memcpy (new_buffer, dh->data.mem.orig_buffer, dh->data.mem.length); dh->data.mem.buffer = new_buffer; dh->data.mem.size = new_size; } unused = dh->data.mem.size - dh->data.mem.offset; if (unused < size) { /* Allocate a large enough buffer with exponential backoff. */ #define INITIAL_ALLOC 512 size_t new_size = dh->data.mem.size ? (2 * dh->data.mem.size) : INITIAL_ALLOC; char *new_buffer; if (new_size < dh->data.mem.offset + size) new_size = dh->data.mem.offset + size; new_buffer = realloc (dh->data.mem.buffer, new_size); if (!new_buffer && new_size > dh->data.mem.offset + size) { /* Maybe we were too greedy, try again. */ new_size = dh->data.mem.offset + size; new_buffer = realloc (dh->data.mem.buffer, new_size); } if (!new_buffer) return -1; dh->data.mem.buffer = new_buffer; dh->data.mem.size = new_size; } memcpy (dh->data.mem.buffer + dh->data.mem.offset, buffer, size); dh->data.mem.offset += size; if (dh->data.mem.length < dh->data.mem.offset) dh->data.mem.length = dh->data.mem.offset; return size; } static gpgme_off_t mem_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) { switch (whence) { case SEEK_SET: if (offset < 0 || offset > dh->data.mem.length) { gpg_err_set_errno (EINVAL); return -1; } dh->data.mem.offset = offset; break; case SEEK_CUR: if ((offset > 0 && dh->data.mem.length - dh->data.mem.offset < offset) || (offset < 0 && dh->data.mem.offset < -offset)) { gpg_err_set_errno (EINVAL); return -1; } dh->data.mem.offset += offset; break; case SEEK_END: if (offset > 0 || -offset > dh->data.mem.length) { gpg_err_set_errno (EINVAL); return -1; } dh->data.mem.offset = dh->data.mem.length + offset; break; default: gpg_err_set_errno (EINVAL); return -1; } return dh->data.mem.offset; } static void mem_release (gpgme_data_t dh) { if (dh->data.mem.buffer) free (dh->data.mem.buffer); } static struct _gpgme_data_cbs mem_cbs = { mem_read, mem_write, mem_seek, mem_release, NULL }; /* Create a new data buffer and return it in R_DH. */ gpgme_error_t gpgme_data_new (gpgme_data_t *r_dh) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme_data_new", r_dh); err = _gpgme_data_new (r_dh, &mem_cbs); if (err) return TRACE_ERR (err); return TRACE_SUC1 ("dh=%p", *r_dh); } /* Create a new data buffer filled with SIZE bytes starting from BUFFER. If COPY is zero, copying is delayed until necessary, and the data is taken from the original location when needed. */ gpgme_error_t gpgme_data_new_from_mem (gpgme_data_t *r_dh, const char *buffer, size_t size, int copy) { gpgme_error_t err; TRACE_BEG4 (DEBUG_DATA, "gpgme_data_new_from_mem", r_dh, "buffer=%p, size=%u, copy=%i (%s)", buffer, size, copy, copy ? "yes" : "no"); err = _gpgme_data_new (r_dh, &mem_cbs); if (err) return TRACE_ERR (err); if (copy) { char *bufcpy = malloc (size); if (!bufcpy) { int saved_err = gpg_error_from_syserror (); _gpgme_data_release (*r_dh); return TRACE_ERR (saved_err); } memcpy (bufcpy, buffer, size); (*r_dh)->data.mem.buffer = bufcpy; } else (*r_dh)->data.mem.orig_buffer = buffer; (*r_dh)->data.mem.size = size; (*r_dh)->data.mem.length = size; return TRACE_SUC1 ("dh=%p", *r_dh); } /* Destroy the data buffer DH and return a pointer to its content. The memory has be to released with gpgme_free() by the user. It's size is returned in R_LEN. */ char * gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) { + gpg_error_t err; char *str = NULL; + size_t len; + int blankout; TRACE_BEG1 (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh, "r_len=%p", r_len); if (!dh || dh->cbs != &mem_cbs) { gpgme_data_release (dh); TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); return NULL; } + err = _gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout); + if (err) + { + gpgme_data_release (dh); + TRACE_ERR (err); + return NULL; + } + str = dh->data.mem.buffer; + len = dh->data.mem.length; + if (blankout && len) + len = 1; + if (!str && dh->data.mem.orig_buffer) { - str = malloc (dh->data.mem.length); + str = malloc (len); if (!str) { int saved_err = gpg_error_from_syserror (); gpgme_data_release (dh); TRACE_ERR (saved_err); return NULL; } - memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length); + if (blankout) + memset (str, 0, len); + else + memcpy (str, dh->data.mem.orig_buffer, len); } else - /* Prevent mem_release from releasing the buffer memory. We must - not fail from this point. */ - dh->data.mem.buffer = NULL; + { + if (blankout && len) + *str = 0; + /* Prevent mem_release from releasing the buffer memory. We + * must not fail from this point. */ + dh->data.mem.buffer = NULL; + } if (r_len) - *r_len = dh->data.mem.length; + *r_len = len; gpgme_data_release (dh); if (r_len) { TRACE_SUC2 ("buffer=%p, len=%u", str, *r_len); } else { TRACE_SUC1 ("buffer=%p", str); } return str; } /* Release the memory returned by gpgme_data_release_and_get_mem() and some other functions. */ void gpgme_free (void *buffer) { TRACE (DEBUG_DATA, "gpgme_free", buffer); if (buffer) free (buffer); } diff --git a/src/data.c b/src/data.c index 7ae5b327..e826f102 100644 --- a/src/data.c +++ b/src/data.c @@ -1,384 +1,661 @@ /* data.c - An abstraction for data objects. Copyright (C) 2002, 2003, 2004, 2005, 2007 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #ifdef HAVE_UNISTD_H # include #endif #include #include +#include #include "gpgme.h" #include "data.h" #include "util.h" #include "ops.h" #include "priv-io.h" #include "debug.h" + +/* The property table which has an entry for each active data object. + * The data object itself uses an index into this table and the table + * has a pointer back to the data object. All access to that table is + * controlled by the property_table_lock. + * + * We use a separate table instead of linking all data objects + * together for faster locating properties of the data object using + * the data objects serial number. We use 64 bit for the serial + * number which is good enough to create a new data object every + * nanosecond for more than 500 years. Thus no wrap around will ever + * happen. + */ +struct property_s +{ + gpgme_data_t dh; /* The data objcet or NULL if the slot is not used. */ + uint64_t dserial; /* The serial number of the data object. */ + struct { + unsigned int blankout : 1; /* Void the held data. */ + } flags; +}; +typedef struct property_s *property_t; + +static property_t property_table; +static unsigned int property_table_size; +DEFINE_STATIC_LOCK (property_table_lock); + + + +/* Insert the newly created data object DH into the property table and + * store the index of it at R_IDX. An error code is returned on error + * and the table is not changed. */ +static gpg_error_t +insert_into_property_table (gpgme_data_t dh, unsigned int *r_idx) +{ + static uint64_t last_dserial; + gpg_error_t err; + unsigned int idx; + + LOCK (property_table_lock); + if (!property_table) + { + property_table_size = 10; + property_table = calloc (property_table_size, sizeof *property_table); + if (!property_table) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Find an empty slot. */ + for (idx = 0; idx < property_table_size; idx++) + if (!property_table[idx].dh) + break; + if (!(idx < property_table_size)) + { + /* No empty slot found. Enlarge the table. */ + property_t newtbl; + unsigned int newsize; + + newsize = property_table_size + 10; + if ((newsize * sizeof *property_table) + < (property_table_size * sizeof *property_table)) + { + err = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + newtbl = realloc (property_table, newsize * sizeof *property_table); + if (!newtbl) + { + err = gpg_error_from_syserror (); + goto leave; + } + property_table = newtbl; + for (idx = property_table_size; idx < newsize; idx++) + property_table[idx].dh = NULL; + idx = property_table_size; + property_table_size = newsize; + } + + /* Slot found. */ + property_table[idx].dh = dh; + property_table[idx].dserial = ++last_dserial; + *r_idx = idx; + err = 0; + + leave: + UNLOCK (property_table_lock); + return err; +} + + +/* Remove the data object at PROPIDX from the table. DH is only used + * for cross checking. */ +static void +remove_from_property_table (gpgme_data_t dh, unsigned int propidx) +{ + LOCK (property_table_lock); + assert (property_table); + assert (propidx < property_table_size); + assert (property_table[propidx].dh == dh); + property_table[propidx].dh = NULL; + UNLOCK (property_table_lock); +} + + +/* Return the data object's serial number for handle DH. This is a + * unique serial number for each created data object. */ +uint64_t +_gpgme_data_get_dserial (gpgme_data_t dh) +{ + uint64_t dserial; + unsigned int idx; + + if (!dh) + return 0; + + idx = dh->propidx; + LOCK (property_table_lock); + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + dserial = property_table[idx].dserial; + UNLOCK (property_table_lock); + + return dserial; +} + + +/* Set an internal property of a data object. The data object may + * either be identified by the usual DH or by using the data serial + * number DSERIAL. */ +gpg_error_t +_gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int value) +{ + gpg_error_t err = 0; + int idx; + TRACE_BEG3 (DEBUG_DATA, "gpgme_data_set_prop", dh, + "dserial=%llu %lu=%d", + (unsigned long long)dserial, + (unsigned long)name, value); + + LOCK (property_table_lock); + if ((!dh && !dserial) || (dh && dserial)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (dh) /* Lookup via handle. */ + { + idx = dh->propidx; + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + } + else /* Lookup via DSERIAL. */ + { + if (!property_table) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + for (idx = 0; idx < property_table_size; idx++) + if (property_table[idx].dh && property_table[idx].dserial == dserial) + break; + if (!(idx < property_table_size)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + switch (name) + { + case DATA_PROP_NONE: /* Nothing to to do. */ + break; + case DATA_PROP_BLANKOUT: + property_table[idx].flags.blankout = !!value; + break; + + default: + err = gpg_error (GPG_ERR_UNKNOWN_NAME); + break; + } + + leave: + UNLOCK (property_table_lock); + return TRACE_ERR (err); +} + + +/* Get an internal property of a data object. This is the counter + * part to _gpgme_data_set_property. The value of the property is + * stored at R_VALUE. On error 0 is stored at R_VALUE. */ +gpg_error_t +_gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int *r_value) +{ + gpg_error_t err = 0; + int idx; + TRACE_BEG2 (DEBUG_DATA, "gpgme_data_get_prop", dh, + "dserial=%llu %lu", + (unsigned long long)dserial, + (unsigned long)name); + + *r_value = 0; + + LOCK (property_table_lock); + if ((!dh && !dserial) || (dh && dserial)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (dh) /* Lookup via handle. */ + { + idx = dh->propidx; + assert (property_table); + assert (idx < property_table_size); + assert (property_table[idx].dh == dh); + } + else /* Lookup via DSERIAL. */ + { + if (!property_table) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + for (idx = 0; idx < property_table_size; idx++) + if (property_table[idx].dh && property_table[idx].dserial == dserial) + break; + if (!(idx < property_table_size)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + switch (name) + { + case DATA_PROP_NONE: /* Nothing to to do. */ + break; + case DATA_PROP_BLANKOUT: + *r_value = property_table[idx].flags.blankout; + break; + + default: + err = gpg_error (GPG_ERR_UNKNOWN_NAME); + break; + } + + leave: + UNLOCK (property_table_lock); + return TRACE_ERR (err); +} + + gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs) { + gpgme_error_t err; gpgme_data_t dh; if (!r_dh) return gpg_error (GPG_ERR_INV_VALUE); *r_dh = NULL; if (_gpgme_selftest) return _gpgme_selftest; dh = calloc (1, sizeof (*dh)); if (!dh) return gpg_error_from_syserror (); dh->cbs = cbs; + err = insert_into_property_table (dh, &dh->propidx); + if (err) + { + free (dh); + return err; + } + *r_dh = dh; return 0; } void _gpgme_data_release (gpgme_data_t dh) { if (!dh) return; + remove_from_property_table (dh, dh->propidx); if (dh->file_name) free (dh->file_name); free (dh); } + /* Read up to SIZE bytes into buffer BUFFER from the data object with the handle DH. Return the number of characters read, 0 on EOF and -1 on error. If an error occurs, errno is set. */ gpgme_ssize_t gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size) { gpgme_ssize_t res; + int blankout; TRACE_BEG2 (DEBUG_DATA, "gpgme_data_read", dh, "buffer=%p, size=%u", buffer, size); if (!dh) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } if (!dh->cbs->read) { gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } - do - res = (*dh->cbs->read) (dh, buffer, size); - while (res < 0 && errno == EINTR); + + if (_gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout) + || blankout) + res = 0; + else + { + do + res = (*dh->cbs->read) (dh, buffer, size); + while (res < 0 && errno == EINTR); + } return TRACE_SYSRES (res); } /* Write up to SIZE bytes from buffer BUFFER to the data object with the handle DH. Return the number of characters written, or -1 on error. If an error occurs, errno is set. */ gpgme_ssize_t gpgme_data_write (gpgme_data_t dh, const void *buffer, size_t size) { gpgme_ssize_t res; TRACE_BEG2 (DEBUG_DATA, "gpgme_data_write", dh, "buffer=%p, size=%u", buffer, size); if (!dh) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } if (!dh->cbs->write) { gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } do res = (*dh->cbs->write) (dh, buffer, size); while (res < 0 && errno == EINTR); return TRACE_SYSRES (res); } /* Set the current position from where the next read or write starts in the data object with the handle DH to OFFSET, relativ to WHENCE. */ gpgme_off_t gpgme_data_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) { TRACE_BEG2 (DEBUG_DATA, "gpgme_data_seek", dh, "offset=%lli, whence=%i", offset, whence); if (!dh) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } if (!dh->cbs->seek) { gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } /* For relative movement, we must take into account the actual position of the read counter. */ if (whence == SEEK_CUR) offset -= dh->pending_len; offset = (*dh->cbs->seek) (dh, offset, whence); if (offset >= 0) dh->pending_len = 0; return TRACE_SYSRES (offset); } /* Convenience function to do a gpgme_data_seek (dh, 0, SEEK_SET). */ gpgme_error_t gpgme_data_rewind (gpgme_data_t dh) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme_data_rewind", dh); err = ((gpgme_data_seek (dh, 0, SEEK_SET) == -1) ? gpg_error_from_syserror () : 0); return TRACE_ERR (err); } /* Release the data object with the handle DH. */ void gpgme_data_release (gpgme_data_t dh) { TRACE (DEBUG_DATA, "gpgme_data_release", dh); if (!dh) return; if (dh->cbs->release) (*dh->cbs->release) (dh); _gpgme_data_release (dh); } /* Get the current encoding meta information for the data object with handle DH. */ gpgme_data_encoding_t gpgme_data_get_encoding (gpgme_data_t dh) { TRACE1 (DEBUG_DATA, "gpgme_data_get_encoding", dh, "dh->encoding=%i", dh ? dh->encoding : GPGME_DATA_ENCODING_NONE); return dh ? dh->encoding : GPGME_DATA_ENCODING_NONE; } /* Set the encoding meta information for the data object with handle DH to ENC. */ gpgme_error_t gpgme_data_set_encoding (gpgme_data_t dh, gpgme_data_encoding_t enc) { TRACE_BEG1 (DEBUG_DATA, "gpgme_data_set_encoding", dh, "encoding=%i", enc); if (!dh) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (enc < 0 || enc > GPGME_DATA_ENCODING_MIME) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); dh->encoding = enc; return TRACE_ERR (0); } /* Set the file name associated with the data object with handle DH to FILE_NAME. */ gpgme_error_t gpgme_data_set_file_name (gpgme_data_t dh, const char *file_name) { TRACE_BEG1 (DEBUG_DATA, "gpgme_data_set_file_name", dh, "file_name=%s", file_name); if (!dh) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (dh->file_name) free (dh->file_name); if (file_name) { dh->file_name = strdup (file_name); if (!dh->file_name) return TRACE_ERR (gpg_error_from_syserror ()); } else dh->file_name = 0; return TRACE_ERR (0); } /* Get the file name associated with the data object with handle DH, or NULL if there is none. */ char * gpgme_data_get_file_name (gpgme_data_t dh) { if (!dh) { TRACE (DEBUG_DATA, "gpgme_data_get_file_name", dh); return NULL; } TRACE1 (DEBUG_DATA, "gpgme_data_get_file_name", dh, "dh->file_name=%s", dh->file_name); return dh->file_name; } /* Set a flag for the data object DH. See the manual for details. */ gpg_error_t gpgme_data_set_flag (gpgme_data_t dh, const char *name, const char *value) { TRACE_BEG2 (DEBUG_DATA, "gpgme_data_set_flag", dh, "%s=%s", name, value); if (!dh) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (!strcmp (name, "size-hint")) { dh->size_hint= value? _gpgme_string_to_off (value) : 0; } else return gpg_error (GPG_ERR_UNKNOWN_NAME); return 0; } /* Functions to support the wait interface. */ gpgme_error_t _gpgme_data_inbound_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; gpgme_data_t dh = (gpgme_data_t) data->handler_value; char buffer[BUFFER_SIZE]; char *bufp = buffer; gpgme_ssize_t buflen; TRACE_BEG1 (DEBUG_CTX, "_gpgme_data_inbound_handler", dh, "fd=0x%x", fd); buflen = _gpgme_io_read (fd, buffer, BUFFER_SIZE); if (buflen < 0) return gpg_error_from_syserror (); if (buflen == 0) { _gpgme_io_close (fd); return TRACE_ERR (0); } do { gpgme_ssize_t amt = gpgme_data_write (dh, bufp, buflen); if (amt == 0 || (amt < 0 && errno != EINTR)) return TRACE_ERR (gpg_error_from_syserror ()); bufp += amt; buflen -= amt; } while (buflen > 0); return TRACE_ERR (0); } gpgme_error_t _gpgme_data_outbound_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; gpgme_data_t dh = (gpgme_data_t) data->handler_value; gpgme_ssize_t nwritten; TRACE_BEG1 (DEBUG_CTX, "_gpgme_data_outbound_handler", dh, "fd=0x%x", fd); if (!dh->pending_len) { gpgme_ssize_t amt = gpgme_data_read (dh, dh->pending, BUFFER_SIZE); if (amt < 0) return TRACE_ERR (gpg_error_from_syserror ()); if (amt == 0) { _gpgme_io_close (fd); return TRACE_ERR (0); } dh->pending_len = amt; } nwritten = _gpgme_io_write (fd, dh->pending, dh->pending_len); if (nwritten == -1 && errno == EAGAIN) return TRACE_ERR (0); if (nwritten == -1 && errno == EPIPE) { /* Not much we can do. The other end closed the pipe, but we still have data. This should only ever happen if the other end is going to tell us what happened on some other channel. Silently close our end. */ _gpgme_io_close (fd); return TRACE_ERR (0); } if (nwritten <= 0) return TRACE_ERR (gpg_error_from_syserror ()); if (nwritten < dh->pending_len) memmove (dh->pending, dh->pending + nwritten, dh->pending_len - nwritten); dh->pending_len -= nwritten; return TRACE_ERR (0); } /* Get the file descriptor associated with DH, if possible. Otherwise return -1. */ int _gpgme_data_get_fd (gpgme_data_t dh) { if (!dh || !dh->cbs->get_fd) return -1; return (*dh->cbs->get_fd) (dh); } /* Get the size-hint value for DH or 0 if not available. */ gpgme_off_t _gpgme_data_get_size_hint (gpgme_data_t dh) { return dh ? dh->size_hint : 0; } diff --git a/src/data.h b/src/data.h index f12508b9..692eb9a2 100644 --- a/src/data.h +++ b/src/data.h @@ -1,146 +1,170 @@ /* data.h - Internal data object abstraction interface. Copyright (C) 2002, 2004, 2005 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef DATA_H #define DATA_H #if HAVE_CONFIG_H #include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #include +#include #include "gpgme.h" /* Read up to SIZE bytes into buffer BUFFER from the data object with the handle DH. Return the number of characters read, 0 on EOF and -1 on error. If an error occurs, errno is set. */ typedef gpgme_ssize_t (*gpgme_data_read_cb) (gpgme_data_t dh, void *buffer, size_t size); /* Write up to SIZE bytes from buffer BUFFER to the data object with the handle DH. Return the number of characters written, or -1 on error. If an error occurs, errno is set. */ typedef gpgme_ssize_t (*gpgme_data_write_cb) (gpgme_data_t dh, const void *buffer, size_t size); /* Set the current position from where the next read or write starts in the data object with the handle DH to OFFSET, relativ to WHENCE. */ typedef gpgme_off_t (*gpgme_data_seek_cb) (gpgme_data_t dh, gpgme_off_t offset, int whence); /* Release the data object with the handle DH. */ typedef void (*gpgme_data_release_cb) (gpgme_data_t dh); /* Get the FD associated with the handle DH, or -1. */ typedef int (*gpgme_data_get_fd_cb) (gpgme_data_t dh); struct _gpgme_data_cbs { gpgme_data_read_cb read; gpgme_data_write_cb write; gpgme_data_seek_cb seek; gpgme_data_release_cb release; gpgme_data_get_fd_cb get_fd; }; struct gpgme_data { struct _gpgme_data_cbs *cbs; gpgme_data_encoding_t encoding; + unsigned int propidx; /* Index into the property table. */ #ifdef PIPE_BUF #define BUFFER_SIZE PIPE_BUF #else #ifdef _POSIX_PIPE_BUF #define BUFFER_SIZE _POSIX_PIPE_BUF #else #define BUFFER_SIZE 512 #endif #endif char pending[BUFFER_SIZE]; int pending_len; /* File name of the data object. */ char *file_name; - /* Hint on the to be expected toatl size of the data. */ + /* Hint on the to be expected total size of the data. */ gpgme_off_t size_hint; union { /* For gpgme_data_new_from_fd. */ int fd; /* For gpgme_data_new_from_stream. */ FILE *stream; /* For gpgme_data_new_from_estream. */ gpgrt_stream_t e_stream; /* For gpgme_data_new_from_cbs. */ struct { gpgme_data_cbs_t cbs; void *handle; } user; /* For gpgme_data_new_from_mem. */ struct { char *buffer; const char *orig_buffer; /* Allocated size of BUFFER. */ size_t size; size_t length; gpgme_off_t offset; } mem; /* For gpgme_data_new_from_read_cb. */ struct { int (*cb) (void *, char *, size_t, size_t *); void *handle; } old_user; } data; }; + +/* The data property types. */ +typedef enum + { + DATA_PROP_NONE = 0, /* Dummy property. */ + DATA_PROP_BLANKOUT /* Do not return the held data. */ + } data_prop_t; + + +/* Return the data object's serial number for handle DH. */ +uint64_t _gpgme_data_get_dserial (gpgme_data_t dh); + +/* Set an internal property of a data object. */ +gpg_error_t _gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int value); + +/* Get an internal property of a data object. */ +gpg_error_t _gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, + data_prop_t name, int *r_value); + +/* Create a new data object. */ gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs); void _gpgme_data_release (gpgme_data_t dh); /* Get the file descriptor associated with DH, if possible. Otherwise return -1. */ int _gpgme_data_get_fd (gpgme_data_t dh); /* Get the size-hint value for DH or 0 if not available. */ gpgme_off_t _gpgme_data_get_size_hint (gpgme_data_t dh); + #endif /* DATA_H */ diff --git a/src/decrypt-verify.c b/src/decrypt-verify.c index 1bd81c31..224edc10 100644 --- a/src/decrypt-verify.c +++ b/src/decrypt-verify.c @@ -1,183 +1,183 @@ /* decrypt-verify.c - Decrypt and verify function. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include "debug.h" #include "gpgme.h" #include "ops.h" static gpgme_error_t decrypt_verify_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_error_t err; err = _gpgme_progress_status_handler (priv, code, args); if (!err) err = _gpgme_decrypt_status_handler (priv, code, args); if (!err) err = _gpgme_verify_status_handler (priv, code, args); return err; } static gpgme_error_t decrypt_verify_start (gpgme_ctx_t ctx, int synchronous, gpgme_decrypt_flags_t flags, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; assert ((flags & GPGME_DECRYPT_VERIFY)); err = _gpgme_op_reset (ctx, synchronous); if (err) return err; - err = _gpgme_op_decrypt_init_result (ctx); + err = _gpgme_op_decrypt_init_result (ctx, plain); if (err) return err; err = _gpgme_op_verify_init_result (ctx); if (err) return err; if (!cipher) return gpg_error (GPG_ERR_NO_DATA); if (!plain) return gpg_error (GPG_ERR_INV_VALUE); if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } _gpgme_engine_set_status_handler (ctx->engine, decrypt_verify_status_handler, ctx); return _gpgme_engine_op_decrypt (ctx->engine, flags, cipher, plain, ctx->export_session_keys, ctx->override_session_key, ctx->auto_key_retrieve); } /* Decrypt ciphertext CIPHER and make a signature verification within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_decrypt_verify_start (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt_verify_start", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = decrypt_verify_start (ctx, 0, GPGME_DECRYPT_VERIFY, cipher, plain); return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER and make a signature verification within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_decrypt_verify (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt_verify", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = decrypt_verify_start (ctx, 1, GPGME_DECRYPT_VERIFY, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_decrypt_ext_start (gpgme_ctx_t ctx, gpgme_decrypt_flags_t flags, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt_ext_start", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if ((flags & GPGME_DECRYPT_VERIFY)) err = decrypt_verify_start (ctx, 0, flags, cipher, plain); else err = _gpgme_decrypt_start (ctx, 0, flags, cipher, plain); return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_decrypt_ext (gpgme_ctx_t ctx, gpgme_decrypt_flags_t flags, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt_ext", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if ((flags & GPGME_DECRYPT_VERIFY)) err = decrypt_verify_start (ctx, 1, flags, cipher, plain); else err = _gpgme_decrypt_start (ctx, 1, flags, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } diff --git a/src/decrypt.c b/src/decrypt.c index f5b93d7c..b51603a3 100644 --- a/src/decrypt.c +++ b/src/decrypt.c @@ -1,607 +1,619 @@ /* decrypt.c - Decrypt function. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2017 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "debug.h" #include "gpgme.h" #include "util.h" #include "context.h" #include "ops.h" - +#include "data.h" typedef struct { struct _gpgme_op_decrypt_result result; /* The error code from a FAILURE status line or 0. */ gpg_error_t failure_code; int okay; /* A flag telling that the a decryption failed and an optional error * code to further specify the failure. */ int failed; gpg_error_t pkdecrypt_failed; /* At least one secret key is not available. gpg issues NO_SECKEY * status lines for each key the message has been encrypted to but * that secret key is not available. This can't be done for hidden * recipients, though. We track it here to allow for a better error * message than the general DECRYPTION_FAILED. */ int any_no_seckey; /* If the engine emits a DECRYPTION_INFO status and that does not * indicate that an integrity protection mode is active, this flag * is set. */ int not_integrity_protected; /* The error code from the first ERROR line. This is in some cases * used to return a better matching error code to the caller. */ gpg_error_t first_status_error; /* A pointer to the next pointer of the last recipient in the list. This makes appending new invalid signers painless while preserving the order. */ gpgme_recipient_t *last_recipient_p; + + /* The data object serial number of the plaintext. */ + uint64_t plaintext_dserial; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; gpgme_recipient_t recipient = opd->result.recipients; free (opd->result.unsupported_algorithm); free (opd->result.file_name); free (opd->result.session_key); free (opd->result.symkey_algo); while (recipient) { gpgme_recipient_t next = recipient->next; free (recipient); recipient = next; } } gpgme_decrypt_result_t gpgme_op_decrypt_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_decrypt_result", ctx); ctx->ignore_mdc_error = 0; /* Always reset this flag. */ err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC0 ("result=(null)"); return NULL; } /* Make sure that SYMKEY_ALGO has a value. */ if (!opd->result.symkey_algo) { opd->result.symkey_algo = strdup ("?.?"); if (!opd->result.symkey_algo) { TRACE_SUC0 ("result=(null)"); return NULL; } } if (_gpgme_debug_trace ()) { gpgme_recipient_t rcp; if (opd->result.unsupported_algorithm) { TRACE_LOG1 ("result: unsupported_algorithm: %s", opd->result.unsupported_algorithm); } if (opd->result.wrong_key_usage) { TRACE_LOG ("result: wrong key usage"); } rcp = opd->result.recipients; while (rcp) { TRACE_LOG3 ("result: recipient: keyid=%s, pubkey_algo=%i, " "status=%s", rcp->keyid, rcp->pubkey_algo, gpg_strerror (rcp->status)); rcp = rcp->next; } if (opd->result.file_name) { TRACE_LOG1 ("result: original file name: %s", opd->result.file_name); } } TRACE_SUC1 ("result=%p", &opd->result); return &opd->result; } /* Parse the ARGS of an error status line and record some error * conditions at OPD. Returns 0 on success. */ static gpgme_error_t parse_status_error (char *args, op_data_t opd) { gpgme_error_t err; char *field[3]; int nfields; char *args2; if (!args) return trace_gpg_error (GPG_ERR_INV_ENGINE); args2 = strdup (args); /* Split modifies the input string. */ nfields = _gpgme_split_fields (args2, field, DIM (field)); if (nfields < 1) { free (args2); return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Required arg missing. */ } err = nfields < 2 ? 0 : atoi (field[1]); if (!strcmp (field[0], "decrypt.algorithm")) { if (gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM && nfields > 2 && strcmp (field[2], "?")) { opd->result.unsupported_algorithm = strdup (field[2]); if (!opd->result.unsupported_algorithm) { free (args2); return gpg_error_from_syserror (); } } } else if (!strcmp (field[0], "decrypt.keyusage")) { if (gpg_err_code (err) == GPG_ERR_WRONG_KEY_USAGE) opd->result.wrong_key_usage = 1; } else if (!strcmp (field[0], "pkdecrypt_failed")) { switch (gpg_err_code (err)) { case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: /* It is better to return with a cancel error code than the * general decryption failed error code. */ opd->pkdecrypt_failed = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED); break; case GPG_ERR_BAD_PASSPHRASE: /* A bad passphrase is severe enough that we return this * error code. */ opd->pkdecrypt_failed = err; break; default: /* For now all other error codes are ignored and the * standard DECRYPT_FAILED is returned. */ break; } } else if (!strcmp (field[0], "nomdc_with_legacy_cipher")) { opd->result.legacy_cipher_nomdc = 1; opd->not_integrity_protected = 1; } /* Record the first error code. */ if (err && !opd->first_status_error) opd->first_status_error = err; free (args2); return 0; } static gpgme_error_t parse_enc_to (char *args, gpgme_recipient_t *recp, gpgme_protocol_t protocol) { gpgme_recipient_t rec; char *tail; int i; rec = malloc (sizeof (*rec)); if (!rec) return gpg_error_from_syserror (); rec->next = NULL; rec->keyid = rec->_keyid; rec->status = 0; for (i = 0; i < sizeof (rec->_keyid) - 1; i++) { if (args[i] == '\0' || args[i] == ' ') break; rec->_keyid[i] = args[i]; } rec->_keyid[i] = '\0'; args = &args[i]; if (*args != '\0' && *args != ' ') { free (rec); return trace_gpg_error (GPG_ERR_INV_ENGINE); } while (*args == ' ') args++; if (*args) { gpg_err_set_errno (0); rec->pubkey_algo = _gpgme_map_pk_algo (strtol (args, &tail, 0), protocol); if (errno || args == tail || *tail != ' ') { /* The crypto backend does not behave. */ free (rec); return trace_gpg_error (GPG_ERR_INV_ENGINE); } } /* FIXME: The key length is always 0 right now, so no need to parse it. */ *recp = rec; return 0; } /* Parse the ARGS of a * DECRYPTION_INFO [] * status. Returns 0 on success and updates the OPD. */ static gpgme_error_t parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol) { char *field[3]; int nfields; char *args2; int mdc, aead_algo; const char *algostr, *modestr; if (!args) return trace_gpg_error (GPG_ERR_INV_ENGINE); args2 = strdup (args); /* Split modifies the input string. */ nfields = _gpgme_split_fields (args2, field, DIM (field)); if (nfields < 2) { free (args2); return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Required arg missing. */ } mdc = atoi (field[0]); algostr = _gpgme_cipher_algo_name (atoi (field[1]), protocol); aead_algo = nfields < 3? 0 : atoi (field[2]); modestr = _gpgme_cipher_mode_name (aead_algo, protocol); free (args2); free (opd->result.symkey_algo); if (!aead_algo && mdc != 2) opd->result.symkey_algo = _gpgme_strconcat (algostr, ".PGPCFB", NULL); else opd->result.symkey_algo = _gpgme_strconcat (algostr, ".", modestr, NULL); if (!opd->result.symkey_algo) return gpg_error_from_syserror (); if (!mdc && !aead_algo) opd->not_integrity_protected = 1; return 0; } gpgme_error_t _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_ctx_t ctx = (gpgme_ctx_t) priv; gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_passphrase_status_handler (priv, code, args); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL); opd = hook; if (err) return err; switch (code) { case GPGME_STATUS_FAILURE: opd->failure_code = _gpgme_parse_failure (args); break; case GPGME_STATUS_EOF: /* We force an encryption failure if we know that integrity * protection is missing. For modern version of gpg using * modern cipher algorithms this is not required because gpg * will issue a failure anyway. However older gpg versions emit * only a warning. * Fixme: These error values should probably be attributed to * the underlying crypto engine (as error source). */ if (opd->failed) { /* This comes from a specialized ERROR status line. */ if (opd->pkdecrypt_failed) return opd->pkdecrypt_failed; /* For an integrity failure return just DECRYPTION_FAILED; * the actual cause can be taken from an already set * decryption result flag. */ if ((opd->not_integrity_protected && !ctx->ignore_mdc_error)) return gpg_error (GPG_ERR_DECRYPT_FAILED); /* If we have any other ERROR code we prefer that over * NO_SECKEY because it is probably the better matching * code. For example a garbled message with multiple * plaintext will return BAD_DATA here but may also have * indicated a NO_SECKEY. */ if (opd->first_status_error) return opd->first_status_error; /* No secret key is pretty common reason. */ if (opd->any_no_seckey) return gpg_error (GPG_ERR_NO_SECKEY); /* Generic decryption failed error code. */ return gpg_error (GPG_ERR_DECRYPT_FAILED); } else if (!opd->okay) { /* No data was found. */ return gpg_error (GPG_ERR_NO_DATA); } else if (opd->failure_code) { /* The engine returned failure code at program exit. */ return opd->failure_code; } break; case GPGME_STATUS_DECRYPTION_INFO: err = parse_decryption_info (args, opd, ctx->protocol); if (err) return err; break; case GPGME_STATUS_DECRYPTION_OKAY: opd->okay = 1; break; case GPGME_STATUS_DECRYPTION_FAILED: opd->failed = 1; + /* Tell the data object that it shall not return any data. We + * use the serial number because the data object may be owned by + * another thread. We also don't check for an error because it + * is possible that the data object has already been destroyed + * and we are then not interested in returning an error. */ + if (!ctx->ignore_mdc_error) + _gpgme_data_set_prop (NULL, opd->plaintext_dserial, + DATA_PROP_BLANKOUT, 1); break; case GPGME_STATUS_ERROR: /* Note that this is an informational status code which should * not lead to an error return unless it is something not * related to the backend. However, it is used to return a * better matching final error code. */ err = parse_status_error (args, opd); if (err) return err; break; case GPGME_STATUS_ENC_TO: err = parse_enc_to (args, opd->last_recipient_p, ctx->protocol); if (err) return err; opd->last_recipient_p = &(*opd->last_recipient_p)->next; break; case GPGME_STATUS_SESSION_KEY: if (opd->result.session_key) free (opd->result.session_key); opd->result.session_key = strdup(args); break; case GPGME_STATUS_NO_SECKEY: { gpgme_recipient_t rec = opd->result.recipients; while (rec) { if (!strcmp (rec->keyid, args)) { rec->status = gpg_error (GPG_ERR_NO_SECKEY); break; } rec = rec->next; } /* FIXME: Is this ok? */ if (!rec) return trace_gpg_error (GPG_ERR_INV_ENGINE); opd->any_no_seckey = 1; } break; case GPGME_STATUS_PLAINTEXT: { int mime = 0; err = _gpgme_parse_plaintext (args, &opd->result.file_name, &mime); if (err) return err; opd->result.is_mime = !!mime; } break; case GPGME_STATUS_INQUIRE_MAXLEN: if (ctx->status_cb && !ctx->full_status) { err = ctx->status_cb (ctx->status_cb_value, "INQUIRE_MAXLEN", args); if (err) return err; } break; case GPGME_STATUS_DECRYPTION_COMPLIANCE_MODE: PARSE_COMPLIANCE_FLAGS (args, &opd->result); break; default: break; } return 0; } static gpgme_error_t decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_error_t err; err = _gpgme_progress_status_handler (priv, code, args); if (!err) err = _gpgme_decrypt_status_handler (priv, code, args); return err; } gpgme_error_t -_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx) +_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, gpgme_data_t plaintext) { gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; opd->last_recipient_p = &opd->result.recipients; + opd->plaintext_dserial = _gpgme_data_get_dserial (plaintext); return 0; } gpgme_error_t _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, gpgme_decrypt_flags_t flags, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; assert (!(flags & GPGME_DECRYPT_VERIFY)); err = _gpgme_op_reset (ctx, synchronous); if (err) return err; - err = _gpgme_op_decrypt_init_result (ctx); + err = _gpgme_op_decrypt_init_result (ctx, plain); if (err) return err; if (!cipher) return gpg_error (GPG_ERR_NO_DATA); if (!plain) return gpg_error (GPG_ERR_INV_VALUE); if (err) return err; if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } _gpgme_engine_set_status_handler (ctx->engine, decrypt_status_handler, ctx); return _gpgme_engine_op_decrypt (ctx->engine, flags, cipher, plain, ctx->export_session_keys, ctx->override_session_key, ctx->auto_key_retrieve); } gpgme_error_t gpgme_op_decrypt_start (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt_start", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_decrypt_start (ctx, 0, 0, cipher, plain); return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_decrypt_start (ctx, 1, 0, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); ctx->ignore_mdc_error = 0; /* Always reset. */ return TRACE_ERR (err); } diff --git a/src/ops.h b/src/ops.h index 5955454c..3b9728dd 100644 --- a/src/ops.h +++ b/src/ops.h @@ -1,194 +1,195 @@ /* ops.h - Internal operation support. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef OPS_H #define OPS_H #include "gpgme.h" #include "context.h" /* From gpgme.c. */ gpgme_error_t _gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err, gpg_error_t op_err); /* Clear all notation data from the context. */ void _gpgme_sig_notation_clear (gpgme_ctx_t ctx); void _gpgme_release_result (gpgme_ctx_t ctx); /* From wait.c. */ gpgme_error_t _gpgme_wait_one (gpgme_ctx_t ctx); gpgme_error_t _gpgme_wait_one_ext (gpgme_ctx_t ctx, gpgme_error_t *op_err); gpgme_error_t _gpgme_wait_on_condition (gpgme_ctx_t ctx, volatile int *cond, gpgme_error_t *op_err); /* From data.c. */ gpgme_error_t _gpgme_data_inbound_handler (void *opaque, int fd); gpgme_error_t _gpgme_data_outbound_handler (void *opaque, int fd); /* From op-support.c. */ /* Find or create the op data object of type TYPE. */ gpgme_error_t _gpgme_op_data_lookup (gpgme_ctx_t ctx, ctx_op_data_id_t type, void **hook, int size, void (*cleanup) (void *)); /* Prepare a new operation on CTX. */ gpgme_error_t _gpgme_op_reset (gpgme_ctx_t ctx, int synchronous); /* Parse the KEY_CONSIDERED status line. */ gpgme_error_t _gpgme_parse_key_considered (const char *args, char **r_fpr, unsigned int *r_flags); /* Parse the INV_RECP status line in ARGS and return the result in KEY. */ gpgme_error_t _gpgme_parse_inv_recp (char *args, int for_signing, const char *kc_fpr, unsigned int kc_flags, gpgme_invalid_key_t *key); /* Parse the PLAINTEXT status line in ARGS and return the result in FILENAMEP and R_MIME. */ gpgme_error_t _gpgme_parse_plaintext (char *args, char **filenamep,int *r_mime); /* Parse a FAILURE status line and return the error code. ARGS is modified to contain the location part. */ gpgme_error_t _gpgme_parse_failure (char *args); /* From verify.c. */ gpgme_error_t _gpgme_op_verify_init_result (gpgme_ctx_t ctx); gpgme_error_t _gpgme_verify_status_handler (void *priv, gpgme_status_code_t code, char *args); /* From decrypt.c. */ -gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx); +gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, + gpgme_data_t plaintext); gpgme_error_t _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args); gpgme_error_t _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, gpgme_decrypt_flags_t flags, gpgme_data_t cipher, gpgme_data_t plain); /* From signers.c. */ void _gpgme_signers_clear (gpgme_ctx_t ctx); /* From sign.c. */ /* Create an initial op data object for signing. Needs to be called once before calling _gpgme_sign_status_handler. */ gpgme_error_t _gpgme_op_sign_init_result (gpgme_ctx_t ctx); /* Process a status line for signing operations. */ gpgme_error_t _gpgme_sign_status_handler (void *priv, gpgme_status_code_t code, char *args); /* From encrypt.c. */ /* Create an initial op data object for encrypt. Needs to be called once before calling _gpgme_encrypt_status_handler. */ gpgme_error_t _gpgme_op_encrypt_init_result (gpgme_ctx_t ctx); /* Process a status line for encryption operations. */ gpgme_error_t _gpgme_encrypt_status_handler (void *priv, gpgme_status_code_t code, char *args); /* From passphrase.c. */ gpgme_error_t _gpgme_passphrase_status_handler (void *priv, gpgme_status_code_t code, char *args); gpgme_error_t _gpgme_passphrase_command_handler (void *opaque, gpgme_status_code_t code, const char *key, int fd, int *processed); /* From progress.c. */ gpgme_error_t _gpgme_progress_status_handler (void *priv, gpgme_status_code_t code, char *args); /* From key.c. */ gpgme_error_t _gpgme_key_new (gpgme_key_t *r_key); gpgme_error_t _gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey); gpgme_error_t _gpgme_key_append_name (gpgme_key_t key, const char *src, int convert); gpgme_key_sig_t _gpgme_key_add_sig (gpgme_key_t key, char *src); /* From keylist.c. */ void _gpgme_op_keylist_event_cb (void *data, gpgme_event_io_t type, void *type_data); /* From trust-item.c. */ /* Create a new trust item. */ gpgme_error_t _gpgme_trust_item_new (gpgme_trust_item_t *r_item); /* From trustlist.c. */ void _gpgme_op_trustlist_event_cb (void *data, gpgme_event_io_t type, void *type_data); /* From version.c. */ /* Return true if MY_VERSION is at least REQ_VERSION, and false otherwise. */ int _gpgme_compare_versions (const char *my_version, const char *req_version); char *_gpgme_get_program_version (const char *const path); /* From sig-notation.c. */ /* Create a new, empty signature notation data object. */ gpgme_error_t _gpgme_sig_notation_create (gpgme_sig_notation_t *notationp, const char *name, int name_len, const char *value, int value_len, gpgme_sig_notation_flags_t flags); /* Free the signature notation object and all associated resources. The object must already be removed from any linked list as the next pointer is ignored. */ void _gpgme_sig_notation_free (gpgme_sig_notation_t notation); /* Parse a notation or policy URL subpacket. If the packet type is not known, return no error but NULL in NOTATION. */ gpgme_error_t _gpgme_parse_notation (gpgme_sig_notation_t *notationp, int type, int pkflags, int len, char *data); #endif /* OPS_H */