diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index d2479e381..1e46108f0 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -1,182 +1,78 @@ # assuan configure checks include(CheckFunctionExists) if ( ASSUAN2_FOUND ) - set ( ASSUAN_SUFFIX "2" ) else ( ASSUAN2_FOUND ) # TODO Clean this up with assuan 2 as hard dependency. message(FATAL_ERROR "At least version 2 of libassuan is required for Kleopatra.") endif ( ASSUAN2_FOUND ) set( USABLE_ASSUAN_FOUND false ) -if ( ASSUAN${ASSUAN_SUFFIX}_FOUND ) - set( CMAKE_REQUIRED_INCLUDES ${ASSUAN${ASSUAN_SUFFIX}_INCLUDES} ) + set( CMAKE_REQUIRED_INCLUDES ${ASSUAN2_INCLUDES} ) - if ( ASSUAN2_FOUND ) set( CMAKE_REQUIRED_LIBRARIES ${ASSUAN2_LIBRARIES} ) set( USABLE_ASSUAN_FOUND true ) - elseif ( WIN32 AND ASSUAN_VANILLA_FOUND ) - set( CMAKE_REQUIRED_LIBRARIES ${ASSUAN_VANILLA_LIBRARIES} ) - set( USABLE_ASSUAN_FOUND true ) - elseif( NOT WIN32 AND ASSUAN_PTHREAD_FOUND ) - set( CMAKE_REQUIRED_LIBRARIES ${ASSUAN_PTHREAD_LIBRARIES} ) - set( USABLE_ASSUAN_FOUND true ) - endif( ASSUAN2_FOUND ) # TODO: this workaround will be removed as soon as we find better solution if(MINGW) set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${KDEWIN32_INCLUDE_DIR}/mingw) elseif(MSVC) set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${KDEWIN32_INCLUDE_DIR}/msvc) endif(MINGW) -endif( ASSUAN${ASSUAN_SUFFIX}_FOUND ) - if ( USABLE_ASSUAN_FOUND ) # check if assuan.h can be compiled standalone (it couldn't, on # Windows, until recently, because of a HAVE_W32_SYSTEM #ifdef in # there) check_cxx_source_compiles( " #include int main() { return 1; } " USABLE_ASSUAN_FOUND ) endif( USABLE_ASSUAN_FOUND ) if ( USABLE_ASSUAN_FOUND ) # check whether assuan and gpgme may be linked to simultaneously check_function_exists( "assuan_get_pointer" USABLE_ASSUAN_FOUND ) endif( USABLE_ASSUAN_FOUND ) -if ( USABLE_ASSUAN_FOUND AND NOT ASSUAN2_FOUND ) - - # check if assuan has assuan_fd_t - check_cxx_source_compiles(" - #include - int main() { - assuan_fd_t fd = ASSUAN_INVALID_FD; - return fd ? 1 : 0 ; - } - " - HAVE_ASSUAN_FD_T ) - - if ( WIN32 AND NOT HAVE_ASSUAN_FD_T ) - set( USABLE_ASSUAN_FOUND false ) - endif ( WIN32 AND NOT HAVE_ASSUAN_FD_T ) - - # check if assuan has assuan_inquire_ext, old style - check_function_exists( "assuan_inquire_ext" HAVE_ASSUAN_INQUIRE_EXT ) - - if ( NOT HAVE_ASSUAN_INQUIRE_EXT ) - set( USABLE_ASSUAN_FOUND false ) - endif( NOT HAVE_ASSUAN_INQUIRE_EXT ) - - # check if assuan has new-style assuan_inquire_ext: - check_cxx_source_compiles(" - #include - static int handler( void *, int, unsigned char*, size_t ) { return 0; } - int main() { - assuan_context_t ctx = 0; - const size_t maxSize = 0U; - assuan_error_t err = assuan_inquire_ext( ctx, \"FOO\", maxSize, handler, (void*)0 ); - return err ? 1 : 0 ; - } - " - HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT ) - -endif( USABLE_ASSUAN_FOUND AND NOT ASSUAN2_FOUND ) - if ( USABLE_ASSUAN_FOUND ) # check if gpg-error already has GPG_ERR_SOURCE_KLEO check_cxx_source_compiles(" #include static gpg_err_source_t src = GPG_ERR_SOURCE_KLEO; int main() { return 0; } " HAVE_GPG_ERR_SOURCE_KLEO ) endif ( USABLE_ASSUAN_FOUND ) -if ( USABLE_ASSUAN_FOUND AND NOT ASSUAN2_FOUND ) - - # check if assuan has assuan_sock_get_nonce (via assuan_sock_nonce_t) - # function_exists runs into linking errors - libassuan is static, - # and assuan_sock_get_nonce drags in stuff that needs linking - # against winsock2. - check_cxx_source_compiles(" - #include - static assuan_sock_nonce_t nonce; - int main() { return 0; } - " - HAVE_ASSUAN_SOCK_GET_NONCE ) - - if ( WIN32 AND NOT HAVE_ASSUAN_SOCK_GET_NONCE ) - set( USABLE_ASSUAN_FOUND false ) - endif ( WIN32 AND NOT HAVE_ASSUAN_SOCK_GET_NONCE ) - -endif ( USABLE_ASSUAN_FOUND AND NOT ASSUAN2_FOUND ) - if ( USABLE_ASSUAN_FOUND ) message( STATUS "Usable assuan found for Kleopatra" ) else ( USABLE_ASSUAN_FOUND ) message( STATUS "NO usable assuan found for Kleopatra" ) endif ( USABLE_ASSUAN_FOUND ) -if ( NOT ASSUAN2_FOUND ) - -# -# Check that libassuan (which is built statically) can be linked into a DSO -# (e.g. on amd64, this requires it to be compiled with -fPIC). -# - -set ( ASSUAN_LINKABLE_TO_DSO false ) - -endif ( NOT ASSUAN2_FOUND ) - OPTION( BUILD_libkleopatraclient "Build directory kleopatra/libkleopatraclient" ${USABLE_ASSUAN_FOUND} ) if ( NOT USABLE_ASSUAN_FOUND ) set( BUILD_libkleopatraclient false ) endif ( NOT USABLE_ASSUAN_FOUND ) -if ( BUILD_libkleopatraclient AND NOT ASSUAN2_FOUND ) - - message( STATUS "Checking whether libassuan can be linked against from DSO's" ) - - set ( YUP TRUE ) - if ( YUP ) - set ( ASSUAN_LINKABLE_TO_DSO true ) - message( STATUS "--> Assuming that it can. If compilation of libkleopatraclient fails on AMD64, check that libassuan is compiled with -fPIC and try again. Otherwise, pass -DBUILD_libkleopatraclient=OFF." ) - else ( YUP ) - # TODO: make this one executed at configure time, so the check below works: - add_library( dso_with_assuan_check SHARED ${CMAKE_SOURCE_DIR}/kleopatra/dso_with_assuan_check.c ) - - set( CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dso_with_assuan_check ) - check_cxx_source_compiles( "int main() { return 0; }" ASSUAN_LINKABLE_TO_DSO ) - - if ( ASSUAN_LINKABLE_TO_DSO ) - message( STATUS "Usable assuan found for libkleopatraclient" ) - else ( ASSUAN_LINKABLE_TO_DSO ) - message( STATUS "NO usable assuan found for libkleopatraclient - if this is AMD64, check that libassuan is compiled with -fPIC" ) - endif ( ASSUAN_LINKABLE_TO_DSO ) - endif ( YUP ) - -endif ( BUILD_libkleopatraclient AND NOT ASSUAN2_FOUND ) - if (USABLE_ASSUAN_FOUND) set (HAVE_USABLE_ASSUAN 1) set (HAVE_KLEOPATRACLIENT_LIBRARY 1) else() set (HAVE_USABLE_ASSUAN 0) set (HAVE_KLEOPATRACLIENT_LIBRARY 0) endif() set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_REQUIRED_LIBRARIES) diff --git a/cmake/modules/FindAssuan2.cmake b/cmake/modules/FindAssuan2.cmake index 176dc142d..2a46fbe19 100644 --- a/cmake/modules/FindAssuan2.cmake +++ b/cmake/modules/FindAssuan2.cmake @@ -1,251 +1,239 @@ # - Try :to find the assuan v2 library # Variables set: # ASSUAN2_{INCLUDES,FOUND,LIBRARIES} will be set for each of the above #if this is built-in, please replace, if it isn't, export into a MacroToBool.cmake of it's own macro( macro_bool_to_bool FOUND_VAR ) foreach( _current_VAR ${ARGN} ) if ( ${FOUND_VAR} ) set( ${_current_VAR} TRUE ) else() set( ${_current_VAR} FALSE ) endif() endforeach() endmacro() if ( WIN32 ) # On Windows, we don't have a libassuan-config script, so we need to # look for the stuff ourselves: # in cmake, AND and OR have the same precedence, there's no # subexpressions, and expressions are evaluated short-circuit'ed # IOW: CMake if() suxx. set( _seem_to_have_cached_assuan2 false ) if ( ASSUAN2_INCLUDES ) if ( ASSUAN2_VANILLA_LIBRARIES )#OR ASSUAN2_QT_LIBRARIES OR ASSUAN2_GLIB_LIBRARIES ) set( _seem_to_have_cached_assuan2 true ) endif() endif() if ( _seem_to_have_cached_assuan2 ) macro_bool_to_bool( ASSUAN2_VANILLA_LIBRARIES ASSUAN2_VANILLA_FOUND ) # this would have been preferred: #set( ASSUAN2_*_FOUND macro_bool_to_bool(ASSUAN2_*_LIBRARIES) ) if ( ASSUAN2_VANILLA_FOUND ) #OR ASSUAN2_GLIB_FOUND OR ASSUAN2_QT_FOUND ) set( ASSUAN2_FOUND true ) else() set( ASSUAN2_FOUND false ) endif() else() set( ASSUAN2_FOUND false ) set( ASSUAN2_VANILLA_FOUND false ) #set( ASSUAN2_GLIB_FOUND false ) #set( ASSUAN2_QT_FOUND false ) find_path( ASSUAN2_INCLUDES assuan.h ${CMAKE_INCLUDE_PATH} ${CMAKE_INSTALL_PREFIX}/include ) find_library( _assuan2_library NAMES assuan2 libassuan2 assuan libassuan assuan-0 libassuan-0 #sic! PATHS ${CMAKE_LIBRARY_PATH} ${CMAKE_INSTALL_PREFIX}/lib ) find_library( _gpg_error_library NAMES gpg-error libgpg-error gpg-error-0 libgpg-error-0 PATHS ${CMAKE_LIBRARY_PATH} ${CMAKE_INSTALL_PREFIX}/lib ) set( ASSUAN2_INCLUDES ${ASSUAN2_INCLUDES} ) if ( _assuan2_library AND _gpg_error_library ) set( ASSUAN2_LIBRARIES ${_assuan2_library} ${_gpg_error_library} ws2_32 ) set( ASSUAN2_FOUND true ) endif() endif() - if (ASSUAN2_FOUND) - set (HAVE_ASSUAN2 1) - else() - set (HAVE_ASSUAN2 0) - endif() - else() # not WIN32 # On *nix, we have the libassuan-config script which can tell us all we # need to know: # see WIN32 case for an explanation of what this does: set( _seem_to_have_cached_assuan2 false ) if ( ASSUAN2_INCLUDES AND ASSUAN2_LIBRARIES ) set( _seem_to_have_cached_assuan2 true ) endif() if ( _seem_to_have_cached_assuan2 ) set( ASSUAN2_FOUND true ) else() set( ASSUAN2_FOUND false ) find_program( _ASSUAN2CONFIG_EXECUTABLE NAMES libassuan-config ) # if libassuan-config has been found if ( _ASSUAN2CONFIG_EXECUTABLE ) message( STATUS "Found libassuan-config at ${_ASSUAN2CONFIG_EXECUTABLE}" ) exec_program( ${_ASSUAN2CONFIG_EXECUTABLE} ARGS --version OUTPUT_VARIABLE ASSUAN2_VERSION ) set( _ASSUAN2_MIN_VERSION "2.0.0" ) if( ASSUAN2_VERSION VERSION_GREATER ${_ASSUAN2_MIN_VERSION} ) set( _ASSUAN2_INSTALLED_VERSION_OK TRUE ) endif() if ( NOT _ASSUAN2_INSTALLED_VERSION_OK ) message( STATUS "The installed version of assuan is too old: ${ASSUAN2_VERSION} (required: >= ${_ASSUAN2_MIN_VERSION})" ) else() message( STATUS "Found assuan v${ASSUAN2_VERSION}" ) exec_program( ${_ASSUAN2CONFIG_EXECUTABLE} ARGS --libs OUTPUT_VARIABLE _assuan2_config_libs RETURN_VALUE _ret ) if ( _ret ) set( _assuan2_config_libs ) endif() # append -lgpg-error to the list of libraries, if necessary if ( _assuan2_config_libs AND NOT _assuan2_config_libs MATCHES "lgpg-error" ) set( _assuan2_config_libs "${_assuan2_config_libs} -lgpg-error" ) endif() if ( _assuan2_config_libs ) exec_program( ${_ASSUAN2CONFIG_EXECUTABLE} ARGS --cflags OUTPUT_VARIABLE _ASSUAN2_CFLAGS ) if ( _ASSUAN2_CFLAGS ) string( REGEX REPLACE "(\r?\n)+$" " " _ASSUAN2_CFLAGS "${_ASSUAN2_CFLAGS}" ) string( REGEX REPLACE " *-I" ";" ASSUAN2_INCLUDES "${_ASSUAN2_CFLAGS}" ) endif() if ( _assuan2_config_libs ) set( _assuan2_library_dirs ) set( _assuan2_library_names ) string( REGEX REPLACE " +" ";" _assuan2_config_libs "${_assuan2_config_libs}" ) foreach( _flag ${_assuan2_config_libs} ) if ( "${_flag}" MATCHES "^-L" ) string( REGEX REPLACE "^-L" "" _dir "${_flag}" ) file( TO_CMAKE_PATH "${_dir}" _dir ) set( _assuan2_library_dirs ${_assuan2_library_dirs} "${_dir}" ) elseif( "${_flag}" MATCHES "^-l" ) string( REGEX REPLACE "^-l" "" _name "${_flag}" ) set( _assuan2_library_names ${_assuan2_library_names} "${_name}" ) endif() endforeach() set( ASSUAN2_FOUND true ) foreach( _name ${_assuan2_library_names} ) set( _assuan2_${_name}_lib ) # if -L options were given, look only there if ( _assuan2_library_dirs ) find_library( _assuan2_${_name}_lib NAMES ${_name} PATHS ${_assuan2_library_dirs} NO_DEFAULT_PATH ) endif() # if not found there, look in system directories if ( NOT _assuan2_${_name}_lib ) find_library( _assuan2_${_name}_lib NAMES ${_name} ) endif() # if still not found, then the whole flavour isn't found if ( NOT _assuan2_${_name}_lib ) if ( ASSUAN2_FOUND ) set( ASSUAN2_FOUND false ) set( _not_found_reason "dependent library ${_name} wasn't found" ) endif() endif() set( ASSUAN2_LIBRARIES ${ASSUAN2_LIBRARIES} "${_assuan2_${_name}_lib}" ) endforeach() #check_c_library_exists_explicit( assuan assuan_check_version "${_ASSUAN2_CFLAGS}" "${ASSUAN2_LIBRARIES}" ASSUAN2_FOUND ) if ( ASSUAN2_FOUND ) message( STATUS " Checking whether assuan is usable...yes" ) else() message( STATUS " Checking whether assuan is usable...no" ) message( STATUS " (${_not_found_reason})" ) endif() endif() # ensure that they are cached set( ASSUAN2_INCLUDES ${ASSUAN2_INCLUDES} ) set( ASSUAN2_LIBRARIES ${ASSUAN2_LIBRARIES} ) endif() endif() endif() endif() - if (ASSUAN2_FOUND) - set (HAVE_ASSUAN2 1) - else() - set (HAVE_ASSUAN2 0) - endif() - endif() # WIN32 | Unix if ( NOT Assuan2_FIND_QUIETLY ) if ( ASSUAN2_FOUND ) message( STATUS "Usable assuan found." ) message( STATUS " Includes: ${ASSUAN2_INCLUDES}" ) message( STATUS " Libraries: ${ASSUAN2_LIBRARIES}" ) else() message( STATUS "No usable assuan found." ) endif() if( Assuan2_FIND_REQUIRED ) set( _ASSUAN2_TYPE "REQUIRED" ) else() set( _ASSUAN2_TYPE "OPTIONAL" ) endif() if ( WIN32 ) set( _assuan2_homepage "https://www.gpg4win.org" ) else() set( _assuan2_homepage "https://www.gnupg.org/related_software/libassuan" ) endif() set_package_properties(ASSUAN2 PROPERTIES DESCRIPTION "Assuan v2 IPC library" URL ${_assuan2_homepage} TYPE ${_ASSUAN2_TYPE} PURPOSE "Needed for Kleopatra to act as the GnuPG UI Server" ) else() if ( Assuan2_FIND_REQUIRED AND NOT ASSUAN2_FOUND ) message( FATAL_ERROR "Assuan2 is required but was not found." ) endif() endif() diff --git a/config-kleopatra.h.cmake b/config-kleopatra.h.cmake index be13e2cbc..e2439bc32 100644 --- a/config-kleopatra.h.cmake +++ b/config-kleopatra.h.cmake @@ -1,64 +1,47 @@ /* Define to 1 if you have a recent enough libassuan */ #cmakedefine HAVE_USABLE_ASSUAN 1 -/* Define to 1 if you have libassuan v2 */ -#cmakedefine HAVE_ASSUAN2 1 - -#ifndef HAVE_ASSUAN2 -/* Define to 1 if your libassuan has the assuan_fd_t type */ -#cmakedefine HAVE_ASSUAN_FD_T 1 - -/* Define to 1 if your libassuan has the assuan_inquire_ext function */ -#cmakedefine HAVE_ASSUAN_INQUIRE_EXT 1 - -/* Define to 1 if your assuan_inquire_ext puts the buffer arguments into the callback signature */ -#cmakedefine HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT 1 - -/* Define to 1 if your libassuan has the assuan_sock_get_nonce function */ -#cmakedefine HAVE_ASSUAN_SOCK_GET_NONCE 1 - -#endif /* Define to 1 if you build libkleopatraclient */ #cmakedefine HAVE_KLEOPATRACLIENT_LIBRARY 1 /* DBus available */ #cmakedefine01 HAVE_QDBUS /* Defined if QGpgME supports changing the expiration date of the primary key and the subkeys simultaneously */ #cmakedefine QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY 1 /* Defined if QGpgME supports retrieving the default value of a config entry */ #cmakedefine QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE 1 /* Defined if QGpgME supports WKD lookup */ #cmakedefine QGPGME_SUPPORTS_WKDLOOKUP 1 /* Defined if QGpgME supports specifying an import filter when importing keys */ #cmakedefine QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1 /* Defined if QGpgME supports setting key origin when importing keys */ #cmakedefine QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1 /* Defined if QGpgME supports the export of secret keys */ #cmakedefine QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1 /* Defined if QGpgME supports the export of secret subkeys */ #cmakedefine QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1 /* Defined if QGpgME supports receiving keys by their key ids */ #cmakedefine QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID 1 /* Defined if QGpgME supports revoking own OpenPGP keys */ #cmakedefine QGPGME_SUPPORTS_KEY_REVOCATION 1 /* Defined if QGpgME supports refreshing keys */ #cmakedefine QGPGME_SUPPORTS_KEY_REFRESH 1 /* Defined if QGpgME supports setting the file name of encrypted data */ #cmakedefine QGPGME_SUPPORTS_SET_FILENAME 1 /* Defined if QGpgME supports setting the primary user id of a key */ #cmakedefine QGPGME_SUPPORTS_SET_PRIMARY_UID 1 /* Defined if GpgME++ supports setting the curve when generating ECC card keys */ #cmakedefine GPGMEPP_SUPPORTS_SET_CURVE 1 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d67e090d7..2cb83ad3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,621 +1,612 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(icons) add_subdirectory(mimetypes) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS selftest/registrycheck.cpp utils/gnupg-registry.c utils/userinfo_win.cpp utils/windowsprocessdevice.cpp ) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS ${_kleopatra_extra_uiserver_SRCS} selftest/uiservercheck.cpp uiserver/assuanserverconnection.cpp uiserver/createchecksumscommand.cpp uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandfilesbase.cpp uiserver/echocommand.cpp uiserver/encryptcommand.cpp uiserver/importfilescommand.cpp uiserver/prepencryptcommand.cpp uiserver/prepsigncommand.cpp uiserver/selectcertificatecommand.cpp uiserver/sessiondata.cpp uiserver/signcommand.cpp uiserver/signencryptfilescommand.cpp uiserver/uiserver.cpp uiserver/verifychecksumscommand.cpp ) -if(ASSUAN2_FOUND) include_directories(${ASSUAN2_INCLUDES}) set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES}) -else() - include_directories(${ASSUAN_INCLUDES}) - if(WIN32) - set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES}) - else() - set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES}) - endif() -endif() if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() if(KF5IdentityManagement_FOUND AND KF5MailTransport_FOUND AND KF5MailTransportAkonadi_FOUND) set(_kleopatra_mail_libs KF5::IdentityManagement # Export OpenPGP keys using WKS KF5::MailTransport KF5::MailTransportAkonadi ) add_definitions(-DMAILAKONADI_ENABLED) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) set(_kleopatra_SRCS ${_kleopatra_extra_SRCS} accessibility/accessiblelink.cpp accessibility/accessiblelink_p.h accessibility/accessiblerichtextlabel.cpp accessibility/accessiblerichtextlabel_p.h accessibility/accessiblevaluelabel.cpp accessibility/accessiblevaluelabel_p.h accessibility/accessiblewidgetfactory.cpp accessibility/accessiblewidgetfactory.h commands/adduseridcommand.cpp commands/adduseridcommand.h commands/authenticatepivcardapplicationcommand.cpp commands/authenticatepivcardapplicationcommand.h commands/cardcommand.cpp commands/cardcommand.h commands/certificatetopivcardcommand.cpp commands/certificatetopivcardcommand.h commands/certifycertificatecommand.cpp commands/certifycertificatecommand.h commands/changeexpirycommand.cpp commands/changeexpirycommand.h commands/changeownertrustcommand.cpp commands/changeownertrustcommand.h commands/changepassphrasecommand.cpp commands/changepassphrasecommand.h commands/changepincommand.cpp commands/changepincommand.h commands/changeroottrustcommand.cpp commands/changeroottrustcommand.h commands/checksumcreatefilescommand.cpp commands/checksumcreatefilescommand.h commands/checksumverifyfilescommand.cpp commands/checksumverifyfilescommand.h commands/clearcrlcachecommand.cpp commands/clearcrlcachecommand.h commands/command.cpp commands/command.h commands/createcsrforcardkeycommand.cpp commands/createcsrforcardkeycommand.h commands/createopenpgpkeyfromcardkeyscommand.cpp commands/createopenpgpkeyfromcardkeyscommand.h commands/decryptverifyclipboardcommand.cpp commands/decryptverifyclipboardcommand.h commands/decryptverifyfilescommand.cpp commands/decryptverifyfilescommand.h commands/deletecertificatescommand.cpp commands/deletecertificatescommand.h commands/detailscommand.cpp commands/detailscommand.h commands/dumpcertificatecommand.cpp commands/dumpcertificatecommand.h commands/dumpcrlcachecommand.cpp commands/dumpcrlcachecommand.h commands/encryptclipboardcommand.cpp commands/encryptclipboardcommand.h commands/exportcertificatecommand.cpp commands/exportcertificatecommand.h commands/exportgroupscommand.cpp commands/exportgroupscommand.h commands/exportopenpgpcertstoservercommand.cpp commands/exportopenpgpcertstoservercommand.h commands/exportopenpgpcerttoprovidercommand.cpp commands/exportopenpgpcerttoprovidercommand.h commands/exportpaperkeycommand.cpp commands/exportpaperkeycommand.h commands/exportsecretkeycommand.cpp commands/exportsecretkeycommand.h commands/exportsecretkeycommand_old.cpp commands/exportsecretkeycommand_old.h commands/exportsecretsubkeycommand.cpp commands/exportsecretsubkeycommand.h commands/genrevokecommand.cpp commands/genrevokecommand.h commands/gnupgprocesscommand.cpp commands/gnupgprocesscommand.h commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromclipboardcommand.h commands/importcertificatefromdatacommand.cpp commands/importcertificatefromdatacommand.h commands/importcertificatefromfilecommand.cpp commands/importcertificatefromfilecommand.h commands/importcertificatefromkeyservercommand.cpp commands/importcertificatefromkeyservercommand.h commands/importcertificatefrompivcardcommand.cpp commands/importcertificatefrompivcardcommand.h commands/importcertificatescommand.cpp commands/importcertificatescommand.h commands/importcrlcommand.cpp commands/importcrlcommand.h commands/importpaperkeycommand.cpp commands/importpaperkeycommand.h commands/keytocardcommand.cpp commands/keytocardcommand.h commands/learncardkeyscommand.cpp commands/learncardkeyscommand.h commands/lookupcertificatescommand.cpp commands/lookupcertificatescommand.h commands/newcertificatesigningrequestcommand.cpp commands/newcertificatesigningrequestcommand.h commands/newopenpgpcertificatecommand.cpp commands/newopenpgpcertificatecommand.h commands/openpgpgeneratecardkeycommand.cpp commands/openpgpgeneratecardkeycommand.h commands/pivgeneratecardkeycommand.cpp commands/pivgeneratecardkeycommand.h commands/refreshcertificatecommand.cpp commands/refreshcertificatecommand.h commands/refreshopenpgpcertscommand.cpp commands/refreshopenpgpcertscommand.h commands/refreshx509certscommand.cpp commands/refreshx509certscommand.h commands/reloadkeyscommand.cpp commands/reloadkeyscommand.h commands/revokecertificationcommand.cpp commands/revokecertificationcommand.h commands/revokekeycommand.cpp commands/revokekeycommand.h commands/revokeuseridcommand.cpp commands/revokeuseridcommand.h commands/selftestcommand.cpp commands/selftestcommand.h commands/setinitialpincommand.cpp commands/setinitialpincommand.h commands/setpivcardapplicationadministrationkeycommand.cpp commands/setpivcardapplicationadministrationkeycommand.h commands/setprimaryuseridcommand.cpp commands/setprimaryuseridcommand.h commands/signclipboardcommand.cpp commands/signclipboardcommand.h commands/signencryptfilescommand.cpp commands/signencryptfilescommand.h commands/signencryptfoldercommand.cpp commands/signencryptfoldercommand.h conf/configuredialog.cpp conf/configuredialog.h conf/groupsconfigdialog.cpp conf/groupsconfigdialog.h conf/groupsconfigpage.cpp conf/groupsconfigpage.h conf/groupsconfigwidget.cpp conf/groupsconfigwidget.h crypto/autodecryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.h crypto/certificateresolver.cpp crypto/certificateresolver.h crypto/checksumsutils_p.cpp crypto/checksumsutils_p.h crypto/controller.cpp crypto/controller.h crypto/createchecksumscontroller.cpp crypto/createchecksumscontroller.h crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyemailcontroller.h crypto/decryptverifyfilescontroller.cpp crypto/decryptverifyfilescontroller.h crypto/decryptverifytask.cpp crypto/decryptverifytask.h crypto/encryptemailcontroller.cpp crypto/encryptemailcontroller.h crypto/encryptemailtask.cpp crypto/encryptemailtask.h crypto/gui/certificatelineedit.cpp crypto/gui/certificatelineedit.h crypto/gui/certificateselectionline.cpp crypto/gui/certificateselectionline.h crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/decryptverifyfilesdialog.h crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfileswizard.h crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyoperationwidget.h crypto/gui/encryptemailwizard.cpp crypto/gui/encryptemailwizard.h crypto/gui/newresultpage.cpp crypto/gui/newresultpage.h crypto/gui/objectspage.cpp crypto/gui/objectspage.h crypto/gui/resolverecipientspage.cpp crypto/gui/resolverecipientspage.h crypto/gui/resultitemwidget.cpp crypto/gui/resultitemwidget.h crypto/gui/resultlistwidget.cpp crypto/gui/resultlistwidget.h crypto/gui/resultpage.cpp crypto/gui/resultpage.h crypto/gui/signemailwizard.cpp crypto/gui/signemailwizard.h crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/signencryptemailconflictdialog.h crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptfileswizard.h crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwidget.h crypto/gui/signencryptwizard.cpp crypto/gui/signencryptwizard.h crypto/gui/signerresolvepage.cpp crypto/gui/signerresolvepage.h crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/signingcertificateselectiondialog.h crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectionwidget.h crypto/gui/unknownrecipientwidget.cpp crypto/gui/unknownrecipientwidget.h crypto/gui/verifychecksumsdialog.cpp crypto/gui/verifychecksumsdialog.h crypto/gui/wizard.cpp crypto/gui/wizard.h crypto/gui/wizardpage.cpp crypto/gui/wizardpage.h crypto/newsignencryptemailcontroller.cpp crypto/newsignencryptemailcontroller.h crypto/recipient.cpp crypto/recipient.h crypto/sender.cpp crypto/sender.h crypto/signemailcontroller.cpp crypto/signemailcontroller.h crypto/signemailtask.cpp crypto/signemailtask.h crypto/signencryptfilescontroller.cpp crypto/signencryptfilescontroller.h crypto/signencrypttask.cpp crypto/signencrypttask.h crypto/task.cpp crypto/task.h crypto/taskcollection.cpp crypto/taskcollection.h crypto/verifychecksumscontroller.cpp crypto/verifychecksumscontroller.h dialogs/adduseriddialog.cpp dialogs/adduseriddialog.h dialogs/certificatedetailsdialog.cpp dialogs/certificatedetailsdialog.h dialogs/certificatedetailsinputwidget.cpp dialogs/certificatedetailsinputwidget.h dialogs/certificatedetailswidget.cpp dialogs/certificatedetailswidget.h dialogs/certificateselectiondialog.cpp dialogs/certificateselectiondialog.h dialogs/certifycertificatedialog.cpp dialogs/certifycertificatedialog.h dialogs/certifywidget.cpp dialogs/certifywidget.h dialogs/createcsrforcardkeydialog.cpp dialogs/createcsrforcardkeydialog.h dialogs/deletecertificatesdialog.cpp dialogs/deletecertificatesdialog.h dialogs/editgroupdialog.cpp dialogs/editgroupdialog.h dialogs/expirydialog.cpp dialogs/expirydialog.h dialogs/exportdialog.cpp dialogs/exportdialog.h dialogs/gencardkeydialog.cpp dialogs/gencardkeydialog.h dialogs/groupdetailsdialog.cpp dialogs/groupdetailsdialog.h dialogs/lookupcertificatesdialog.cpp dialogs/lookupcertificatesdialog.h dialogs/nameandemailwidget.cpp dialogs/nameandemailwidget.h dialogs/newopenpgpcertificatedetailsdialog.cpp dialogs/newopenpgpcertificatedetailsdialog.h dialogs/pivcardapplicationadministrationkeyinputdialog.cpp dialogs/pivcardapplicationadministrationkeyinputdialog.h dialogs/revokekeydialog.cpp dialogs/revokekeydialog.h dialogs/selftestdialog.cpp dialogs/selftestdialog.h dialogs/setinitialpindialog.cpp dialogs/setinitialpindialog.h dialogs/subkeyswidget.cpp dialogs/subkeyswidget.h dialogs/trustchainwidget.cpp dialogs/trustchainwidget.h dialogs/updatenotification.cpp dialogs/updatenotification.h dialogs/weboftrustdialog.cpp dialogs/weboftrustdialog.h dialogs/weboftrustwidget.cpp dialogs/weboftrustwidget.h interfaces/anchorprovider.h interfaces/focusfirstchild.h newcertificatewizard/advancedsettingsdialog.cpp newcertificatewizard/advancedsettingsdialog_p.h newcertificatewizard/enterdetailspage.cpp newcertificatewizard/enterdetailspage_p.h newcertificatewizard/keyalgo.cpp newcertificatewizard/keyalgo_p.h newcertificatewizard/keycreationpage.cpp newcertificatewizard/keycreationpage_p.h newcertificatewizard/listwidget.cpp newcertificatewizard/listwidget.h newcertificatewizard/newcertificatewizard.cpp newcertificatewizard/newcertificatewizard.h newcertificatewizard/resultpage.cpp newcertificatewizard/resultpage_p.h newcertificatewizard/wizardpage.cpp newcertificatewizard/wizardpage_p.h selftest/compliancecheck.cpp selftest/compliancecheck.h selftest/enginecheck.cpp selftest/enginecheck.h selftest/gpgagentcheck.cpp selftest/gpgagentcheck.h selftest/gpgconfcheck.cpp selftest/gpgconfcheck.h selftest/libkleopatrarccheck.cpp selftest/libkleopatrarccheck.h selftest/selftest.cpp selftest/selftest.h smartcard/algorithminfo.h smartcard/card.cpp smartcard/card.h smartcard/deviceinfowatcher.cpp smartcard/deviceinfowatcher.h smartcard/keypairinfo.cpp smartcard/keypairinfo.h smartcard/netkeycard.cpp smartcard/netkeycard.h smartcard/openpgpcard.cpp smartcard/openpgpcard.h smartcard/p15card.cpp smartcard/p15card.h smartcard/pivcard.cpp smartcard/pivcard.h smartcard/readerstatus.cpp smartcard/readerstatus.h smartcard/utils.cpp smartcard/utils.h utils/accessibility.cpp utils/accessibility.h utils/action_data.cpp utils/action_data.h utils/applicationstate.cpp utils/applicationstate.h utils/archivedefinition.cpp utils/archivedefinition.h utils/clipboardmenu.cpp utils/clipboardmenu.h utils/debug-helpers.cpp utils/debug-helpers.h utils/dragqueen.cpp utils/dragqueen.h utils/email.cpp utils/email.h utils/emptypassphraseprovider.cpp utils/emptypassphraseprovider.h utils/filedialog.cpp utils/filedialog.h utils/gui-helper.cpp utils/gui-helper.h utils/headerview.cpp utils/headerview.h utils/input.cpp utils/input.h utils/iodevicelogger.cpp utils/iodevicelogger.h utils/kdpipeiodevice.cpp utils/kdpipeiodevice.h utils/keyparameters.cpp utils/keyparameters.h utils/keys.cpp utils/keys.h utils/kuniqueservice.cpp utils/kuniqueservice.h utils/log.cpp utils/log.h utils/memory-helpers.h utils/multivalidator.cpp utils/multivalidator.h utils/output.cpp utils/output.h utils/path-helper.cpp utils/path-helper.h utils/scrollarea.cpp utils/scrollarea.h utils/systemtrayicon.cpp utils/systemtrayicon.h utils/tags.cpp utils/tags.h utils/types.cpp utils/types.h utils/userinfo.cpp utils/userinfo.h utils/validation.cpp utils/validation.h utils/writecertassuantransaction.cpp utils/writecertassuantransaction.h utils/wsastarter.cpp utils/wsastarter.h view/anchorcache.cpp view/anchorcache_p.h view/errorlabel.cpp view/errorlabel.h view/formtextinput.cpp view/formtextinput.h view/htmllabel.cpp view/htmllabel.h view/infofield.cpp view/infofield.h view/keycacheoverlay.cpp view/keycacheoverlay.h view/keylistcontroller.cpp view/keylistcontroller.h view/keytreeview.cpp view/keytreeview.h view/netkeywidget.cpp view/netkeywidget.h view/nullpinwidget.cpp view/nullpinwidget.h view/openpgpkeycardwidget.cpp view/openpgpkeycardwidget.h view/p15cardwidget.cpp view/p15cardwidget.h view/padwidget.cpp view/padwidget.h view/pgpcardwidget.cpp view/pgpcardwidget.h view/pivcardwidget.cpp view/pivcardwidget.h view/searchbar.cpp view/searchbar.h view/smartcardwidget.cpp view/smartcardwidget.h view/tabwidget.cpp view/tabwidget.h view/urllabel.cpp view/urllabel.h view/waitwidget.cpp view/waitwidget.h view/welcomewidget.cpp view/welcomewidget.h aboutdata.cpp aboutdata.h kleopatra.qrc kleopatraapplication.cpp kleopatraapplication.h main.cpp mainwindow.cpp mainwindow.h systrayicon.cpp systrayicon.h ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) endif() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS}) ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_kleopatra EXPORT KLEOPATRA ) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/selectchecklevelwidget.ui dialogs/setinitialpindialog.ui dialogs/trustchainwidget.ui newcertificatewizard/listwidget.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/settings.kcfgc kcfg/smimevalidationpreferences.kcfgc kcfg/tagspreferences.kcfgc kcfg/tooltippreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) # For the ConfigureDialog & KCMs target_link_libraries(kleopatra_bin kcm_kleopatra_static) #if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) # set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON) #endif() set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) if (WIN32) set(_kleopatra_platform_libs "secur32") endif () target_link_libraries(kleopatra_bin Gpgmepp ${_kleopatra_extra_libs} KF5::Libkleo KF5::Mime KF5::I18n KF5::XmlGui KF5::IconThemes KF5::WindowSystem KF5::CoreAddons KF5::ItemModels KF5::Crash ${_kleopatra_mail_libs} Qt::Network Qt::PrintSupport # Printing secret keys ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} kleopatraclientcore ${_kleopatra_platform_libs} ) if (QT_MAJOR_VERSION STREQUAL "6") target_link_libraries(kleopatra_bin QGpgmeQt6) else() target_link_libraries(kleopatra_bin QGpgme) endif() install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install( PROGRAMS data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus ) diff --git a/src/libkleopatraclient/core/CMakeLists.txt b/src/libkleopatraclient/core/CMakeLists.txt index 68b738525..eb03c6c1e 100644 --- a/src/libkleopatraclient/core/CMakeLists.txt +++ b/src/libkleopatraclient/core/CMakeLists.txt @@ -1,74 +1,59 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause if(WIN32) set(_kleopatraclientcore_extra_SRCS ../../utils/gnupg-registry.c) else() set(_kleopatraclientcore_extra_SRCS) endif() ecm_qt_declare_logging_category(_kleopatraclientcore_common_SRCS HEADER libkleopatraclientcore_debug.h IDENTIFIER LIBKLEOPATRACLIENTCORE_LOG CATEGORY_NAME org.kde.pim.libkleopatraclientcore DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_libkleopatraclientcore EXPORT KLEOPATRA ) add_library(kleopatraclientcore ${_kleopatraclientcore_extra_SRCS} initialization.cpp command.cpp selectcertificatecommand.cpp signencryptfilescommand.cpp decryptverifyfilescommand.cpp libkleopatraclientcore_debug.cpp initialization.h command.h selectcertificatecommand.h signencryptfilescommand.h decryptverifyfilescommand.h libkleopatraclientcore_debug.h ${_kleopatraclientcore_common_SRCS} ) generate_export_header(kleopatraclientcore BASE_NAME kleopatraclientcore) set_target_properties(kleopatraclientcore PROPERTIES VERSION ${libkleopatraclient_version} SOVERSION ${libkleopatraclient_soversion} ) if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(kleopatraclientcore PROPERTIES UNITY_BUILD ON) endif() if(WIN32) - if(ASSUAN2_FOUND) target_link_libraries(kleopatraclientcore ${ASSUAN2_LIBRARIES} ws2_32 ) - else() - target_link_libraries(kleopatraclientcore - - ${ASSUAN_VANILLA_LIBRARIES} - ws2_32 - ) - endif() else() - if(ASSUAN2_FOUND) target_link_libraries(kleopatraclientcore ${ASSUAN2_LIBRARIES} ) - else() - target_link_libraries(kleopatraclientcore - - ${ASSUAN_PTHREAD_LIBRARIES} - ) - endif() endif() target_link_libraries(kleopatraclientcore Qt::Widgets KF5::I18n Gpgmepp) install(TARGETS kleopatraclientcore ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/libkleopatraclient/core/command.cpp b/src/libkleopatraclient/core/command.cpp index cef2a1f1f..8476a4907 100644 --- a/src/libkleopatraclient/core/command.cpp +++ b/src/libkleopatraclient/core/command.cpp @@ -1,702 +1,678 @@ /* -*- mode: c++; c-basic-offset:4 -*- command.cpp This file is part of KleopatraClient, the Kleopatra interface library SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "command.h" #include "command_p.h" #include // Q_OS_WIN #ifdef Q_OS_WIN // HACK: AllowSetForegroundWindow needs _WIN32_WINDOWS >= 0x0490 set # ifndef _WIN32_WINDOWS # define _WIN32_WINDOWS 0x0500 # endif # ifndef _WIN32_WINNT # define _WIN32_WINNT 0x0500 // good enough for Vista too # endif # include # include #endif #include #include #include "libkleopatraclientcore_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KleopatraClientCopy; // copied from kleopatra/utils/hex.cpp static std::string hexencode(const std::string &in) { std::string result; result.reserve(3 * in.size()); static const char hex[] = "0123456789ABCDEF"; for (std::string::const_iterator it = in.begin(), end = in.end(); it != end; ++it) switch (const unsigned char ch = *it) { default: if ((ch >= '!' && ch <= '~') || ch > 0xA0) { result += ch; break; } Q_FALLTHROUGH(); // else fall through case ' ': result += '+'; break; case '"': case '#': case '$': case '%': case '\'': case '+': case '=': result += '%'; result += hex[(ch & 0xF0) >> 4 ]; result += hex[(ch & 0x0F) ]; break; } return result; } #ifdef UNUSED static std::string hexencode(const char *in) { if (!in) { return std::string(); } return hexencode(std::string(in)); } #endif // changed from returning QByteArray to returning std::string static std::string hexencode(const QByteArray &in) { if (in.isNull()) { return std::string(); } return hexencode(std::string(in.data(), in.size())); } // end copied from kleopatra/utils/hex.cpp Command::Command(QObject *p) : QObject(p), d(new Private(this)) { d->init(); } Command::Command(Private *pp, QObject *p) : QObject(p), d(pp) { d->init(); } Command::~Command() { delete d; d = nullptr; } void Command::Private::init() { connect(this, &QThread::started, q, &Command::started); connect(this, &QThread::finished, q, &Command::finished); } void Command::setParentWId(WId wid) { const QMutexLocker locker(&d->mutex); d->inputs.parentWId = wid; } WId Command::parentWId() const { const QMutexLocker locker(&d->mutex); return d->inputs.parentWId; } void Command::setServerLocation(const QString &location) { const QMutexLocker locker(&d->mutex); d->outputs.serverLocation = location; } QString Command::serverLocation() const { const QMutexLocker locker(&d->mutex); return d->outputs.serverLocation; } bool Command::waitForFinished() { return d->wait(); } bool Command::waitForFinished(unsigned long ms) { return d->wait(ms); } bool Command::error() const { const QMutexLocker locker(&d->mutex); return !d->outputs.errorString.isEmpty(); } bool Command::wasCanceled() const { const QMutexLocker locker(&d->mutex); return d->outputs.canceled; } QString Command::errorString() const { const QMutexLocker locker(&d->mutex); return d->outputs.errorString; } qint64 Command::serverPid() const { const QMutexLocker locker(&d->mutex); return d->outputs.serverPid; } void Command::start() { d->start(); } void Command::cancel() { qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "Sorry, not implemented: KleopatraClient::Command::Cancel"; } void Command::setOptionValue(const char *name, const QVariant &value, bool critical) { if (!name || !*name) { return; } const Private::Option opt = { value, true, critical }; const QMutexLocker locker(&d->mutex); d->inputs.options[name] = opt; } QVariant Command::optionValue(const char *name) const { if (!name || !*name) { return QVariant(); } const QMutexLocker locker(&d->mutex); const auto it = d->inputs.options.find(name); if (it == d->inputs.options.end()) { return QVariant(); } else { return it->second.value; } } void Command::setOption(const char *name, bool critical) { if (!name || !*name) { return; } const QMutexLocker locker(&d->mutex); if (isOptionSet(name)) { unsetOption(name); } const Private::Option opt = { QVariant(), false, critical }; d->inputs.options[name] = opt; } void Command::unsetOption(const char *name) { if (!name || !*name) { return; } const QMutexLocker locker(&d->mutex); d->inputs.options.erase(name); } bool Command::isOptionSet(const char *name) const { if (!name || !*name) { return false; } const QMutexLocker locker(&d->mutex); return d->inputs.options.count(name); } bool Command::isOptionCritical(const char *name) const { if (!name || !*name) { return false; } const QMutexLocker locker(&d->mutex); const auto it = d->inputs.options.find(name); return it != d->inputs.options.end() && it->second.isCritical; } void Command::setFilePaths(const QStringList &filePaths) { const QMutexLocker locker(&d->mutex); d->inputs.filePaths = filePaths; } QStringList Command::filePaths() const { const QMutexLocker locker(&d->mutex); return d->inputs.filePaths; } void Command::setRecipients(const QStringList &recipients, bool informative) { const QMutexLocker locker(&d->mutex); d->inputs.recipients = recipients; d->inputs.areRecipientsInformative = informative; } QStringList Command::recipients() const { const QMutexLocker locker(&d->mutex); return d->inputs.recipients; } bool Command::areRecipientsInformative() const { const QMutexLocker locker(&d->mutex); return d->inputs.areRecipientsInformative; } void Command::setSenders(const QStringList &senders, bool informative) { const QMutexLocker locker(&d->mutex); d->inputs.senders = senders; d->inputs.areSendersInformative = informative; } QStringList Command::senders() const { const QMutexLocker locker(&d->mutex); return d->inputs.senders; } bool Command::areSendersInformative() const { const QMutexLocker locker(&d->mutex); return d->inputs.areSendersInformative; } void Command::setInquireData(const char *what, const QByteArray &data) { const QMutexLocker locker(&d->mutex); d->inputs.inquireData[what] = data; } void Command::unsetInquireData(const char *what) { const QMutexLocker locker(&d->mutex); d->inputs.inquireData.erase(what); } QByteArray Command::inquireData(const char *what) const { const QMutexLocker locker(&d->mutex); const auto it = d->inputs.inquireData.find(what); if (it == d->inputs.inquireData.end()) { return QByteArray(); } else { return it->second; } } bool Command::isInquireDataSet(const char *what) const { const QMutexLocker locker(&d->mutex); const auto it = d->inputs.inquireData.find(what); return it != d->inputs.inquireData.end(); } QByteArray Command::receivedData() const { const QMutexLocker locker(&d->mutex); return d->outputs.data; } void Command::setCommand(const char *command) { const QMutexLocker locker(&d->mutex); d->inputs.command = command; } QByteArray Command::command() const { const QMutexLocker locker(&d->mutex); return d->inputs.command; } // // here comes the ugly part // -#ifdef HAVE_ASSUAN2 static void my_assuan_release(assuan_context_t ctx) { if (ctx) { assuan_release(ctx); } } -#endif using AssuanContextBase = std::shared_ptr::type>; namespace { struct AssuanClientContext : AssuanContextBase { AssuanClientContext() : AssuanContextBase() {} -#ifndef HAVE_ASSUAN2 - explicit AssuanClientContext(assuan_context_t ctx) : AssuanContextBase(ctx, &assuan_disconnect) {} - void reset(assuan_context_t ctx = nullptr) - { - AssuanContextBase::reset(ctx, &assuan_disconnect); - } -#else explicit AssuanClientContext(assuan_context_t ctx) : AssuanContextBase(ctx, &my_assuan_release) {} void reset(assuan_context_t ctx = nullptr) { AssuanContextBase::reset(ctx, &my_assuan_release); } -#endif }; } -#ifdef HAVE_ASSUAN2 // compatibility typedef - remove when we require assuan v2... using assuan_error_t = gpg_error_t; -#endif static assuan_error_t my_assuan_transact(const AssuanClientContext &ctx, const char *command, assuan_error_t (*data_cb)(void *, const void *, size_t) = nullptr, void *data_cb_arg = nullptr, assuan_error_t (*inquire_cb)(void *, const char *) = nullptr, void *inquire_cb_arg = nullptr, assuan_error_t (*status_cb)(void *, const char *) = nullptr, void *status_cb_arg = nullptr) { return assuan_transact(ctx.get(), command, data_cb, data_cb_arg, inquire_cb, inquire_cb_arg, status_cb, status_cb_arg); } static QString to_error_string(int err) { char buffer[1024]; gpg_strerror_r(static_cast(err), buffer, sizeof buffer); buffer[sizeof buffer - 1] = '\0'; return QString::fromLocal8Bit(buffer); } static QString gnupg_home_directory() { static const char *hDir = GpgME::dirInfo("homedir"); return QFile::decodeName(hDir); } static QString get_default_socket_name() { const QString socketPath{QString::fromUtf8(GpgME::dirInfo("uiserver-socket"))}; if (!socketPath.isEmpty()) { // Note: The socket directory exists after GpgME::dirInfo() has been called. return socketPath; } const QString homeDir = gnupg_home_directory(); if (homeDir.isEmpty()) { return QString(); } return QDir(homeDir).absoluteFilePath(QStringLiteral("S.uiserver")); } static QString default_socket_name() { static QString name = get_default_socket_name(); return name; } static QString uiserver_executable() { return QStringLiteral("kleopatra"); } static QString start_uiserver() { // Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process. if (!QProcess::startDetached(uiserver_executable(), QStringList() << QStringLiteral("--daemon"))) { return i18n("Failed to start uiserver %1", uiserver_executable()); } else { return QString(); } } static assuan_error_t getinfo_pid_cb(void *opaque, const void *buffer, size_t length) { qint64 &pid = *static_cast(opaque); pid = QByteArray(static_cast(buffer), length).toLongLong(); return 0; } static assuan_error_t command_data_cb(void *opaque, const void *buffer, size_t length) { QByteArray &ba = *static_cast(opaque); ba.append(QByteArray(static_cast(buffer), length)); return 0; } namespace { struct inquire_data { const std::map *map; const AssuanClientContext *ctx; }; } static assuan_error_t command_inquire_cb(void *opaque, const char *what) { if (!opaque) { return 0; } const inquire_data &id = *static_cast(opaque); const auto it = id.map->find(what); if (it != id.map->end()) { const QByteArray &v = it->second; assuan_send_data(id.ctx->get(), v.data(), v.size()); } return 0; } static inline std::ostream &operator<<(std::ostream &s, const QByteArray &ba) { return s << std::string(ba.data(), ba.size()); } static assuan_error_t send_option(const AssuanClientContext &ctx, const char *name, const QVariant &value) { std::stringstream ss; ss << "OPTION " << name; if (value.isValid()) { ss << '=' << value.toString().toUtf8(); } return my_assuan_transact(ctx, ss.str().c_str()); } static assuan_error_t send_file(const AssuanClientContext &ctx, const QString &file) { std::stringstream ss; ss << "FILE " << hexencode(QFile::encodeName(file)); return my_assuan_transact(ctx, ss.str().c_str()); } static assuan_error_t send_recipient(const AssuanClientContext &ctx, const QString &recipient, bool info) { std::stringstream ss; ss << "RECIPIENT "; if (info) { ss << "--info "; } ss << "--" << hexencode(recipient.toUtf8()); return my_assuan_transact(ctx, ss.str().c_str()); } static assuan_error_t send_sender(const AssuanClientContext &ctx, const QString &sender, bool info) { std::stringstream ss; ss << "SENDER "; if (info) { ss << "--info "; } ss << "--" << hexencode(sender.toUtf8()); return my_assuan_transact(ctx, ss.str().c_str()); } void Command::Private::run() { // Take a snapshot of the input data, and clear the output data: Inputs in; Outputs out; { const QMutexLocker locker(&mutex); in = inputs; outputs = out; } out.canceled = false; if (out.serverLocation.isEmpty()) { out.serverLocation = default_socket_name(); } -#ifndef HAVE_ASSUAN2 - assuan_context_t naked_ctx = 0; -#endif AssuanClientContext ctx; assuan_error_t err = 0; inquire_data id = { &in.inquireData, &ctx }; const QString socketName = out.serverLocation; if (socketName.isEmpty()) { out.errorString = i18n("Invalid socket name!"); goto leave; } -#ifndef HAVE_ASSUAN2 - err = assuan_socket_connect(&naked_ctx, socketName.toUtf8().constData(), -1); -#else { assuan_context_t naked_ctx = nullptr; err = assuan_new(&naked_ctx); if (err) { out.errorString = i18n("Could not allocate resources to connect to Kleopatra UI server at %1: %2" , socketName, to_error_string(err)); goto leave; } ctx.reset(naked_ctx); } err = assuan_socket_connect(ctx.get(), socketName.toUtf8().constData(), -1, 0); -#endif if (err) { qDebug("UI server not running, starting it"); const QString errorString = start_uiserver(); if (!errorString.isEmpty()) { out.errorString = errorString; goto leave; } // give it a bit of time to start up and try a couple of times for (int i = 0; err && i < 20; ++i) { msleep(500); err = assuan_socket_connect(ctx.get(), socketName.toUtf8().constData(), -1, 0); } } if (err) { out.errorString = i18n("Could not connect to Kleopatra UI server at %1: %2", socketName, to_error_string(err)); goto leave; } -#ifndef HAVE_ASSUAN2 - ctx.reset(naked_ctx); - naked_ctx = 0; -#endif - out.serverPid = -1; err = my_assuan_transact(ctx, "GETINFO pid", &getinfo_pid_cb, &out.serverPid); if (err || out.serverPid <= 0) { out.errorString = i18n("Could not get the process-id of the Kleopatra UI server at %1: %2", socketName, to_error_string(err)); goto leave; } qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "Server PID =" << out.serverPid; #if defined(Q_OS_WIN) if (!AllowSetForegroundWindow((pid_t)out.serverPid)) { qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "AllowSetForegroundWindow(" << out.serverPid << ") failed: " << GetLastError(); } #endif if (in.command.isEmpty()) { goto leave; } if (in.parentWId) { err = send_option(ctx, "window-id", QString::number(in.parentWId, 16)); if (err) { qDebug("sending option window-id failed - ignoring"); } } for (auto it = in.options.begin(), end = in.options.end(); it != end; ++it) if ((err = send_option(ctx, it->first.c_str(), it->second.hasValue ? it->second.value.toString() : QVariant()))) { if (it->second.isCritical) { out.errorString = i18n("Failed to send critical option %1: %2", QString::fromLatin1(it->first.c_str()), to_error_string(err)); goto leave; } else { qCDebug(LIBKLEOPATRACLIENTCORE_LOG) << "Failed to send non-critical option" << it->first.c_str() << ":" << to_error_string(err); } } for (const QString &filePath : std::as_const(in.filePaths)) { if ((err = send_file(ctx, filePath))) { out.errorString = i18n("Failed to send file path %1: %2", filePath, to_error_string(err)); goto leave; } } for (const QString &sender : std::as_const(in.senders)) { if ((err = send_sender(ctx, sender, in.areSendersInformative))) { out.errorString = i18n("Failed to send sender %1: %2", sender, to_error_string(err)); goto leave; } } for (const QString &recipient : std::as_const(in.recipients)) { if ((err = send_recipient(ctx, recipient, in.areRecipientsInformative))) { out.errorString = i18n("Failed to send recipient %1: %2", recipient, to_error_string(err)); goto leave; } } #if 0 setup I / O; #endif err = my_assuan_transact(ctx, in.command.constData(), &command_data_cb, &out.data, &command_inquire_cb, &id); if (err) { if (gpg_err_code(err) == GPG_ERR_CANCELED) { out.canceled = true; } else { out.errorString = i18n("Command (%1) failed: %2", QString::fromLatin1(in.command.constData()), to_error_string(err)); } goto leave; } leave: const QMutexLocker locker(&mutex); // copy outputs to where Command can see them: outputs = out; } diff --git a/src/libkleopatraclient/core/initialization.cpp b/src/libkleopatraclient/core/initialization.cpp index 86a46eb48..d3162f1c0 100644 --- a/src/libkleopatraclient/core/initialization.cpp +++ b/src/libkleopatraclient/core/initialization.cpp @@ -1,31 +1,27 @@ /* -*- mode: c++; c-basic-offset:4 -*- initialization.h This file is part of KleopatraClient, the Kleopatra interface library SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "initialization.h" #include #include using namespace KleopatraClientCopy; Initialization::Initialization() { -#ifndef HAVE_ASSUAN2 - assuan_set_assuan_err_source(GPG_ERR_SOURCE_DEFAULT); -#else assuan_set_gpg_err_source(GPG_ERR_SOURCE_DEFAULT); -#endif } Initialization::~Initialization() { } diff --git a/src/uiserver/assuancommand.h b/src/uiserver/assuancommand.h index 445ad4e9a..a0296930d 100644 --- a/src/uiserver/assuancommand.h +++ b/src/uiserver/assuancommand.h @@ -1,385 +1,370 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/assuancommand.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include -#ifdef HAVE_ASSUAN2 #include -#endif #include #include // for WId #include #include #include #include class QVariant; class QObject; #include struct assuan_context_s; namespace Kleo { class Input; class Output; class AssuanCommandFactory; /*! \brief Base class for GnuPG UI Server commands \note large parts of this are outdated by now!

Implementing a new AssuanCommand

You do not directly inherit AssuanCommand, unless you want to deal with implementing low-level, repetitive things like name() in terms of staticName(). Assuming you don't, then you inherit your command class from AssuanCommandMixin, passing your class as the template argument to AssuanCommandMixin, like this: \code class MyFooCommand : public AssuanCommandMixin { \endcode (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) You then choose a command name, and return that from the static method staticName(), which is by convention queried by both AssuanCommandMixin<> and GenericAssuanCommandFactory<>: \code static const char * staticName() { return "MYFOO"; } \endcode The string should be all-uppercase by convention, but the UiServer implementation doesn't enforce this. The next step is to implement start(), the starting point of command execution:

Executing the command

\code int start( const std::string & line ) { \endcode This should set everything up and check the parameters in \a line and any options this command understands. If there's an error, choose one of the gpg-error codes and create a gpg_error_t from it using the protected makeError() function: \code return makeError( GPG_ERR_NOT_IMPLEMENTED ); \endcode But usually, you will want to create a dialog, or call some GpgME function from here. In case of errors from GpgME, you shouldn't pipe them through makeError(), but return them as-is. This will preserve the error source. Error created using makeError() will have Kleopatra as their error source, so watch out what you're doing :) In addition to options and the command line, your command might require bulk data input or output. That's what the bulk input and output channels are for. You can check whether the client handed you an input channel by checking that bulkInputDevice() isn't NULL, likewise for bulkOutputDevice(). If everything is ok, you return 0. This indicates to the client that the command has been accepted and is now in progress. In this mode (start() returned 0), there are a bunch of options for your command to do. Some commands may require additional information from the client. The options passed to start() are designed to be persistent across commands, and rather limited in length (there's a strict line length limit in the assuan protocol with no line continuation mechanism). The same is true for command line arguments, which, in addition, you have to parse yourself. Those usually apply only to this command, and not to following ones. If you need data that might be larger than the line length limit, you can either expect it on the bulkInputDevice(), or, if you have the need for more than one such data channel, or the data is optional or conditional on some condition that can only be determined during command execution, you can \em inquire the missing information from the client. As an example, a VERIFY command would expect the signed data on the bulkInputDevice(). But if the input stream doesn't contain an embedded (opaque) signature, indicating a \em detached signature, it would go and inquire that data from the client. Here's how it works: \code const int err = inquire( "DETACHED_SIGNATURE", this, SLOT(slotDetachedSignature(int,QByteArray,QByteArray)) ); if ( err ) done( err ); \endcode This should be self-explanatory: You give a slot to call when the data has arrived. The slot's first argument is an error code. The second the data (if any), and the third is just repeating what you gave as inquire()'s first argument. As usual, you can leave argument off of the end, if you are not interested in them. You can do as many inquiries as you want, but only one at a time. You should periodically send status updates to the client. You do that by calling sendStatus(). Once your command has finished executing, call done(). If it's with an error code, call done(err) like above. Do not forget to call done() when done!. It will close bulkInputDevice(), bulkOutputDevice(), and send an OK or ERR message back to the client. At that point, your command has finished executing, and a new one can be accepted, or the connection closed. Apropos connection closed. The only way for the client to cancel an operation is to shut down the connection. In this case, the canceled() function will be called. At that point, the connection to the client will have been broken already, and all you can do is pack your things and go down gracefully. If _you_ detect that the user has canceled (your dialog contains a cancel button, doesn't it?), then you should instead call done( GPG_ERR_CANCELED ), like for normal operation.

Registering the command with UiServer

To register a command, you implement a AssuanCommandFactory for your AssuanCommand subclass, and register it with the UiServer. This can be made considerably easier using GenericAssuanCommandFactory: \code UiServer server; server.registerCommandFactory( shared_ptr( new GenericAssuanCommandFactory ) ); // more registerCommandFactory calls... server.start(); \endcode */ class AssuanCommand : public ExecutionContext, public std::enable_shared_from_this { // defined in assuanserverconnection.cpp! public: AssuanCommand(); ~AssuanCommand() override; int start(); void canceled(); virtual const char *name() const = 0; class Memento { public: virtual ~Memento() {} }; template class TypedMemento : public Memento { T m_t; public: explicit TypedMemento(const T &t) : m_t(t) {} const T &get() const { return m_t; } T &get() { return m_t; } }; template static std::shared_ptr< TypedMemento > make_typed_memento(const T &t) { return std::shared_ptr< TypedMemento >(new TypedMemento(t)); } static int makeError(int code); // convenience methods: enum Mode { NoMode, EMail, FileManager }; Mode checkMode() const; enum CheckProtocolOption { AllowProtocolMissing = 0x01 }; GpgME::Protocol checkProtocol(Mode mode, int options = 0) const; void applyWindowID(QWidget *w) const override { doApplyWindowID(w); } WId parentWId() const; void setNohup(bool on); bool isNohup() const; bool isDone() const; QString sessionTitle() const; unsigned int sessionId() const; bool informativeRecipients() const; bool informativeSenders() const; const std::vector &recipients() const; const std::vector &senders() const; bool hasMemento(const QByteArray &tag) const; std::shared_ptr memento(const QByteArray &tag) const; template std::shared_ptr mementoAs(const QByteArray &tag) const { return std::dynamic_pointer_cast(this->memento(tag)); } QByteArray registerMemento(const std::shared_ptr &mem); QByteArray registerMemento(const QByteArray &tag, const std::shared_ptr &mem); void removeMemento(const QByteArray &tag); template T mementoContent(const QByteArray &tag) const { if (std::shared_ptr< TypedMemento > m = mementoAs< TypedMemento >(tag)) { return m->get(); } else { return T(); } } bool hasOption(const char *opt) const; QVariant option(const char *opt) const; const std::map &options() const; const std::vector< std::shared_ptr > &inputs() const; const std::vector< std::shared_ptr > &messages() const; const std::vector< std::shared_ptr > &outputs() const; QStringList fileNames() const; unsigned int numFiles() const; void sendStatus(const char *keyword, const QString &text); void sendStatusEncoded(const char *keyword, const std::string &text); void sendData(const QByteArray &data, bool moreToCome = false); int inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize = 0); void done(const GpgME::Error &err = GpgME::Error()); void done(const GpgME::Error &err, const QString &details); void done(int err) { done(GpgME::Error(err)); } void done(int err, const QString &details) { done(GpgME::Error(err), details); } private: virtual void doCanceled() = 0; virtual int doStart() = 0; private: void doApplyWindowID(QWidget *w) const; private: const std::map< QByteArray, std::shared_ptr > &mementos() const; private: friend class ::Kleo::AssuanCommandFactory; class Private; kdtools::pimpl_ptr d; }; class AssuanCommandFactory { public: virtual ~AssuanCommandFactory() {} virtual std::shared_ptr create() const = 0; virtual const char *name() const = 0; -#ifndef HAVE_ASSUAN2 - typedef int(*_Handler)(assuan_context_s *, char *); -#else using _Handler = gpg_error_t (*)(assuan_context_s *, char *); -#endif virtual _Handler _handler() const = 0; -#ifndef HAVE_ASSUAN2 - static int _handle(assuan_context_s *, char *, const char *); -#else static gpg_error_t _handle(assuan_context_s *, char *, const char *); -#endif }; template class GenericAssuanCommandFactory : public AssuanCommandFactory { AssuanCommandFactory::_Handler _handler() const override { return &GenericAssuanCommandFactory::_handle; } -#ifndef HAVE_ASSUAN2 - static int _handle(assuan_context_s *_ctx, char *_line) - { -#else static gpg_error_t _handle(assuan_context_s *_ctx, char *_line) { -#endif return AssuanCommandFactory::_handle(_ctx, _line, Command::staticName()); } std::shared_ptr create() const override { return make(); } const char *name() const override { return Command::staticName(); } public: static std::shared_ptr make() { return std::shared_ptr(new Command); } }; template class AssuanCommandMixin : public Base { protected: /* reimp */ const char *name() const override { return Derived::staticName(); } }; } diff --git a/src/uiserver/assuanserverconnection.cpp b/src/uiserver/assuanserverconnection.cpp index fa39c6335..950cc897e 100644 --- a/src/uiserver/assuanserverconnection.cpp +++ b/src/uiserver/assuanserverconnection.cpp @@ -1,1693 +1,1503 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/assuanserverconnection.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef QT_NO_CAST_TO_ASCII # define QT_NO_CAST_TO_ASCII #endif #ifndef QT_NO_CAST_FROM_ASCII # define QT_NO_CAST_FROM_ASCII #endif #include #include #include "assuanserverconnection.h" #include "assuancommand.h" #include "sessiondata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __GLIBCXX__ # include // for is_sorted #endif #ifdef Q_OS_WIN32 # include # include #else # include # include #endif using namespace Kleo; static const unsigned int INIT_SOCKET_FLAGS = 3; // says info assuan... //static int(*USE_DEFAULT_HANDLER)(assuan_context_t,char*) = 0; static const int FOR_READING = 0; static const unsigned int MAX_ACTIVE_FDS = 32; -#ifdef HAVE_ASSUAN2 static void my_assuan_release(assuan_context_t ctx) { if (ctx) { assuan_release(ctx); } } -#endif // std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server: using AssuanContextBase = std::shared_ptr::type>; struct AssuanContext : AssuanContextBase { AssuanContext() : AssuanContextBase() {} -#ifndef HAVE_ASSUAN2 - explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &assuan_deinit_server) {} -#else explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &my_assuan_release) {} -#endif -#ifndef HAVE_ASSUAN2 - void reset(assuan_context_t ctx = 0) - { - AssuanContextBase::reset(ctx, &assuan_deinit_server); - } -#else void reset(assuan_context_t ctx = nullptr) { AssuanContextBase::reset(ctx, &my_assuan_release); } -#endif }; static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const char *err_msg) { return assuan_process_done(ctx, assuan_set_error(ctx, err, err_msg)); } static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const std::string &err_msg) { return assuan_process_done_msg(ctx, err, err_msg.c_str()); } static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const QString &err_msg) { return assuan_process_done_msg(ctx, err, err_msg.toUtf8().constData()); } static std::map upcase_option(const char *option, std::map options) { std::string value; bool value_found = false; auto it = options.begin(); while (it != options.end()) if (qstricmp(it->first.c_str(), option) == 0) { value = it->second; options.erase(it++); value_found = true; } else { ++it; } if (value_found) { options[option] = value; } return options; } static std::map parse_commandline(const char *line) { std::map result; if (line) { const char *begin = line; const char *lastEQ = nullptr; while (*line) { if (*line == ' ' || *line == '\t') { if (begin != line) { if (begin[0] == '-' && begin[1] == '-') { begin += 2; // skip initial "--" } if (lastEQ && lastEQ > begin) { result[ std::string(begin, lastEQ - begin) ] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1))); } else { result[ std::string(begin, line - begin) ] = std::string(); } } begin = line + 1; } else if (*line == '=') { if (line == begin) throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("No option name given")); else { lastEQ = line; } } ++line; } if (begin != line) { if (begin[0] == '-' && begin[1] == '-') { begin += 2; // skip initial "--" } if (lastEQ && lastEQ > begin) { result[ std::string(begin, lastEQ - begin) ] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1))); } else { result[ begin ] = std::string(); } } } return result; } static WId wid_from_string(const QString &winIdStr, bool *ok = nullptr) { return static_cast(winIdStr.toULongLong(ok, 16)); } static void apply_window_id(QWidget *widget, const QString &winIdStr) { if (!widget || winIdStr.isEmpty()) { return; } bool ok = false; const WId wid = wid_from_string(winIdStr, &ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "window-id value" << wid << "doesn't look like a number"; return; } if (QWidget *pw = QWidget::find(wid)) { widget->setParent(pw, widget->windowFlags()); } else { widget->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(widget->windowHandle(), wid); } } // // // AssuanServerConnection: // // class AssuanServerConnection::Private : public QObject { Q_OBJECT friend class ::Kleo::AssuanServerConnection; friend class ::Kleo::AssuanCommandFactory; friend class ::Kleo::AssuanCommand; AssuanServerConnection *const q; public: Private(assuan_fd_t fd_, const std::vector< std::shared_ptr > &factories_, AssuanServerConnection *qq); ~Private() override; Q_SIGNALS: void startKeyManager(); public Q_SLOTS: void slotReadActivity(int) { Q_ASSERT(ctx); -#ifndef HAVE_ASSUAN2 - if (const int err = assuan_process_next(ctx.get())) { -#else int done = false; if (const int err = assuan_process_next(ctx.get(), &done) || done) { -#endif //if ( err == -1 || gpg_err_code(err) == GPG_ERR_EOF ) { topHalfDeletion(); if (nohupedCommands.empty()) { bottomHalfDeletion(); } //} else { //assuan_process_done( ctx.get(), err ); //return; //} } } int startCommandBottomHalf(); private: void nohupDone(AssuanCommand *cmd) { const auto it = std::find_if(nohupedCommands.begin(), nohupedCommands.end(), [cmd](const std::shared_ptr &other) { return other.get() == cmd; }); Q_ASSERT(it != nohupedCommands.end()); nohupedCommands.erase(it); if (nohupedCommands.empty() && closed) { bottomHalfDeletion(); } } void commandDone(AssuanCommand *cmd) { if (!cmd || cmd != currentCommand.get()) { return; } currentCommand.reset(); } void topHalfDeletion() { if (currentCommand) { currentCommand->canceled(); } if (fd != ASSUAN_INVALID_FD) { #if defined(Q_OS_WIN32) CloseHandle(fd); #else ::close(fd); #endif } notifiers.clear(); closed = true; } void bottomHalfDeletion() { if (sessionId) { SessionDataHandler::instance()->exitSession(sessionId); } cleanup(); const QPointer that = this; Q_EMIT q->closed(q); if (that) { // still there q->deleteLater(); } } private: -#ifndef HAVE_ASSUAN2 - static void reset_handler(assuan_context_t ctx_) - { -#else static gpg_error_t reset_handler(assuan_context_t ctx_, char *) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); conn.reset(); -#ifdef HAVE_ASSUAN2 return 0; -#endif } -#ifndef HAVE_ASSUAN2 - static int option_handler(assuan_context_t ctx_, const char *key, const char *value) - { -#else static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (key && key[0] == '-' && key[1] == '-') { key += 2; // skip "--" } conn.options[key] = QString::fromUtf8(value); return 0; //return gpg_error( GPG_ERR_UNKNOWN_OPTION ); } -#ifndef HAVE_ASSUAN2 - static int session_handler(assuan_context_t ctx_, char *line) - { -#else static gpg_error_t session_handler(assuan_context_t ctx_, char *line) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); const QString str = QString::fromUtf8(line); static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\d+)(?:\s+(.*))?)")); const QRegularExpressionMatch match = rx.match(str); if (!match.hasMatch()) { static const QString errorString = i18n("Parse error"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString); } bool ok = false; if (const qulonglong id = match.captured(1).toULongLong(&ok)) { if (ok && id <= std::numeric_limits::max()) { SessionDataHandler::instance()->enterSession(id); conn.sessionId = id; } else { static const QString errorString = i18n("Parse error: numeric session id too large"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString); } } const QString cap2 = match.captured(2); if (!cap2.isEmpty()) { conn.sessionTitle = cap2; } qCDebug(KLEOPATRA_LOG) << "session_handler: " << "id=" << static_cast(conn.sessionId) << ", title=" << qPrintable(conn.sessionTitle); return assuan_process_done(ctx_, 0); } -#ifndef HAVE_ASSUAN2 - static int capabilities_handler(assuan_context_t ctx_, char *line) - { -#else static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line) { -#endif if (!QByteArray(line).trimmed().isEmpty()) { static const QString errorString = i18n("CAPABILITIES does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } static const char capabilities[] = "SENDER=info\n" "RECIPIENT=info\n" "SESSION\n" ; return assuan_process_done(ctx_, assuan_send_data(ctx_, capabilities, sizeof capabilities - 1)); } -#ifndef HAVE_ASSUAN2 - static int getinfo_handler(assuan_context_t ctx_, char *line) - { -#else static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (qstrcmp(line, "version") == 0) { static const char version[] = "Kleopatra " KLEOPATRA_VERSION_STRING; return assuan_process_done(ctx_, assuan_send_data(ctx_, version, sizeof version - 1)); } QByteArray ba; if (qstrcmp(line, "pid") == 0) { ba = QByteArray::number(QCoreApplication::applicationPid()); } else if (qstrcmp(line, "options") == 0) { ba = conn.dumpOptions(); } else if (qstrcmp(line, "x-mementos") == 0) { ba = conn.dumpMementos(); } else if (qstrcmp(line, "senders") == 0) { ba = conn.dumpSenders(); } else if (qstrcmp(line, "recipients") == 0) { ba = conn.dumpRecipients(); } else if (qstrcmp(line, "x-files") == 0) { ba = conn.dumpFiles(); } else { static const QString errorString = i18n("Unknown value for WHAT"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } return assuan_process_done(ctx_, assuan_send_data(ctx_, ba.constData(), ba.size())); } -#ifndef HAVE_ASSUAN2 - static int start_keymanager_handler(assuan_context_t ctx_, char *line) - { -#else static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (line && *line) { static const QString errorString = i18n("START_KEYMANAGER does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } Q_EMIT conn.q->startKeyManagerRequested(); return assuan_process_done(ctx_, 0); } -#ifndef HAVE_ASSUAN2 - static int start_confdialog_handler(assuan_context_t ctx_, char *line) - { -#else static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (line && *line) { static const QString errorString = i18n("START_CONFDIALOG does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } Q_EMIT conn.q->startConfigDialogRequested(); return assuan_process_done(ctx_, 0); } template struct Input_or_Output : std::conditional {}; // format: TAG (FD|FD=\d+|FILE=...) template -#ifndef HAVE_ASSUAN2 - static int IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) - { -#else static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); char *binOpt = strstr(line_, "--binary"); if (binOpt && !in) { /* Note there is also --armor and --base64 allowed but we don't need * to parse those because they are default. * We remove it here so that it is not parsed as an Option.*/ memset(binOpt, ' ', 8); } try { /*const*/ std::map options = upcase_option("FD", upcase_option("FILE", parse_commandline(line_))); if (options.size() < 1 || options.size() > 2) { throw gpg_error(GPG_ERR_ASS_SYNTAX); } std::shared_ptr< typename Input_or_Output::type > io; if (options.count("FD")) { if (options.count("FILE")) { throw gpg_error(GPG_ERR_CONFLICT); } assuan_fd_t fd = ASSUAN_INVALID_FD; const std::string fdstr = options["FD"]; if (fdstr.empty()) { if (const gpg_error_t err = assuan_receivefd(conn.ctx.get(), &fd)) { throw err; } } else { #if defined(Q_OS_WIN32) fd = (assuan_fd_t)std::stoi(fdstr); #else fd = std::stoi(fdstr); #endif } io = Input_or_Output::type::createFromPipeDevice(fd, in ? i18n("Message #%1", (conn.*which).size() + 1) : QString()); options.erase("FD"); } else if (options.count("FILE")) { if (options.count("FD")) { throw gpg_error(GPG_ERR_CONFLICT); } const QString filePath = QFile::decodeName(options["FILE"].c_str()); if (filePath.isEmpty()) { throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Empty file path")); } const QFileInfo fi(filePath); if (!fi.isAbsolute()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed")); } if (!fi.isFile()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only files are allowed in INPUT/OUTPUT FILE")); } else { io = Input_or_Output::type::createFromFile(fi.absoluteFilePath(), true); } options.erase("FILE"); } else { throw gpg_error(GPG_ERR_ASS_PARAMETER); } if (options.size()) { throw gpg_error(GPG_ERR_UNKNOWN_OPTION); } (conn.*which).push_back(io); if (binOpt && !in) { auto out = reinterpret_cast (io.get()); out->setBinaryOpt(true); qCDebug(KLEOPATRA_LOG) << "Configured output for binary data"; } qCDebug(KLEOPATRA_LOG) << "AssuanServerConnection: added" << io->label(); return assuan_process_done(conn.ctx.get(), 0); } catch (const GpgME::Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().c_str()); } catch (const std::exception &) { return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_ASS_SYNTAX)); } catch (const gpg_error_t &e) { return assuan_process_done(conn.ctx.get(), e); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), "unknown exception caught"); } } -#ifndef HAVE_ASSUAN2 - static int input_handler(assuan_context_t ctx, char *line) - { -#else static gpg_error_t input_handler(assuan_context_t ctx, char *line) { -#endif return IO_handler(ctx, line, &Private::inputs); } -#ifndef HAVE_ASSUAN2 - static int output_handler(assuan_context_t ctx, char *line) - { -#else static gpg_error_t output_handler(assuan_context_t ctx, char *line) { -#endif return IO_handler(ctx, line, &Private::outputs); } -#ifndef HAVE_ASSUAN2 - static int message_handler(assuan_context_t ctx, char *line) - { -#else static gpg_error_t message_handler(assuan_context_t ctx, char *line) { -#endif return IO_handler(ctx, line, &Private::messages); } -#ifndef HAVE_ASSUAN2 - static int file_handler(assuan_context_t ctx_, char *line) - { -#else static gpg_error_t file_handler(assuan_context_t ctx_, char *line) { -#endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); try { const QFileInfo fi(QFile::decodeName(hexdecode(line).c_str())); if (!fi.isAbsolute()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed")); } if (!fi.exists()) { throw gpg_error(GPG_ERR_ENOENT); } if (!fi.isReadable() || (fi.isDir() && !fi.isExecutable())) { throw gpg_error(GPG_ERR_EPERM); } conn.files.push_back(fi.absoluteFilePath()); return assuan_process_done(conn.ctx.get(), 0); } catch (const Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().toUtf8().constData()); } catch (const gpg_error_t &e) { return assuan_process_done(conn.ctx.get(), e); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("unknown exception caught").toUtf8().constData()); } } static bool parse_informative(const char *&begin, GpgME::Protocol &protocol) { protocol = GpgME::UnknownProtocol; bool informative = false; const char *pos = begin; while (true) { while (*pos == ' ' || *pos == '\t') { ++pos; } if (qstrnicmp(pos, "--info", strlen("--info")) == 0) { informative = true; pos += strlen("--info"); if (*pos == '=') { ++pos; break; } } else if (qstrnicmp(pos, "--protocol=", strlen("--protocol=")) == 0) { pos += strlen("--protocol="); if (qstrnicmp(pos, "OpenPGP", strlen("OpenPGP")) == 0) { protocol = GpgME::OpenPGP; pos += strlen("OpenPGP"); } else if (qstrnicmp(pos, "CMS", strlen("CMS")) == 0) { protocol = GpgME::CMS; pos += strlen("CMS"); } else { ; } } else if (qstrncmp(pos, "-- ", strlen("-- ")) == 0) { pos += 3; while (*pos == ' ' || *pos == '\t') { ++pos; } break; } else { break; } } begin = pos; return informative; } template -#ifndef HAVE_ASSUAN2 - static int recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) - { -#else static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) { -#endif Q_ASSERT(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); if (!line || !*line) { return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG)); } const char *begin = line; const char *const end = begin + qstrlen(line); GpgME::Protocol proto = GpgME::UnknownProtocol; const bool informative = parse_informative(begin, proto); if (!(conn.*mp).empty() && informative != (conn.*info)) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_CONFLICT), i18n("Cannot mix --info with non-info SENDER or RECIPIENT").toUtf8().constData()); KMime::Types::Mailbox mb; if (!KMime::HeaderParsing::parseMailbox(begin, end, mb)) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Argument is not a valid RFC-2822 mailbox").toUtf8().constData()); if (begin != end) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Garbage after valid RFC-2822 mailbox detected").toUtf8().constData()); (conn.*info) = informative; (conn.*mp).push_back(mb); const QString email = mb.addrSpec().asString(); (void)assuan_write_line(conn.ctx.get(), qPrintable(QString::asprintf("# ok, parsed as \"%s\"", qPrintable(email)))); if (sender && !informative) { return AssuanCommandFactory::_handle(conn.ctx.get(), line, "PREP_SIGN"); } else { return assuan_process_done(ctx, 0); } } -#ifndef HAVE_ASSUAN2 - static int recipient_handler(assuan_context_t ctx, char *line) - { -#else static gpg_error_t recipient_handler(assuan_context_t ctx, char *line) { -#endif return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line); } -#ifndef HAVE_ASSUAN2 - static int sender_handler(assuan_context_t ctx, char *line) - { -#else static gpg_error_t sender_handler(assuan_context_t ctx, char *line) { -#endif return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true); } QByteArray dumpOptions() const { QByteArray result; for (auto it = options.begin(), end = options.end(); it != end; ++it) { result += it->first.c_str() + it->second.toString().toUtf8() + '\n'; } return result; } static QByteArray dumpStringList(const QStringList &sl) { return sl.join(QLatin1Char('\n')).toUtf8(); } template static QByteArray dumpStringList(const T_container &c) { QStringList sl; std::copy(c.begin(), c.end(), std::back_inserter(sl)); return dumpStringList(sl); } template static QByteArray dumpMailboxes(const T_container &c) { QStringList sl; std::transform(c.begin(), c.end(), std::back_inserter(sl), [](typename T_container::const_reference val) { return val.prettyAddress(); }); return dumpStringList(sl); } QByteArray dumpSenders() const { return dumpMailboxes(senders); } QByteArray dumpRecipients() const { return dumpMailboxes(recipients); } QByteArray dumpMementos() const { QByteArray result; for (auto it = mementos.begin(), end = mementos.end(); it != end; ++it) { char buf[2 + 2 * sizeof(void *) + 2]; sprintf(buf, "0x%p\n", (void *)it->second.get()); buf[sizeof(buf) - 1] = '\0'; result += it->first + QByteArray::fromRawData(buf, sizeof buf); } return result; } QByteArray dumpFiles() const { QStringList rv; rv.reserve(files.size()); std::copy(files.cbegin(), files.cend(), std::back_inserter(rv)); return dumpStringList(rv); } void cleanup(); void reset() { options.clear(); senders.clear(); informativeSenders = false; recipients.clear(); informativeRecipients = false; sessionTitle.clear(); sessionId = 0; mementos.clear(); files.clear(); std::for_each(inputs.begin(), inputs.end(), std::mem_fn(&Input::finalize)); inputs.clear(); std::for_each(outputs.begin(), outputs.end(), std::mem_fn(&Output::finalize)); outputs.clear(); std::for_each(messages.begin(), messages.end(), std::mem_fn(&Input::finalize)); messages.clear(); bias = GpgME::UnknownProtocol; } assuan_fd_t fd; AssuanContext ctx; bool closed : 1; bool cryptoCommandsEnabled : 1; bool commandWaitingForCryptoCommandsEnabled : 1; bool currentCommandIsNohup : 1; bool informativeSenders; // address taken, so no : 1 bool informativeRecipients; // address taken, so no : 1 GpgME::Protocol bias; QString sessionTitle; unsigned int sessionId; std::vector< std::shared_ptr > notifiers; std::vector< std::shared_ptr > factories; // sorted: _detail::ByName std::shared_ptr currentCommand; std::vector< std::shared_ptr > nohupedCommands; std::map options; std::vector senders, recipients; std::vector< std::shared_ptr > inputs, messages; std::vector< std::shared_ptr > outputs; std::vector files; std::map< QByteArray, std::shared_ptr > mementos; }; void AssuanServerConnection::Private::cleanup() { Q_ASSERT(nohupedCommands.empty()); reset(); currentCommand.reset(); currentCommandIsNohup = false; commandWaitingForCryptoCommandsEnabled = false; notifiers.clear(); ctx.reset(); fd = ASSUAN_INVALID_FD; } AssuanServerConnection::Private::Private(assuan_fd_t fd_, const std::vector< std::shared_ptr > &factories_, AssuanServerConnection *qq) : QObject(), q(qq), fd(fd_), closed(false), cryptoCommandsEnabled(false), commandWaitingForCryptoCommandsEnabled(false), currentCommandIsNohup(false), informativeSenders(false), informativeRecipients(false), bias(GpgME::UnknownProtocol), sessionId(0), factories(factories_) { #ifdef __GLIBCXX__ Q_ASSERT(__gnu_cxx::is_sorted(factories_.begin(), factories_.end(), _detail::ByName())); #endif if (fd == ASSUAN_INVALID_FD) { throw Exception(gpg_error(GPG_ERR_INV_ARG), "pre-assuan_init_socket_server_ext"); } -#ifndef HAVE_ASSUAN2 - assuan_context_t naked_ctx = 0; - if (const gpg_error_t err = assuan_init_socket_server_ext(&naked_ctx, fd, INIT_SOCKET_FLAGS)) -#else { assuan_context_t naked_ctx = nullptr; if (const gpg_error_t err = assuan_new(&naked_ctx)) { throw Exception(err, "assuan_new"); } ctx.reset(naked_ctx); } if (const gpg_error_t err = assuan_init_socket_server(ctx.get(), fd, INIT_SOCKET_FLAGS)) -#endif throw Exception(err, "assuan_init_socket_server_ext"); -#ifndef HAVE_ASSUAN2 - ctx.reset(naked_ctx); naked_ctx = 0; -#endif - // for callbacks, associate the context with this connection: assuan_set_pointer(ctx.get(), this); FILE *const logFile = Log::instance()->logFile(); assuan_set_log_stream(ctx.get(), logFile ? logFile : stderr); // register FDs with the event loop: assuan_fd_t fds[MAX_ACTIVE_FDS]; const int numFDs = assuan_get_active_fds(ctx.get(), FOR_READING, fds, MAX_ACTIVE_FDS); Q_ASSERT(numFDs != -1); // == 1 if (!numFDs || fds[0] != fd) { const std::shared_ptr sn(new QSocketNotifier((intptr_t)fd, QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater)); connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity); notifiers.push_back(sn); } notifiers.reserve(notifiers.size() + numFDs); for (int i = 0; i < numFDs; ++i) { const std::shared_ptr sn(new QSocketNotifier((intptr_t)fds[i], QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater)); connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity); notifiers.push_back(sn); } // register our INPUT/OUTPUT/MESSGAE/FILE handlers: -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, "")) -#endif throw Exception(err, "register \"INPUT\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, "")) -#endif throw Exception(err, "register \"MESSAGE\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, "")) -#endif throw Exception(err, "register \"OUTPUT\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, "")) -#endif throw Exception(err, "register \"FILE\" handler"); // register user-defined commands: for (std::shared_ptr fac : std::as_const(factories)) -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler())) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), "")) -#endif throw Exception(err, std::string("register \"") + fac->name() + "\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, "")) -#endif throw Exception(err, "register \"GETINFO\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, "")) -#endif throw Exception(err, "register \"START_KEYMANAGER\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, "")) -#endif throw Exception(err, "register \"START_CONFDIALOG\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, "")) -#endif throw Exception(err, "register \"RECIPIENT\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, "")) -#endif throw Exception(err, "register \"SENDER\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, "")) -#endif throw Exception(err, "register \"SESSION\" handler"); -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler)) -#else if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, "")) -#endif throw Exception(err, "register \"CAPABILITIES\" handler"); assuan_set_hello_line(ctx.get(), "GPG UI server (Kleopatra/" KLEOPATRA_VERSION_STRING ") ready to serve"); //assuan_set_hello_line( ctx.get(), GPG UI server (qApp->applicationName() + " v" + kapp->applicationVersion() + "ready to serve" ) // some notifiers we're interested in: if (const gpg_error_t err = assuan_register_reset_notify(ctx.get(), reset_handler)) { throw Exception(err, "register reset notify"); } if (const gpg_error_t err = assuan_register_option_handler(ctx.get(), option_handler)) { throw Exception(err, "register option handler"); } // and last, we need to call assuan_accept, which doesn't block // (d/t INIT_SOCKET_FLAGS), but performs vital connection // establishing handling: if (const gpg_error_t err = assuan_accept(ctx.get())) { throw Exception(err, "assuan_accept"); } } AssuanServerConnection::Private::~Private() { cleanup(); } AssuanServerConnection::AssuanServerConnection(assuan_fd_t fd, const std::vector< std::shared_ptr > &factories, QObject *p) : QObject(p), d(new Private(fd, factories, this)) { } AssuanServerConnection::~AssuanServerConnection() {} void AssuanServerConnection::enableCryptoCommands(bool on) { if (on == d->cryptoCommandsEnabled) { return; } d->cryptoCommandsEnabled = on; if (d->commandWaitingForCryptoCommandsEnabled) { QTimer::singleShot(0, d.get(), &Private::startCommandBottomHalf); } } // // // AssuanCommand: // // namespace Kleo { class InquiryHandler : public QObject { Q_OBJECT public: -#if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) explicit InquiryHandler(const char *keyword_, QObject *p = nullptr) : QObject(p), -# if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) - buffer(0), - buflen(0), -# endif keyword(keyword_) { } -# if defined(HAVE_ASSUAN2) || defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) -# ifndef HAVE_ASSUAN2 - static int handler(void *cb_data, int rc, unsigned char *buffer, size_t buflen) -# else static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen) -# endif { Q_ASSERT(cb_data); auto this_ = static_cast(cb_data); Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast(buffer), buflen), this_->keyword); std::free(buffer); delete this_; return 0; } -# else - static int handler(void *cb_data, int rc) - { - Q_ASSERT(cb_data); - InquiryHandler *this_ = static_cast(cb_data); - Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast(this_->buffer), this_->buflen), this_->keyword); - std::free(this_->buffer); - delete this_; - return 0; - } -# endif private: -#if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) - friend class ::Kleo::AssuanCommand; - unsigned char *buffer; - size_t buflen; -#endif const char *keyword; -#endif // defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) Q_SIGNALS: void signal(int rc, const QByteArray &data, const QByteArray &keyword); }; } // namespace Kleo class AssuanCommand::Private { public: Private() : informativeRecipients(false), informativeSenders(false), bias(GpgME::UnknownProtocol), done(false), nohup(false) { } std::map options; std::vector< std::shared_ptr > inputs, messages; std::vector< std::shared_ptr > outputs; std::vector files; std::vector recipients, senders; bool informativeRecipients, informativeSenders; GpgME::Protocol bias; QString sessionTitle; unsigned int sessionId; QByteArray utf8ErrorKeepAlive; AssuanContext ctx; bool done; bool nohup; }; AssuanCommand::AssuanCommand() : d(new Private) { } AssuanCommand::~AssuanCommand() { } int AssuanCommand::start() { try { if (const int err = doStart()) if (!d->done) { done(err); } return 0; } catch (const Exception &e) { if (!d->done) { done(e.error_code(), e.message()); } return 0; } catch (const GpgME::Exception &e) { if (!d->done) { done(e.error(), QString::fromLocal8Bit(e.message().c_str())); } return 0; } catch (const std::exception &e) { if (!d->done) { done(makeError(GPG_ERR_INTERNAL), i18n("Caught unexpected exception: %1", QString::fromLocal8Bit(e.what()))); } return 0; } catch (...) { if (!d->done) { done(makeError(GPG_ERR_INTERNAL), i18n("Caught unknown exception - please report this error to the developers.")); } return 0; } } void AssuanCommand::canceled() { d->done = true; doCanceled(); } // static int AssuanCommand::makeError(int code) { return makeGnuPGError(code); } bool AssuanCommand::hasOption(const char *opt) const { return d->options.count(opt); } QVariant AssuanCommand::option(const char *opt) const { const auto it = d->options.find(opt); if (it == d->options.end()) { return QVariant(); } else { return it->second; } } const std::map &AssuanCommand::options() const { return d->options; } namespace { template std::vector keys(const std::map &map) { std::vector result; result.resize(map.size()); for (typename std::map::const_iterator it = map.begin(), end = map.end(); it != end; ++it) { result.push_back(it->first); } return result; } } const std::map< QByteArray, std::shared_ptr > &AssuanCommand::mementos() const { // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); const AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); return conn.mementos; } bool AssuanCommand::hasMemento(const QByteArray &tag) const { if (const unsigned int id = sessionId()) { return SessionDataHandler::instance()->sessionData(id)->mementos.count(tag) || mementos().count(tag); } else { return mementos().count(tag); } } std::shared_ptr AssuanCommand::memento(const QByteArray &tag) const { if (const unsigned int id = sessionId()) { const std::shared_ptr sdh = SessionDataHandler::instance(); const std::shared_ptr sd = sdh->sessionData(id); const auto it = sd->mementos.find(tag); if (it != sd->mementos.end()) { return it->second; } } const auto it = mementos().find(tag); if (it == mementos().end()) { return std::shared_ptr(); } else { return it->second; } } QByteArray AssuanCommand::registerMemento(const std::shared_ptr &mem) { const QByteArray tag = QByteArray::number(reinterpret_cast(mem.get()), 36); return registerMemento(tag, mem); } QByteArray AssuanCommand::registerMemento(const QByteArray &tag, const std::shared_ptr &mem) { // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); if (const unsigned int id = sessionId()) { SessionDataHandler::instance()->sessionData(id)->mementos[tag] = mem; } else { conn.mementos[tag] = mem; } return tag; } void AssuanCommand::removeMemento(const QByteArray &tag) { // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); conn.mementos.erase(tag); if (const unsigned int id = sessionId()) { SessionDataHandler::instance()->sessionData(id)->mementos.erase(tag); } } const std::vector< std::shared_ptr > &AssuanCommand::inputs() const { return d->inputs; } const std::vector< std::shared_ptr > &AssuanCommand::messages() const { return d->messages; } const std::vector< std::shared_ptr > &AssuanCommand::outputs() const { return d->outputs; } QStringList AssuanCommand::fileNames() const { QStringList rv; rv.reserve(d->files.size()); std::copy(d->files.cbegin(), d->files.cend(), std::back_inserter(rv)); return rv; } unsigned int AssuanCommand::numFiles() const { return d->files.size(); } void AssuanCommand::sendStatus(const char *keyword, const QString &text) { sendStatusEncoded(keyword, text.toUtf8().constData()); } void AssuanCommand::sendStatusEncoded(const char *keyword, const std::string &text) { if (d->nohup) { return; } if (const int err = assuan_write_status(d->ctx.get(), keyword, text.c_str())) { throw Exception(err, i18n("Cannot send \"%1\" status", QString::fromLatin1(keyword))); } } void AssuanCommand::sendData(const QByteArray &data, bool moreToCome) { if (d->nohup) { return; } if (const gpg_error_t err = assuan_send_data(d->ctx.get(), data.constData(), data.size())) { throw Exception(err, i18n("Cannot send data")); } if (!moreToCome) if (const gpg_error_t err = assuan_send_data(d->ctx.get(), nullptr, 0)) { // flush throw Exception(err, i18n("Cannot flush data")); } } int AssuanCommand::inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize) { Q_ASSERT(keyword); Q_ASSERT(receiver); Q_ASSERT(slot); if (d->nohup) { return makeError(GPG_ERR_INV_OP); } -#if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) std::unique_ptr ih(new InquiryHandler(keyword, receiver)); receiver->connect(ih.get(), SIGNAL(signal(int,QByteArray,QByteArray)), slot); if (const gpg_error_t err = assuan_inquire_ext(d->ctx.get(), keyword, -# if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) - &ih->buffer, &ih->buflen, -# endif maxSize, InquiryHandler::handler, ih.get())) { return err; } ih.release(); return 0; -#else - return makeError(GPG_ERR_NOT_SUPPORTED); // libassuan too old -#endif // defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) } void AssuanCommand::done(const GpgME::Error &err, const QString &details) { if (d->ctx && !d->done && !details.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Error: " << details; d->utf8ErrorKeepAlive = details.toUtf8(); if (!d->nohup) { assuan_set_error(d->ctx.get(), err.encodedError(), d->utf8ErrorKeepAlive.constData()); } } done(err); } void AssuanCommand::done(const GpgME::Error &err) { if (!d->ctx) { qCDebug(KLEOPATRA_LOG) << err.asString() << ": called with NULL ctx."; return; } if (d->done) { qCDebug(KLEOPATRA_LOG) << err.asString() << ": called twice!"; return; } d->done = true; std::for_each(d->messages.begin(), d->messages.end(), std::mem_fn(&Input::finalize)); std::for_each(d->inputs.begin(), d->inputs.end(), std::mem_fn(&Input::finalize)); std::for_each(d->outputs.begin(), d->outputs.end(), std::mem_fn(&Output::finalize)); d->messages.clear(); d->inputs.clear(); d->outputs.clear(); d->files.clear(); // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); if (d->nohup) { conn.nohupDone(this); return; } const gpg_error_t rc = assuan_process_done(d->ctx.get(), err.encodedError()); if (gpg_err_code(rc) != GPG_ERR_NO_ERROR) qFatal("AssuanCommand::done: assuan_process_done returned error %d (%s)", static_cast(rc), gpg_strerror(rc)); d->utf8ErrorKeepAlive.clear(); conn.commandDone(this); } void AssuanCommand::setNohup(bool nohup) { d->nohup = nohup; } bool AssuanCommand::isNohup() const { return d->nohup; } bool AssuanCommand::isDone() const { return d->done; } QString AssuanCommand::sessionTitle() const { return d->sessionTitle; } unsigned int AssuanCommand::sessionId() const { return d->sessionId; } bool AssuanCommand::informativeSenders() const { return d->informativeSenders; } bool AssuanCommand::informativeRecipients() const { return d->informativeRecipients; } const std::vector &AssuanCommand::recipients() const { return d->recipients; } const std::vector &AssuanCommand::senders() const { return d->senders; } -#ifndef HAVE_ASSUAN2 -int AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) -{ -#else gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) { -#endif Q_ASSERT(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); try { const auto it = std::lower_bound(conn.factories.begin(), conn.factories.end(), commandName, _detail::ByName()); kleo_assert(it != conn.factories.end()); kleo_assert(*it); kleo_assert(qstricmp((*it)->name(), commandName) == 0); const std::shared_ptr cmd = (*it)->create(); kleo_assert(cmd); cmd->d->ctx = conn.ctx; cmd->d->options = conn.options; cmd->d->inputs.swap(conn.inputs); kleo_assert(conn.inputs.empty()); cmd->d->messages.swap(conn.messages); kleo_assert(conn.messages.empty()); cmd->d->outputs.swap(conn.outputs); kleo_assert(conn.outputs.empty()); cmd->d->files.swap(conn.files); kleo_assert(conn.files.empty()); cmd->d->senders.swap(conn.senders); kleo_assert(conn.senders.empty()); cmd->d->recipients.swap(conn.recipients); kleo_assert(conn.recipients.empty()); cmd->d->informativeRecipients = conn.informativeRecipients; cmd->d->informativeSenders = conn.informativeSenders; cmd->d->bias = conn.bias; cmd->d->sessionTitle = conn.sessionTitle; cmd->d->sessionId = conn.sessionId; const std::map cmdline_options = parse_commandline(line); for (auto it = cmdline_options.begin(), end = cmdline_options.end(); it != end; ++it) { cmd->d->options[it->first] = QString::fromUtf8(it->second.c_str()); } bool nohup = false; if (cmd->d->options.count("nohup")) { if (!cmd->d->options["nohup"].toString().isEmpty()) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_ASS_PARAMETER), "--nohup takes no argument"); } nohup = true; cmd->d->options.erase("nohup"); } conn.currentCommand = cmd; conn.currentCommandIsNohup = nohup; QTimer::singleShot(0, &conn, &AssuanServerConnection::Private::startCommandBottomHalf); return 0; } catch (const Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error_code(), e.message()); } catch (const std::exception &e) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what()); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception")); } } int AssuanServerConnection::Private::startCommandBottomHalf() { commandWaitingForCryptoCommandsEnabled = currentCommand && !cryptoCommandsEnabled; if (!cryptoCommandsEnabled) { return 0; } const std::shared_ptr cmd = currentCommand; if (!cmd) { return 0; } currentCommand.reset(); const bool nohup = currentCommandIsNohup; currentCommandIsNohup = false; try { if (const int err = cmd->start()) { if (cmd->isDone()) { return err; } else { return assuan_process_done(ctx.get(), err); } } if (cmd->isDone()) { return 0; } if (nohup) { cmd->setNohup(true); nohupedCommands.push_back(cmd); return assuan_process_done_msg(ctx.get(), 0, "Command put in the background to continue executing after connection end."); } else { currentCommand = cmd; return 0; } } catch (const Exception &e) { return assuan_process_done_msg(ctx.get(), e.error_code(), e.message()); } catch (const std::exception &e) { return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what()); } catch (...) { return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception")); } } // // // AssuanCommand convenience methods // // /*! Checks the \c --mode parameter. \returns The parameter as an AssuanCommand::Mode enum value. If no \c --mode was given, or it's value wasn't recognized, throws an Kleo::Exception. */ AssuanCommand::Mode AssuanCommand::checkMode() const { if (!hasOption("mode")) { throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --mode option missing")); } const QString modeString = option("mode").toString().toLower(); if (modeString == QLatin1String("filemanager")) { return FileManager; } if (modeString == QLatin1String("email")) { return EMail; } throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid mode: \"%1\"", modeString)); } /*! Checks the \c --protocol parameter. \returns The parameter as a GpgME::Protocol enum value. If \c --protocol was given, but has an invalid value, throws an Kleo::Exception. If no \c --protocol was given, checks the connection bias, if available, otherwise, in FileManager mode, returns GpgME::UnknownProtocol, but if \a mode == \c EMail, throws an Kleo::Exception instead. */ GpgME::Protocol AssuanCommand::checkProtocol(Mode mode, int options) const { if (!hasOption("protocol")) if (d->bias != GpgME::UnknownProtocol) { return d->bias; } else if (mode == AssuanCommand::EMail && (options & AllowProtocolMissing) == 0) { throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --protocol option missing")); } else { return GpgME::UnknownProtocol; } else if (mode == AssuanCommand::FileManager) { throw Exception(makeError(GPG_ERR_INV_FLAG), i18n("--protocol is not allowed here")); } const QString protocolString = option("protocol").toString().toLower(); if (protocolString == QLatin1String("openpgp")) { return GpgME::OpenPGP; } if (protocolString == QLatin1String("cms")) { return GpgME::CMS; } throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid protocol \"%1\"", protocolString)); } void AssuanCommand::doApplyWindowID(QWidget *widget) const { if (!widget || !hasOption("window-id")) { return; } apply_window_id(widget, option("window-id").toString()); } WId AssuanCommand::parentWId() const { return wid_from_string(option("window-id").toString()); } #include "assuanserverconnection.moc" diff --git a/src/uiserver/uiserver.cpp b/src/uiserver/uiserver.cpp index ab4d2ca71..5901de354 100644 --- a/src/uiserver/uiserver.cpp +++ b/src/uiserver/uiserver.cpp @@ -1,297 +1,283 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/uiserver.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "uiserver.h" #include "uiserver_p.h" #include "sessiondata.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include using namespace Kleo; // static void UiServer::setLogStream(FILE *stream) { assuan_set_assuan_log_stream(stream); } UiServer::Private::Private(UiServer *qq) : QTcpServer(), q(qq), file(), factories(), connections(), suggestedSocketName(), actualSocketName(), cryptoCommandsEnabled(false) { -#ifndef HAVE_ASSUAN2 - assuan_set_assuan_err_source(GPG_ERR_SOURCE_DEFAULT); -#else assuan_set_gpg_err_source(GPG_ERR_SOURCE_DEFAULT); assuan_sock_init(); -#endif } bool UiServer::Private::isStaleAssuanSocket(const QString &fileName) { assuan_context_t ctx = nullptr; -#ifndef HAVE_ASSUAN2 - const bool error = assuan_socket_connect_ext(&ctx, QFile::encodeName(fileName).constData(), -1, 0); -#else const bool error = assuan_new(&ctx) || assuan_socket_connect(ctx, QFile::encodeName(fileName).constData(), ASSUAN_INVALID_PID, 0); -#endif if (!error) -#ifndef HAVE_ASSUAN2 - assuan_disconnect(ctx); -#else assuan_release(ctx); -#endif return error; } UiServer::UiServer(const QString &socket, QObject *p) : QObject(p), d(new Private(this)) { d->suggestedSocketName = d->makeFileName(socket); } UiServer::~UiServer() { if (QFile::exists(d->actualSocketName)) { QFile::remove(d->actualSocketName); } } namespace { using Iterator = std::vector>::iterator; static bool empty(std::pair iters) { return iters.first == iters.second; } } bool UiServer::registerCommandFactory(const std::shared_ptr &cf) { if (cf && empty(std::equal_range(d->factories.begin(), d->factories.end(), cf, _detail::ByName()))) { d->factories.push_back(cf); std::inplace_merge(d->factories.begin(), d->factories.end() - 1, d->factories.end(), _detail::ByName()); return true; } else { if (!cf) { qCWarning(KLEOPATRA_LOG) << "NULL factory"; } else { qCWarning(KLEOPATRA_LOG) << (void *)cf.get() << " factory already registered"; } return false; } } void UiServer::start() { d->makeListeningSocket(); } void UiServer::stop() { d->close(); if (d->file.exists()) { d->file.remove(); } if (isStopped()) { SessionDataHandler::instance()->clear(); Q_EMIT stopped(); } } void UiServer::enableCryptoCommands(bool on) { if (on == d->cryptoCommandsEnabled) { return; } d->cryptoCommandsEnabled = on; std::for_each(d->connections.cbegin(), d->connections.cend(), [on](std::shared_ptr conn) { conn->enableCryptoCommands(on); }); } QString UiServer::socketName() const { return d->actualSocketName; } bool UiServer::waitForStopped(unsigned int ms) { if (isStopped()) { return true; } QEventLoop loop; QTimer timer; timer.setInterval(ms); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(this, &UiServer::stopped, &loop, &QEventLoop::quit); loop.exec(); return !timer.isActive(); } bool UiServer::isStopped() const { return d->connections.empty() && !d->isListening(); } bool UiServer::isStopping() const { return !d->connections.empty() && !d->isListening(); } void UiServer::Private::slotConnectionClosed(Kleo::AssuanServerConnection *conn) { qCDebug(KLEOPATRA_LOG) << "UiServer: connection " << (void *)conn << " closed"; connections.erase(std::remove_if(connections.begin(), connections.end(), [conn](const std::shared_ptr &other) { return conn == other.get(); }), connections.end()); if (q->isStopped()) { SessionDataHandler::instance()->clear(); Q_EMIT q->stopped(); } } void UiServer::Private::incomingConnection(qintptr fd) { try { qCDebug(KLEOPATRA_LOG) << "UiServer: client connect on fd " << fd; -#if defined(HAVE_ASSUAN_SOCK_GET_NONCE) || defined(HAVE_ASSUAN2) if (assuan_sock_check_nonce((assuan_fd_t)fd, &nonce)) { qCDebug(KLEOPATRA_LOG) << "UiServer: nonce check failed"; assuan_sock_close((assuan_fd_t)fd); return; } -#endif const std::shared_ptr c(new AssuanServerConnection((assuan_fd_t)fd, factories)); connect(c.get(), &AssuanServerConnection::closed, this, &Private::slotConnectionClosed); connect(c.get(), &AssuanServerConnection::startKeyManagerRequested, q, &UiServer::startKeyManagerRequested, Qt::QueuedConnection); connect(c.get(), &AssuanServerConnection::startConfigDialogRequested, q, &UiServer::startConfigDialogRequested, Qt::QueuedConnection); c->enableCryptoCommands(cryptoCommandsEnabled); connections.push_back(c); qCDebug(KLEOPATRA_LOG) << "UiServer: client connection " << (void *)c.get() << " established successfully"; } catch (const Exception &e) { qCDebug(KLEOPATRA_LOG) << "UiServer: client connection failed: " << e.what(); QTcpSocket s; s.setSocketDescriptor(fd); QTextStream(&s) << "ERR " << e.error_code() << " " << e.what() << "\r\n"; s.waitForBytesWritten(); s.close(); } catch (...) { qCDebug(KLEOPATRA_LOG) << "UiServer: client connection failed: unknown exception caught"; // this should never happen... QTcpSocket s; s.setSocketDescriptor(fd); QTextStream(&s) << "ERR 63 unknown exception caught\r\n"; s.waitForBytesWritten(); s.close(); } } QString UiServer::Private::makeFileName(const QString &socket) const { if (!socket.isEmpty()) { return socket; } const QString socketPath{QString::fromUtf8(GpgME::dirInfo("uiserver-socket"))}; if (!socketPath.isEmpty()) { // Note: The socket directory exists after GpgME::dirInfo() has been called. return socketPath; } // GPGME (or GnuPG) is too old to return the socket path. // In this case we fallback to assume that the socket directory is // the home directory as we did in the past. This is not correct but // probably the safest fallback we can do despite that it is a // bug to assume the socket directory in the home directory. See // https://dev.gnupg.org/T5613 const QString gnupgHome = gnupgHomeDirectory(); if (gnupgHome.isEmpty()) { throw_(i18n("Could not determine the GnuPG home directory. Consider setting the GNUPGHOME environment variable.")); } // We should not create the home directory, but this only happens for very // old and long unsupported versions of gnupg. ensureDirectoryExists(gnupgHome); const QDir dir(gnupgHome); Q_ASSERT(dir.exists()); return dir.absoluteFilePath(QStringLiteral("S.uiserver")); } void UiServer::Private::ensureDirectoryExists(const QString &path) const { const QFileInfo info(path); if (info.exists() && !info.isDir()) { throw_(i18n("Cannot determine the GnuPG home directory: %1 exists but is not a directory.", path)); } if (info.exists()) { return; } const QDir dummy; //there is no static QDir::mkpath()... errno = 0; if (!dummy.mkpath(path)) { throw_(i18n("Could not create GnuPG home directory %1: %2", path, systemErrorString())); } } void UiServer::Private::makeListeningSocket() { // First, create a file (we do this only for the name, gmpfh) const QString fileName = suggestedSocketName; if (QFile::exists(fileName)) { if (isStaleAssuanSocket(fileName)) { QFile::remove(fileName); } else { throw_(i18n("Detected another running gnupg UI server listening at %1.", fileName)); } } doMakeListeningSocket(fileName.toUtf8()); actualSocketName = suggestedSocketName; } #include "moc_uiserver_p.cpp" diff --git a/src/uiserver/uiserver_unix.cpp b/src/uiserver/uiserver_unix.cpp index 954900b4b..a9312cfe7 100644 --- a/src/uiserver/uiserver_unix.cpp +++ b/src/uiserver/uiserver_unix.cpp @@ -1,81 +1,71 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/uiserver_unix.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "uiserver_p.h" #include #include #include #include #include #include #include #include #include using namespace Kleo; QString UiServer::Private::systemErrorString() { return QString::fromLocal8Bit(strerror(errno)); } void UiServer::Private::doMakeListeningSocket(const QByteArray &encodedFileName) { // Create a Unix Domain Socket: -#if defined(HAVE_ASSUAN2) || HAVE_ASSUAN_SOCK_GET_NONCE const assuan_fd_t sock = assuan_sock_new(AF_UNIX, SOCK_STREAM, 0); -#else - const assuan_fd_t sock = ::socket(AF_UNIX, SOCK_STREAM, 0); -#endif if (sock == ASSUAN_INVALID_FD) { throw_(i18n("Could not create socket: %1", systemErrorString())); } try { // Bind struct sockaddr_un sa; std::memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; std::strncpy(sa.sun_path, encodedFileName.constData(), sizeof(sa.sun_path) - 1); -#if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_SOCK_GET_NONCE) if (assuan_sock_bind(sock, (struct sockaddr *)&sa, sizeof(sa))) -#else - if (::bind(sock, (struct sockaddr *)&sa, sizeof(sa))) -#endif throw_(i18n("Could not bind to socket: %1", systemErrorString())); // ### TODO: permissions? -#if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_SOCK_GET_NONCE) if (assuan_sock_get_nonce((struct sockaddr *)&sa, sizeof(sa), &nonce)) { throw_(i18n("Could not get socket nonce: %1", systemErrorString())); } -#endif // Listen if (::listen(sock, SOMAXCONN)) { throw_(i18n("Could not listen to socket: %1", systemErrorString())); } if (!setSocketDescriptor(sock)) { throw_(i18n("Could not pass socket to Qt: %1. This should not happen, please report this bug.", errorString())); } } catch (...) { ::close(sock); throw; } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 40a6e0611..182921fa2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,86 +1,79 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(gnupg_home) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) include_directories( ${CMAKE_SOURCE_DIR}/src/ ${CMAKE_BINARY_DIR}/src/ ${GPGME_INCLUDES} ) ########### next target ############### set(test_verify_SRCS test_verify.cpp) add_definitions(-DKLEO_TEST_GNUPGHOME="${CMAKE_CURRENT_BINARY_DIR}/gnupg_home") add_definitions(-DKLEO_TEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}") add_executable(test_verify ${test_verify_SRCS}) add_test(NAME test_verify COMMAND test_verify) ecm_mark_as_test(test_verify) target_link_libraries(test_verify KF5::Libkleo Qt::Test KF5::CoreAddons KF5::I18n Qt::Widgets ) if (QT_MAJOR_VERSION STREQUAL "6") target_link_libraries(test_verify QGpgmeQt6) else() target_link_libraries(test_verify QGpgme) endif() ########### next target ############### if(USABLE_ASSUAN_FOUND) # this doesn't yet work on Windows add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_2) set(test_uiserver_SRCS test_uiserver.cpp ${CMAKE_SOURCE_DIR}/src/utils/wsastarter.cpp) #FIXME: omitting TEST makes test_uiserver print output again on a Win32 console; # find a better fix for this issue if(WIN32) add_executable(test_uiserver ${test_uiserver_SRCS}) else() add_executable(test_uiserver ${test_uiserver_SRCS}) endif() target_link_libraries(test_uiserver KF5::I18n) - if(ASSUAN2_FOUND) target_link_libraries(test_uiserver KF5::Libkleo ${ASSUAN2_LIBRARIES} ) - else() - target_link_libraries(test_uiserver - KF5::Libkleo - ${ASSUAN_LIBRARIES} - ) - endif() if(WIN32) target_link_libraries(test_uiserver ${ASSUAN_VANILLA_LIBRARIES} ws2_32 ) else() target_link_libraries(test_uiserver ${ASSUAN_PTHREAD_LIBRARIES} ) endif() if (QT_MAJOR_VERSION STREQUAL "6") target_link_libraries(test_uiserver QGpgmeQt6) else() target_link_libraries(test_uiserver QGpgme) endif() endif() diff --git a/tests/test_uiserver.cpp b/tests/test_uiserver.cpp index 6f7684b5e..28fdb04d6 100644 --- a/tests/test_uiserver.cpp +++ b/tests/test_uiserver.cpp @@ -1,298 +1,270 @@ /* -*- mode: c++; c-basic-offset:4 -*- tests/test_uiserver.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ // // Usage: test_uiserver --verify-detached // #include #include #include #include #include #include "utils/wsastarter.h" #ifndef Q_OS_WIN32 # include # include # include # include # include #endif #include #include #include #include #include using namespace Kleo; #ifdef Q_OS_WIN32 static const bool HAVE_FD_PASSING = false; #else static const bool HAVE_FD_PASSING = true; #endif static const unsigned int ASSUAN_CONNECT_FLAGS = HAVE_FD_PASSING ? 1 : 0; static std::vector inFDs, outFDs, msgFDs; static std::vector inFiles, outFiles, msgFiles; static std::map inquireData; static void usage(const std::string &msg = std::string()) { std::cerr << msg << std::endl << "\n" "Usage: test_uiserver [] [] [] command []\n" "where:\n" #ifdef Q_OS_WIN32 " : [--input[-fd] ] [--output[-fd] ] [--message[-fd] ]\n" #else " : [--input ] [--output ] [--message ]\n" #endif " : *[--option name=value]\n" " : [--inquire keyword=]\n"; exit(1); } -#ifndef HAVE_ASSUAN2 -static assuan_error_t data(void *void_ctx, const void *buffer, size_t len) -{ -#else static gpg_error_t data(void *void_ctx, const void *buffer, size_t len) { -#endif (void)void_ctx; (void)buffer; (void)len; return 0; // ### implement me } -#ifndef HAVE_ASSUAN2 -static assuan_error_t status(void *void_ctx, const char *line) -{ -#else static gpg_error_t status(void *void_ctx, const char *line) { -#endif (void)void_ctx; (void)line; return 0; } -#ifndef HAVE_ASSUAN2 -static assuan_error_t inquire(void *void_ctx, const char *keyword) -{ -#else static gpg_error_t inquire(void *void_ctx, const char *keyword) { -#endif assuan_context_t ctx = (assuan_context_t)void_ctx; Q_ASSERT(ctx); const std::map::const_iterator it = inquireData.find(keyword); if (it == inquireData.end()) { return gpg_error(GPG_ERR_UNKNOWN_COMMAND); } if (!it->second.empty() && it->second[0] == '@') { return gpg_error(GPG_ERR_NOT_IMPLEMENTED); } if (const gpg_error_t err = assuan_send_data(ctx, it->second.c_str(), it->second.size())) { qDebug("assuan_write_data: %s", gpg_strerror(err)); return err; } return 0; } int main(int argc, char *argv[]) { const Kleo::WSAStarter _wsastarter; -#ifndef HAVE_ASSUAN2 - assuan_set_assuan_err_source(GPG_ERR_SOURCE_DEFAULT); -#else assuan_set_gpg_err_source(GPG_ERR_SOURCE_DEFAULT); -#endif if (argc < 3) { usage(); // need socket and command, at least } const char *socket = argv[1]; std::vector options; std::string command; for (int optind = 2; optind < argc; ++optind) { const char *const arg = argv[optind]; if (qstrcmp(arg, "--input") == 0) { const std::string file = argv[++optind]; inFiles.push_back(file); } else if (qstrcmp(arg, "--output") == 0) { const std::string file = argv[++optind]; outFiles.push_back(file); } else if (qstrcmp(arg, "--message") == 0) { const std::string file = argv[++optind]; msgFiles.push_back(file); #ifndef Q_OS_WIN32 } else if (qstrcmp(arg, "--input-fd") == 0) { int inFD; if ((inFD = open(argv[++optind], O_RDONLY)) == -1) { perror("--input-fd open()"); return 1; } inFDs.push_back(inFD); } else if (qstrcmp(arg, "--output-fd") == 0) { int outFD; if ((outFD = open(argv[++optind], O_WRONLY | O_CREAT, 0666)) == -1) { perror("--output-fd open()"); return 1; } outFDs.push_back(outFD); } else if (qstrcmp(arg, "--message-fd") == 0) { int msgFD; if ((msgFD = open(argv[++optind], O_RDONLY)) == -1) { perror("--message-fd open()"); return 1; } msgFDs.push_back(msgFD); #endif } else if (qstrcmp(arg, "--option") == 0) { options.push_back(argv[++optind]); } else if (qstrcmp(arg, "--inquire") == 0) { const std::string inqval = argv[++optind]; const size_t pos = inqval.find('='); // ### implement indirection with "@file"... inquireData[inqval.substr(0, pos)] = inqval.substr(pos + 1); } else { while (optind < argc) { if (!command.empty()) { command += ' '; } command += argv[optind++]; } } } if (command.empty()) { usage("Command expected, but only options found"); } assuan_context_t ctx = nullptr; -#ifndef HAVE_ASSUAN2 - if (const gpg_error_t err = assuan_socket_connect_ext(&ctx, socket, -1, ASSUAN_CONNECT_FLAGS)) { - qDebug("%s", Exception(err, "assuan_socket_connect_ext").what()); -#else if (const gpg_error_t err = assuan_new(&ctx)) { qDebug("%s", Exception(err, "assuan_new").what()); return 1; } if (const gpg_error_t err = assuan_socket_connect(ctx, socket, -1, ASSUAN_CONNECT_FLAGS)) { qDebug("%s", Exception(err, "assuan_socket_connect").what()); -#endif return 1; } assuan_set_log_stream(ctx, stderr); #ifndef Q_OS_WIN32 for (std::vector::const_iterator it = inFDs.begin(), end = inFDs.end(); it != end; ++it) { if (const gpg_error_t err = assuan_sendfd(ctx, *it)) { qDebug("%s", Exception(err, "assuan_sendfd( inFD )").what()); return 1; } if (const gpg_error_t err = assuan_transact(ctx, "INPUT FD", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, "INPUT FD").what()); return 1; } } for (std::vector::const_iterator it = msgFDs.begin(), end = msgFDs.end(); it != end; ++it) { if (const gpg_error_t err = assuan_sendfd(ctx, *it)) { qDebug("%s", Exception(err, "assuan_sendfd( msgFD )").what()); return 1; } if (const gpg_error_t err = assuan_transact(ctx, "MESSAGE FD", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, "MESSAGE FD").what()); return 1; } } for (std::vector::const_iterator it = outFDs.begin(), end = outFDs.end(); it != end; ++it) { if (const gpg_error_t err = assuan_sendfd(ctx, *it)) { qDebug("%s", Exception(err, "assuan_sendfd( outFD )").what()); return 1; } if (const gpg_error_t err = assuan_transact(ctx, "OUTPUT FD", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, "OUTPUT FD").what()); return 1; } } #endif for (std::vector::const_iterator it = inFiles.begin(), end = inFiles.end(); it != end; ++it) { char buffer[1024]; sprintf(buffer, "INPUT FILE=%s", hexencode(*it).c_str()); if (const gpg_error_t err = assuan_transact(ctx, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, buffer).what()); return 1; } } for (std::vector::const_iterator it = msgFiles.begin(), end = msgFiles.end(); it != end; ++it) { char buffer[1024]; sprintf(buffer, "MESSAGE FILE=%s", hexencode(*it).c_str()); if (const gpg_error_t err = assuan_transact(ctx, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, buffer).what()); return 1; } } for (std::vector::const_iterator it = outFiles.begin(), end = outFiles.end(); it != end; ++it) { char buffer[1024]; sprintf(buffer, "OUTPUT FILE=%s", hexencode(*it).c_str()); if (const gpg_error_t err = assuan_transact(ctx, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, buffer).what()); return 1; } } for (const char *opt : std::as_const(options)) { std::string line = "OPTION "; line += opt; if (const gpg_error_t err = assuan_transact(ctx, line.c_str(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, line).what()); return 1; } } if (const gpg_error_t err = assuan_transact(ctx, command.c_str(), data, ctx, inquire, ctx, status, ctx)) { qDebug("%s", Exception(err, command).what()); return 1; } -#ifndef HAVE_ASSUAN2 - assuan_disconnect(ctx); -#else assuan_release(ctx); -#endif return 0; }