Merge branch 'release/2.4.2' into develop

This commit is contained in:
Jonathan White 2019-05-07 13:00:27 -04:00
commit bc891761b6
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
62 changed files with 710 additions and 243 deletions

View File

@ -20,9 +20,10 @@ project(KeePassXC)
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel." "Choose the type of build, options are: Debug Release RelWithDebInfo Profile"
FORCE) FORCE)
endif() endif()
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
@ -40,22 +41,21 @@ option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF)
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF) option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON) option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
set(WITH_XC_ALL OFF CACHE BOOLEAN "Build in all available plugins") set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins")
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF) option(WITH_XC_NETWORKING "Include networking code (e.g. for downloading website icons)." OFF)
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF) option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF) option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF) option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF) option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON) option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
if(APPLE) if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif() endif()
if(WITH_XC_ALL) if(WITH_XC_ALL)
# Enable all options # Enable all options (except update check)
set(WITH_XC_AUTOTYPE ON) set(WITH_XC_AUTOTYPE ON)
set(WITH_XC_NETWORKING ON) set(WITH_XC_NETWORKING ON)
set(WITH_XC_BROWSER ON) set(WITH_XC_BROWSER ON)
@ -67,18 +67,16 @@ if(WITH_XC_ALL)
endif() endif()
endif() endif()
if(WITH_XC_KEESHARE_SECURE)
set(WITH_XC_KEESHARE ON)
endif()
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE) if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
set(WITH_XC_CRYPTO_SSH ON) set(WITH_XC_CRYPTO_SSH ON)
else() else()
set(WITH_XC_CRYPTO_SSH OFF) set(WITH_XC_CRYPTO_SSH OFF)
endif() endif()
if(WITH_XC_UPDATECHECK) # Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
set(WITH_XC_NETWORKING ON) if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
set(WITH_XC_UPDATECHECK OFF)
endif() endif()
set(KEEPASSXC_VERSION_MAJOR "2") set(KEEPASSXC_VERSION_MAJOR "2")
@ -162,11 +160,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
set(IS_32BIT TRUE) set(IS_32BIT TRUE)
endif() endif()
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") if("${CMAKE_C_COMPILER}" MATCHES "clang$"
OR "${CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANG 1) set(CMAKE_COMPILER_IS_CLANG 1)
endif() endif()
if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$"
OR "${CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANGXX 1) set(CMAKE_COMPILER_IS_CLANGXX 1)
endif() endif()
@ -199,7 +201,7 @@ add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
add_gcc_compiler_flags("-fvisibility=hidden") add_gcc_compiler_flags("-fvisibility=hidden")
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden") add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
add_gcc_compiler_flags("-Werror") add_gcc_compiler_flags("-Werror")
endif() endif()
@ -230,7 +232,6 @@ if(WITH_ASAN)
endif() endif()
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2") add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
endif() endif()
@ -264,6 +265,11 @@ endif()
add_gcc_compiler_cflags("-std=c99") add_gcc_compiler_cflags("-std=c99")
add_gcc_compiler_cxxflags("-std=c++11") add_gcc_compiler_cxxflags("-std=c++11")
if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.99) OR
(CMAKE_COMPILER_IS_CLANGXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6.99))
add_gcc_compiler_cxxflags("-fsized-deallocation")
endif()
if(APPLE) if(APPLE)
add_gcc_compiler_cxxflags("-stdlib=libc++") add_gcc_compiler_cxxflags("-stdlib=libc++")
endif() endif()
@ -276,7 +282,7 @@ if(MINGW)
set(CMAKE_RC_COMPILER_INIT windres) set(CMAKE_RC_COMPILER_INIT windres)
enable_language(RC) enable_language(RC)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>") set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) if(NOT (CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo"))
# Enable DEP and ASLR # Enable DEP and ASLR
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
@ -365,10 +371,17 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH TRUE) set(CMAKE_MACOSX_RPATH TRUE)
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH) find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
if(NOT MACDEPLOYQT_EXE) if(NOT MACDEPLOYQT_EXE)
message(FATAL_ERROR "macdeployqt is required to build in macOS") message(FATAL_ERROR "macdeployqt is required to build on macOS")
else() else()
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}") message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
endif() endif()
elseif(MINGW)
find_program(WINDEPLOYQT_EXE windeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
if(NOT WINDEPLOYQT_EXE)
message(FATAL_ERROR "windeployqt is required to build on Windows")
else()
message(STATUS "Using windeployqt: ${WINDEPLOYQT_EXE}")
endif()
endif() endif()
# Debian sets the the build type to None for package builds. # Debian sets the the build type to None for package builds.
@ -380,6 +393,7 @@ find_package(Gcrypt 1.7.0 REQUIRED)
find_package(Argon2 REQUIRED) find_package(Argon2 REQUIRED)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(QREncode REQUIRED) find_package(QREncode REQUIRED)
find_package(sodium 1.0.12 REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
@ -387,20 +401,7 @@ if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format") message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
endif() endif()
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
# Optional
if(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE ON)
if(WITH_XC_KEESHARE_SECURE)
# ZLIB is needed and already required
find_package(QuaZip REQUIRED)
include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR})
endif()
else()
set(WITH_XC_KEESHARE_INSECURE OFF)
set(WITH_XC_KEESHARE_SECURE OFF)
endif()
# Optional # Optional
if(WITH_XC_YUBIKEY) if(WITH_XC_YUBIKEY)

View File

@ -25,8 +25,8 @@ The following libraries are required:
* zlib * zlib
* libmicrohttpd * libmicrohttpd
* libxi, libxtst, qtx11extras (optional for auto-type on X11) * libxi, libxtst, qtx11extras (optional for auto-type on X11)
* libsodium (>= 1.0.12, optional for KeePassXC-Browser support) * libsodium (>= 1.0.12)
* argon2 * libargon2
* qrencode * qrencode
* yubikey ykpers (optional to support YubiKey) * yubikey ykpers (optional to support YubiKey)
@ -99,18 +99,26 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON) -DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF) -DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF) -DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (favicon download) (default: OFF) -DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (e.g., favicon downloading) (default: OFF)
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF) -DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group syncronization extension (default: OFF)
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF) -DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare signed containers, requires libquazip5 (default: OFF)
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF) -DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare secure containers, requires libquazip5 (default: OFF)
-DWITH_XC_UPDATECHECK=[ON|OFF] Enable/Disable automatic updating checking (requires WITH_XC_NETWORKING) (default: ON)
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON) -DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF) -DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF) -DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF) -DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF) -DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
-DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON) -DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON)
-DKEEPASSXC_BUILD_TYPE=[Snapshot|PreRelease|Release] Set the build type to show/hide stability warnings (default: "Snapshot")
-DKEEPASSXC_DIST_TYPE=[Snap|AppImage|Other] Specify the distribution method (default: "Other")
-DOVERRIDE_VERSION=[X.X.X] Specify a version number when building. Used with snapshot builds (default: "")
-DGIT_HEAD_OVERRIDE=[XXXXXXX] Specify the 7 digit git commit ref for this build. Used with distribution builds (default: "")
``` ```
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/` * If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`

View File

@ -112,7 +112,7 @@ mark_as_advanced(
CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") if(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"

View File

@ -4,38 +4,21 @@
# QUAZIP_LIBRARIES - List of QuaZip libraries # QUAZIP_LIBRARIES - List of QuaZip libraries
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers # QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) if(MINGW)
# in cache already find_library(QUAZIP_LIBRARIES libquazip5)
SET(QUAZIP_FOUND TRUE) find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5)
ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h)
IF(Qt5Core_FOUND) else()
set(QUAZIP_LIB_VERSION_SUFFIX 5) find_library(QUAZIP_LIBRARIES
ENDIF() NAMES quazip5 quazip
IF(WIN32) PATHS /usr/lib /usr/lib64 /usr/local/lib
FIND_PATH(QUAZIP_LIBRARY_DIR
WIN32_DEBUG_POSTFIX d
NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll
HINTS "C:/Programme/" "C:/Program Files"
PATH_SUFFIXES QuaZip/lib
) )
FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR}) find_path(QUAZIP_INCLUDE_DIR quazip.h
FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5) PATHS /usr/include /usr/local/include
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h) PATH_SUFFIXES quazip5 quazip
ELSE(WIN32)
FIND_PACKAGE(PkgConfig)
pkg_check_modules(PC_QUAZIP quazip)
FIND_LIBRARY(QUAZIP_LIBRARIES
WIN32_DEBUG_POSTFIX d
NAMES quazip${QUAZIP_LIB_VERSION_SUFFIX}
HINTS /usr/lib /usr/lib64
) )
FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include)
HINTS /usr/include /usr/local/include endif()
PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX} include(FindPackageHandleStandardArgs)
) set(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include) find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
ENDIF(WIN32)
INCLUDE(FindPackageHandleStandardArgs)
SET(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
ENDIF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)

View File

@ -0,0 +1,11 @@
# /snap/local/launchers
Here are the launchers, or wrapper programs to deal with some runtime-fixable problems for the snapped applications, like setting proper environmental variables in snap.
In convention launchers are named _something_-launch, for dealing certain problem with _something_, and usually can be called in a stacked manner to consolidate their modifications.
```yaml
apps:
_app_name_:
command: foo-launch bar-launch _app_command_
```

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# This is the maintainence launcher for the snap, make necessary runtime environment changes to make the snap work here. You may also insert security confinement/deprecation/obsoletion notice of the snap here.
set \
-o errexit \
-o errtrace \
-o nounset \
-o pipefail
# gtk-common-themes support
export QT_QPA_PLATFORMTHEME=gtk3
# Finally run the next part of the command chain
exec "${@}"

View File

@ -9,16 +9,28 @@ description: |
confinement: strict confinement: strict
base: core18 base: core18
plugs: plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
icon-themes: # fix mouse cursor theme gtk-3-themes:
interface: content
target: $SNAP/data-dir/themes
default-provider: gtk-common-themes:gtk-3-themes
icon-themes:
interface: content interface: content
target: $SNAP/data-dir/icons target: $SNAP/data-dir/icons
default-provider: gtk-common-themes default-provider: gtk-common-themes:icon-themes
sound-themes:
interface: content
target: $SNAP/data-dir/sounds
default-provider: gtk-common-themes:sounds-themes
apps: apps:
keepassxc: keepassxc:
command: desktop-launch keepassxc adapter: full
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy] command: usr/bin/keepassxc -style fusion
command-chain:
- bin/desktop-launch
- bin/gtk3-env-launch
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy, desktop]
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
environment: environment:
DISABLE_WAYLAND: 1 DISABLE_WAYLAND: 1
@ -73,7 +85,7 @@ parts:
- libquazip5-1 - libquazip5-1
- libusb-1.0-0 - libusb-1.0-0
- qtwayland5 - qtwayland5
- qt5-style-plugins # for mouse cursor theme fix - qt5-gtk-platformtheme # for theming, font settings, cursor and to use gtk3 file chooser
override-build: | override-build: |
snapcraftctl build snapcraftctl build
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
@ -83,6 +95,14 @@ parts:
- -opt - -opt
after: [desktop-qt5] after: [desktop-qt5]
launchers: # custom launcher to set QT_QPA_PLATFORMTHEME=gtk3 correctly
source: snap/local/launchers
plugin: dump
organize:
'*': bin/
stage:
- -bin/README.*
desktop-qt5: desktop-qt5:
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
source-subdir: qt source-subdir: qt

View File

@ -16,9 +16,6 @@
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
find_library(ZXCVBN_LIBRARIES zxcvbn) find_library(ZXCVBN_LIBRARIES zxcvbn)
if(NOT ZXCVBN_LIBRARIES) if(NOT ZXCVBN_LIBRARIES)
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c) add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
@ -27,6 +24,7 @@ if(NOT ZXCVBN_LIBRARIES)
endif(NOT ZXCVBN_LIBRARIES) endif(NOT ZXCVBN_LIBRARIES)
set(keepassx_SOURCES set(keepassx_SOURCES
core/Alloc.cpp
core/AutoTypeAssociations.cpp core/AutoTypeAssociations.cpp
core/AutoTypeMatch.cpp core/AutoTypeMatch.cpp
core/Compare.cpp core/Compare.cpp
@ -167,7 +165,8 @@ if(APPLE)
core/ScreenLockListenerMac.cpp core/ScreenLockListenerMac.cpp
core/MacPasteboard.cpp core/MacPasteboard.cpp
gui/macutils/MacUtils.cpp gui/macutils/MacUtils.cpp
gui/macutils/AppKitImpl.mm) gui/macutils/AppKitImpl.mm
gui/macutils/AppKit.h)
endif() endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
set(keepassx_SOURCES set(keepassx_SOURCES
@ -192,8 +191,7 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)") add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser") add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent") add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare") add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response") add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking") add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
if(APPLE) if(APPLE)
@ -255,8 +253,13 @@ endif()
if(WITH_XC_TOUCHID) if(WITH_XC_TOUCHID)
list(APPEND keepassx_SOURCES touchid/TouchID.mm) list(APPEND keepassx_SOURCES touchid/TouchID.mm)
# TODO: Remove -Wno-error once deprecation warnings have been resolved.
set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error")
endif() endif()
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
add_library(autotype STATIC ${autotype_SOURCES}) add_library(autotype STATIC ${autotype_SOURCES})
target_link_libraries(autotype Qt5::Core Qt5::Widgets) target_link_libraries(autotype Qt5::Core Qt5::Widgets)
@ -271,6 +274,7 @@ target_link_libraries(keepassx_core
Qt5::Concurrent Qt5::Concurrent
Qt5::Network Qt5::Network
Qt5::Widgets Qt5::Widgets
${sodium_LIBRARY_RELEASE}
${YUBIKEY_LIBRARIES} ${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES} ${ZXCVBN_LIBRARIES}
${ARGON2_LIBRARIES} ${ARGON2_LIBRARIES}
@ -411,25 +415,19 @@ if(MINGW)
install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime) install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime)
include(DeployQt4) # Deploy all 3rd party library dependencies first
install_qt4_executable(${PROGNAME}.exe) install(CODE "include(BundleUtilities)
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${PROGNAME}.exe\" \"\" \"\")"
COMPONENT Runtime)
# install Qt5 plugins # Use windeployqt.exe to setup Qt dependencies
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins) set(WINDEPLOYQT_MODE "--release")
install(FILES if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll set(WINDEPLOYQT_MODE "--debug")
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll endif()
DESTINATION "platforms")
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles") install(CODE "execute_process(COMMAND ${WINDEPLOYQT_EXE} ${PROGNAME}.exe ${WINDEPLOYQT_MODE} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX} OUTPUT_QUIET)"
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts") COMPONENT Runtime)
install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$<CONFIG:Debug>:d>.dll DESTINATION "iconengines")
install(FILES
${PLUGINS_DIR}/imageformats/qgif$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qicns$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qico$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qjpeg$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qwebp$<$<CONFIG:Debug>:d>.dll
DESTINATION "imageformats")
# install CA cert chains # install CA cert chains
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs") install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")

View File

@ -296,6 +296,7 @@ QString BrowserService::storeKey(const QString& key)
do { do {
QInputDialog keyDialog; QInputDialog keyDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
keyDialog.setWindowTitle(tr("KeePassXC: New key association request")); keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n" keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
"If you would like to allow it access to your KeePassXC database,\n" "If you would like to allow it access to your KeePassXC database,\n"
@ -310,7 +311,7 @@ QString BrowserService::storeKey(const QString& key)
id = keyDialog.textValue(); id = keyDialog.textValue();
if (ok != QDialog::Accepted || id.isEmpty()) { if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) {
hideWindow(); hideWindow();
return {}; return {};
} }
@ -406,6 +407,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
return QJsonArray(); return QJsonArray();
} }
// Ensure that database is not locked when the popup was visible
if (!isDatabaseOpened()) {
return QJsonArray();
}
// Sort results // Sort results
pwEntries = sortEntries(pwEntries, host, submitUrl); pwEntries = sortEntries(pwEntries, host, submitUrl);
@ -760,6 +766,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
m_dialogActive = true; m_dialogActive = true;
BrowserAccessControlDialog accessControlDialog; BrowserAccessControlDialog accessControlDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
accessControlDialog.setUrl(url); accessControlDialog.setUrl(url);
accessControlDialog.setItems(pwEntriesToConfirm); accessControlDialog.setItems(pwEntriesToConfirm);

View File

@ -16,7 +16,6 @@
if(WITH_XC_BROWSER) if(WITH_XC_BROWSER)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
find_package(sodium 1.0.12 REQUIRED)
set(keepassxcbrowser_SOURCES set(keepassxcbrowser_SOURCES
BrowserAccessControlDialog.cpp BrowserAccessControlDialog.cpp
@ -33,5 +32,5 @@ if(WITH_XC_BROWSER)
Variant.cpp) Variant.cpp)
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES}) add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium) target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
endif() endif()

View File

@ -84,7 +84,7 @@ int Add::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add"); errorTextStream << parser.helpText().replace("[options]", "add [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli
keepassx_core keepassx_core
Qt5::Core Qt5::Core
${GCRYPT_LIBRARIES} ${GCRYPT_LIBRARIES}
${sodium_LIBRARY_RELEASE}
${ARGON2_LIBRARIES} ${ARGON2_LIBRARIES}
${GPGERROR_LIBRARIES} ${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}

View File

@ -63,7 +63,7 @@ int Clip::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2 && args.size() != 3) { if (args.size() != 2 && args.size() != 3) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli clip"); errorTextStream << parser.helpText().replace("[options]", "clip [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -70,7 +70,7 @@ int Create::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() < 1) { if (args.size() < 1) {
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create"); out << parser.helpText().replace("[options]", "create [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -58,7 +58,7 @@ int Diceware::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) { if (!args.isEmpty()) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); errorTextStream << parser.helpText().replace("[options]", "diceware [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -78,7 +78,7 @@ int Diceware::execute(const QStringList& arguments)
} }
if (!dicewareGenerator.isValid()) { if (!dicewareGenerator.isValid()) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); outputTextStream << parser.helpText().replace("[options]", "diceware [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -88,7 +88,7 @@ int Edit::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); errorTextStream << parser.helpText().replace("[options]", "edit [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -171,7 +171,7 @@ int Estimate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() > 1) { if (args.size() > 1) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); errorTextStream << parser.helpText().replace("[options]", "estimate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -53,7 +53,7 @@ int Extract::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 1) { if (args.size() != 1) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract"); errorTextStream << parser.helpText().replace("[options]", "extract [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -84,7 +84,7 @@ int Generate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) { if (!args.isEmpty()) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -128,7 +128,7 @@ int Generate::execute(const QStringList& arguments)
passwordGenerator.setExcludedChars(parser.value(exclude)); passwordGenerator.setExcludedChars(parser.value(exclude));
if (!passwordGenerator.isValid()) { if (!passwordGenerator.isValid()) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -59,7 +59,7 @@ int List::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 1 && args.size() != 2) { if (args.size() != 1 && args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli ls"); errorTextStream << parser.helpText().replace("[options]", "ls [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -56,7 +56,7 @@ int Locate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate"); errorTextStream << parser.helpText().replace("[options]", "locate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -69,7 +69,7 @@ int Merge::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge"); errorTextStream << parser.helpText().replace("[options]", "merge [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -58,7 +58,7 @@ int Remove::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); errorTextStream << parser.helpText().replace("[options]", "rm [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -69,7 +69,7 @@ int Show::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli show"); errorTextStream << parser.helpText().replace("[options]", "show [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -19,6 +19,9 @@
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include <QTextCodec> #include <QTextCodec>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
TextStream::TextStream() TextStream::TextStream()
{ {
@ -59,12 +62,26 @@ void TextStream::detectCodec()
{ {
QString codecName = "UTF-8"; QString codecName = "UTF-8";
auto env = QProcessEnvironment::systemEnvironment(); auto env = QProcessEnvironment::systemEnvironment();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (!env.contains("SHELL")) { WINBOOL success = false;
// native shell (no Msys or cygwin) #ifdef CP_UTF8
success = SetConsoleOutputCP(CP_UTF8);
#endif
if (!success && !env.contains("SHELL")) {
// Fall back to cp850 if this is Windows without CP_UTF8 and we
// are running in a native shell (i.e., no Msys or Cygwin).
codecName = "Windows-850"; codecName = "Windows-850";
} }
#else
if (env.contains("LANG") && !env.value("LANG").isEmpty() && env.value("LANG") != "C") {
// Only override codec if LANG is set, otherwise Qt will assume
// US-ASCII, which is almost always wrong and results in
// Unicode passwords being displayed as question marks.
codecName = QTextCodec::codecForLocale()->name();
}
#endif #endif
codecName = env.value("ENCODING_OVERRIDE", codecName); codecName = env.value("ENCODING_OVERRIDE", codecName);
auto* codec = QTextCodec::codecForName(codecName.toLatin1()); auto* codec = QTextCodec::codecForName(codecName.toLatin1());
if (codec) { if (codec) {

89
src/core/Alloc.cpp Normal file
View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtGlobal>
#include <cstdint>
#include <sodium.h>
#ifdef Q_OS_MACOS
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#if defined(NDEBUG) && !defined(__cpp_sized_deallocation)
#warning "KeePassXC is being compiled without sized deallocation support. Deletes may be slow."
#endif
/**
* Custom sized delete operator which securely zeroes out allocated
* memory before freeing it (requires C++14 sized deallocation support).
*/
void operator delete(void* ptr, std::size_t size) noexcept
{
if (!ptr) {
return;
}
sodium_memzero(ptr, size);
std::free(ptr);
}
void operator delete[](void* ptr, std::size_t size) noexcept
{
::operator delete(ptr, size);
}
/**
* Custom delete operator which securely zeroes out
* allocated memory before freeing it.
*/
void operator delete(void* ptr) noexcept
{
if (!ptr) {
return;
}
#if defined(Q_OS_WIN)
::operator delete(ptr, _msize(ptr));
#elif defined(Q_OS_MACOS)
::operator delete(ptr, malloc_size(ptr));
#elif defined(Q_OS_UNIX)
::operator delete(ptr, malloc_usable_size(ptr));
#else
// whatever OS this is, give up and simply free stuff
std::free(ptr);
#endif
}
void operator delete[](void* ptr) noexcept
{
::operator delete(ptr);
}
/**
* Custom insecure delete operator that does not zero out memory before
* freeing a buffer. Can be used for better performance.
*/
void operator delete(void* ptr, bool) noexcept
{
std::free(ptr);
}
void operator delete[](void* ptr, bool) noexcept
{
::operator delete(ptr, false);
}

View File

@ -85,6 +85,12 @@ namespace Bootstrap
bootstrap(); bootstrap();
MessageBox::initializeButtonDefs(); MessageBox::initializeButtonDefs();
#ifdef KEEPASSXC_DIST_SNAP
// snap: force fallback theme to avoid using system theme (gtk integration)
// with missing actions just like on Windows and macOS
QIcon::setThemeSearchPaths(QStringList() << ":/icons");
#endif
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
// Don't show menu icons on OSX // Don't show menu icons on OSX
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);

View File

@ -16,9 +16,12 @@
*/ */
#include "CustomData.h" #include "CustomData.h"
#include "Clock.h"
#include "core/Global.h" #include "core/Global.h"
const QString CustomData::LastModified = "_LAST_MODIFIED";
CustomData::CustomData(QObject* parent) CustomData::CustomData(QObject* parent)
: QObject(parent) : QObject(parent)
{ {
@ -60,6 +63,7 @@ void CustomData::set(const QString& key, const QString& value)
if (addAttribute || changeValue) { if (addAttribute || changeValue) {
m_data.insert(key, value); m_data.insert(key, value);
updateLastModified();
emit customDataModified(); emit customDataModified();
} }
@ -74,6 +78,7 @@ void CustomData::remove(const QString& key)
m_data.remove(key); m_data.remove(key);
updateLastModified();
emit removed(key); emit removed(key);
emit customDataModified(); emit customDataModified();
} }
@ -94,6 +99,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
m_data.remove(oldKey); m_data.remove(oldKey);
m_data.insert(newKey, data); m_data.insert(newKey, data);
updateLastModified();
emit customDataModified(); emit customDataModified();
emit renamed(oldKey, newKey); emit renamed(oldKey, newKey);
} }
@ -108,9 +114,19 @@ void CustomData::copyDataFrom(const CustomData* other)
m_data = other->m_data; m_data = other->m_data;
updateLastModified();
emit reset(); emit reset();
emit customDataModified(); emit customDataModified();
} }
QDateTime CustomData::getLastModified() const
{
if (m_data.contains(LastModified)) {
return Clock::parse(m_data.value(LastModified));
}
return {};
}
bool CustomData::operator==(const CustomData& other) const bool CustomData::operator==(const CustomData& other) const
{ {
return (m_data == other.m_data); return (m_data == other.m_data);
@ -152,3 +168,13 @@ int CustomData::dataSize() const
} }
return size; return size;
} }
void CustomData::updateLastModified()
{
if (m_data.size() == 1 && m_data.contains(LastModified)) {
m_data.remove(LastModified);
return;
}
m_data.insert(LastModified, Clock::currentDateTimeUtc().toString());
}

View File

@ -42,9 +42,12 @@ public:
int size() const; int size() const;
int dataSize() const; int dataSize() const;
void copyDataFrom(const CustomData* other); void copyDataFrom(const CustomData* other);
QDateTime getLastModified() const;
bool operator==(const CustomData& other) const; bool operator==(const CustomData& other) const;
bool operator!=(const CustomData& other) const; bool operator!=(const CustomData& other) const;
static const QString LastModified;
signals: signals:
void customDataModified(); void customDataModified();
void aboutToBeAdded(const QString& key); void aboutToBeAdded(const QString& key);
@ -55,6 +58,10 @@ signals:
void renamed(const QString& oldKey, const QString& newKey); void renamed(const QString& oldKey, const QString& newKey);
void aboutToBeReset(); void aboutToBeReset();
void reset(); void reset();
void lastModified();
private slots:
void updateLastModified();
private: private:
QHash<QString, QString> m_data; QHash<QString, QString> m_data;

View File

@ -609,9 +609,6 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
// TODO HNH: missing handling of recycle bin, names, templates for groups and entries, // TODO HNH: missing handling of recycle bin, names, templates for groups and entries,
// public data (entries of newer dict override keys of older dict - ignoring // public data (entries of newer dict override keys of older dict - ignoring
// their own age - it is enough if one entry of the whole dict is newer) => possible lost update // their own age - it is enough if one entry of the whole dict is newer) => possible lost update
// TODO HNH: CustomData is merged with entries of the new customData overwrite entries
// of the older CustomData - the dict with the newest entry is considered
// newer regardless of the age of the other entries => possible lost update
ChangeList changes; ChangeList changes;
auto* sourceMetadata = context.m_sourceDb->metadata(); auto* sourceMetadata = context.m_sourceDb->metadata();
auto* targetMetadata = context.m_targetDb->metadata(); auto* targetMetadata = context.m_targetDb->metadata();
@ -624,5 +621,32 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex())); changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex()));
} }
} }
// Merge Custom Data if source is newer
const auto targetCustomDataModificationTime = sourceMetadata->customData()->getLastModified();
const auto sourceCustomDataModificationTime = targetMetadata->customData()->getLastModified();
if (!targetMetadata->customData()->contains(CustomData::LastModified) ||
(targetCustomDataModificationTime.isValid() && sourceCustomDataModificationTime.isValid() &&
targetCustomDataModificationTime > sourceCustomDataModificationTime)) {
const auto sourceCustomDataKeys = sourceMetadata->customData()->keys();
const auto targetCustomDataKeys = targetMetadata->customData()->keys();
// Check missing keys from source. Remove those from target
for (const auto& key : targetCustomDataKeys) {
if (!sourceMetadata->customData()->contains(key)) {
auto value = targetMetadata->customData()->value(key);
targetMetadata->customData()->remove(key);
changes << tr("Removed custom data %1 [%2]").arg(key, value);
}
}
// Transfer new/existing keys
for (const auto& key : sourceCustomDataKeys) {
auto value = sourceMetadata->customData()->value(key);
targetMetadata->customData()->set(key, value);
changes << tr("Adding custom data %1 [%2]").arg(key, value);
}
}
return changes; return changes;
} }

View File

@ -195,7 +195,7 @@ QPixmap Metadata::customIconScaledPixmap(const QUuid& uuid) const
QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid]; QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid];
if (!QPixmapCache::find(cacheKey, &pixmap)) { if (!QPixmapCache::find(cacheKey, &pixmap)) {
QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::KeepAspectRatio, Qt::SmoothTransformation); QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
pixmap = QPixmap::fromImage(image); pixmap = QPixmap::fromImage(image);
cacheKey = QPixmapCache::insert(pixmap); cacheKey = QPixmapCache::insert(pixmap);
} }

View File

@ -34,14 +34,15 @@
*/ */
void Translator::installTranslators() void Translator::installTranslators()
{ {
QLocale locale;
QString language = config()->get("GUI/Language").toString(); QString language = config()->get("GUI/Language").toString();
if (language == "system" || language.isEmpty()) { if (!language.isEmpty() && language != "system") {
language = QLocale::system().name();
}
if (language == "en") {
// use actual English translation instead of the English locale source language // use actual English translation instead of the English locale source language
if (language == "en") {
language = "en_US"; language = "en_US";
} }
locale = QLocale(language);
}
const QStringList paths = { const QStringList paths = {
#ifdef QT_DEBUG #ifdef QT_DEBUG
@ -51,11 +52,12 @@ void Translator::installTranslators()
bool translationsLoaded = false; bool translationsLoaded = false;
for (const QString& path : paths) { for (const QString& path : paths) {
translationsLoaded |= installTranslator(language, path) || installTranslator("en_US", path); translationsLoaded |= installTranslator(locale, path) || installTranslator(QLocale("en_US"), path);
if (!installQtTranslator(language, path)) { if (!installQtTranslator(language, path)) {
installQtTranslator("en", path); installQtTranslator(QLocale("en"), path);
} }
} }
if (!translationsLoaded) { if (!translationsLoaded) {
// couldn't load configured language or fallback // couldn't load configured language or fallback
qWarning("Couldn't load translations."); qWarning("Couldn't load translations.");
@ -114,10 +116,10 @@ QList<QPair<QString, QString>> Translator::availableLanguages()
* @param path local search path * @param path local search path
* @return true on success * @return true on success
*/ */
bool Translator::installTranslator(const QString& language, const QString& path) bool Translator::installTranslator(const QLocale& locale, const QString& path)
{ {
QScopedPointer<QTranslator> translator(new QTranslator(qApp)); QScopedPointer<QTranslator> translator(new QTranslator(qApp));
if (translator->load(QString("keepassx_%1").arg(language), path)) { if (translator->load(locale, "keepassx_", "", path)) {
return QCoreApplication::installTranslator(translator.take()); return QCoreApplication::installTranslator(translator.take());
} }
return false; return false;
@ -131,13 +133,12 @@ bool Translator::installTranslator(const QString& language, const QString& path)
* @param path local search path * @param path local search path
* @return true on success * @return true on success
*/ */
bool Translator::installQtTranslator(const QString& language, const QString& path) bool Translator::installQtTranslator(const QLocale& locale, const QString& path)
{ {
QScopedPointer<QTranslator> qtTranslator(new QTranslator(qApp)); QScopedPointer<QTranslator> qtTranslator(new QTranslator(qApp));
if (qtTranslator->load(QString("qtbase_%1").arg(language), path)) { if (qtTranslator->load(locale, "qtbase_", "", path)) {
return QCoreApplication::installTranslator(qtTranslator.take()); return QCoreApplication::installTranslator(qtTranslator.take());
} else if (qtTranslator->load(QString("qtbase_%1").arg(language), } else if (qtTranslator->load(locale, "qtbase_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
return QCoreApplication::installTranslator(qtTranslator.take()); return QCoreApplication::installTranslator(qtTranslator.take());
} }
return false; return false;

View File

@ -20,6 +20,7 @@
#include <QPair> #include <QPair>
#include <QString> #include <QString>
#include <QLocale>
class Translator class Translator
{ {
@ -28,8 +29,8 @@ public:
static QList<QPair<QString, QString>> availableLanguages(); static QList<QPair<QString, QString>> availableLanguages();
private: private:
static bool installTranslator(const QString& language, const QString& path); static bool installTranslator(const QLocale& locale, const QString& path);
static bool installQtTranslator(const QString& language, const QString& path); static bool installQtTranslator(const QLocale& locale, const QString& path);
}; };
#endif // KEEPASSX_TRANSLATOR_H #endif // KEEPASSX_TRANSLATOR_H

View File

@ -64,6 +64,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
, m_globalAutoTypeModifiers(Qt::NoModifier) , m_globalAutoTypeModifiers(Qt::NoModifier)
{ {
setHeadline(tr("Application Settings")); setHeadline(tr("Application Settings"));
showApplyButton(false);
m_secUi->setupUi(m_secWidget); m_secUi->setupUi(m_secWidget);
m_generalUi->setupUi(m_generalWidget); m_generalUi->setupUi(m_generalWidget);
@ -75,7 +76,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
} }
connect(this, SIGNAL(accepted()), SLOT(saveSettings())); connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
connect(this, SIGNAL(apply()), SLOT(saveSettings()));
connect(this, SIGNAL(rejected()), SLOT(reject())); connect(this, SIGNAL(rejected()), SLOT(reject()));
// clang-format off // clang-format off

View File

@ -212,7 +212,7 @@
<item> <item>
<widget class="QCheckBox" name="fallbackToSearch"> <widget class="QCheckBox" name="fallbackToSearch">
<property name="text"> <property name="text">
<string>Use DuckDuckGo as fallback for downloading website icons</string> <string>Use DuckDuckGo service to download website icons</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -63,6 +63,10 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
// clang-format on // clang-format on
#ifdef Q_OS_MACOS
connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
#endif
} }
DatabaseTabWidget::~DatabaseTabWidget() DatabaseTabWidget::~DatabaseTabWidget()

View File

@ -1158,9 +1158,10 @@ void DatabaseWidget::onDatabaseModified()
{ {
if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) { if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) {
save(); save();
} } else {
// Only block once, then reset
m_blockAutoSave = false; m_blockAutoSave = false;
}
} }
QString DatabaseWidget::getCurrentSearch() QString DatabaseWidget::getCurrentSearch()
@ -1258,11 +1259,13 @@ bool DatabaseWidget::lock()
} }
if (m_db->isModified()) { if (m_db->isModified()) {
bool saved = false;
// Attempt to save on exit, but don't block locking if it fails
if (config()->get("AutoSaveOnExit").toBool()) { if (config()->get("AutoSaveOnExit").toBool()) {
if (!save()) { saved = save();
return false;
} }
} else {
if (!saved) {
QString msg; QString msg;
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) { if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped()); msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
@ -1521,11 +1524,14 @@ bool DatabaseWidget::save()
return true; return true;
} }
// Read-only and new databases ask for filename
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) { if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
return saveAs(); return saveAs();
} }
// Prevent recursions and infinite save loops
blockAutoReload(true); blockAutoReload(true);
m_blockAutoSave = true;
++m_saveAttempts; ++m_saveAttempts;
// TODO: Make this async, but lock out the database widget to prevent re-entrance // TODO: Make this async, but lock out the database widget to prevent re-entrance
@ -1536,6 +1542,7 @@ bool DatabaseWidget::save()
if (ok) { if (ok) {
m_saveAttempts = 0; m_saveAttempts = 0;
m_blockAutoSave = false;
return true; return true;
} }

View File

@ -35,7 +35,8 @@ void DialogyWidget::keyPressEvent(QKeyEvent* e)
} }
} else } else
#endif #endif
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { if (!e->modifiers() || e->modifiers() == Qt::ControlModifier
|| (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
switch (e->key()) { switch (e->key()) {
case Qt::Key_Enter: case Qt::Key_Enter:
case Qt::Key_Return: case Qt::Key_Return:

View File

@ -197,8 +197,6 @@ void EditWidgetIcons::downloadFavicon()
QString fullyQualifiedDomain = m_url.host(); QString fullyQualifiedDomain = m_url.host();
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
// Determine if host portion of URL is an IP address by resolving it and // Determine if host portion of URL is an IP address by resolving it and
// searching for a match with the returned address(es). // searching for a match with the returned address(es).
bool hostIsIp = false; bool hostIsIp = false;
@ -209,32 +207,35 @@ void EditWidgetIcons::downloadFavicon()
} }
} }
// Determine the second-level domain, if available
QString secondLevelDomain;
if (!hostIsIp) { if (!hostIsIp) {
QString secondLevelDomain = getSecondLevelDomain(m_url); secondLevelDomain = getSecondLevelDomain(m_url);
// Attempt to simply load the favicon.ico file
if (fullyQualifiedDomain != secondLevelDomain) {
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
}
} }
// Try to use alternative fallback URL, if enabled // Start with the "fallback" url (if enabled) to try to get the best favicon
if (config()->get("security/IconDownloadFallback", false).toBool()) { if (config()->get("security/IconDownloadFallback", false).toBool()) {
QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com");
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
m_urlsToTry.append(fallbackUrl); m_urlsToTry.append(fallbackUrl);
if (!hostIsIp) { // Also try a direct pull of the second-level domain (if possible)
QString secondLevelDomain = getSecondLevelDomain(m_url); if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
if (fullyQualifiedDomain != secondLevelDomain) {
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico"); fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
m_urlsToTry.append(fallbackUrl); m_urlsToTry.append(fallbackUrl);
} }
} }
// Add a direct pull of the website's own favicon.ico file
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
// Also try a direct pull of the second-level domain (if possible)
if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
} }
// Use the first URL to start the download process
// If a favicon is not found, the next URL will be tried
startFetchFavicon(m_urlsToTry.takeFirst()); startFetchFavicon(m_urlsToTry.takeFirst());
#endif #endif
} }
@ -277,7 +278,7 @@ void EditWidgetIcons::fetchFinished()
if (!image.isNull()) { if (!image.isNull()) {
if (!addCustomIcon(image)) { if (!addCustomIcon(image)) {
emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information);
} else if (!this->isVisible()) { } else if (!isVisible()) {
// Show confirmation message if triggered from Entry tab download button // Show confirmation message if triggered from Entry tab download button
emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive); emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive);
} }
@ -289,7 +290,7 @@ void EditWidgetIcons::fetchFinished()
if (!fallbackEnabled) { if (!fallbackEnabled) {
emit messageEditEntry( emit messageEditEntry(
tr("Unable to fetch favicon.") + "\n" tr("Unable to fetch favicon.") + "\n"
+ tr("Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security"), + tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security"),
MessageWidget::Error); MessageWidget::Error);
} else { } else {
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);

View File

@ -41,6 +41,10 @@
#include "keys/FileKey.h" #include "keys/FileKey.h"
#include "keys/PasswordKey.h" #include "keys/PasswordKey.h"
#ifdef Q_OS_MACOS
#include "macutils/MacUtils.h"
#endif
#ifdef WITH_XC_UPDATECHECK #ifdef WITH_XC_UPDATECHECK
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
#include "gui/UpdateCheckDialog.h" #include "gui/UpdateCheckDialog.h"
@ -135,6 +139,7 @@ MainWindow::MainWindow()
, m_trayIcon(nullptr) , m_trayIcon(nullptr)
, m_appExitCalled(false) , m_appExitCalled(false)
, m_appExiting(false) , m_appExiting(false)
, m_lastFocusOutTime(0)
{ {
g_MainWindow = this; g_MainWindow = this;
@ -370,6 +375,9 @@ MainWindow::MainWindow()
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true); setUnifiedTitleAndToolBarOnMac(true);
if (macUtils()->isDarkMode()) {
setStyleSheet("QToolButton {color:white;}");
}
#endif #endif
#ifdef WITH_XC_UPDATECHECK #ifdef WITH_XC_UPDATECHECK
@ -396,6 +404,12 @@ MainWindow::MainWindow()
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock())); connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
#endif #endif
// Tray Icon setup
connect(Application::instance(), SIGNAL(focusWindowChanged(QWindow*)), SLOT(focusWindowChanged(QWindow*)));
m_trayIconTriggerReason = QSystemTrayIcon::Unknown;
m_trayIconTriggerTimer.setSingleShot(true);
connect(&m_trayIconTriggerTimer, SIGNAL(timeout()), SLOT(processTrayIconTrigger()));
updateTrayIcon(); updateTrayIcon();
if (config()->hasAccessError()) { if (config()->hasAccessError()) {
@ -912,7 +926,7 @@ bool MainWindow::saveLastDatabases()
} }
QStringList openDatabases; QStringList openDatabases;
for (int i=0; i < m_ui->tabWidget->count(); ++i) { for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i); auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i);
openDatabases.append(dbWidget->database()->filePath()); openDatabases.append(dbWidget->database()->filePath());
} }
@ -1031,10 +1045,38 @@ void MainWindow::applySettingsChanges()
updateTrayIcon(); updateTrayIcon();
} }
void MainWindow::focusWindowChanged(QWindow* focusWindow)
{
if (focusWindow != windowHandle()) {
m_lastFocusOutTime = Clock::currentSecondsSinceEpoch();
}
}
void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
{ {
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::MiddleClick) { if (!m_trayIconTriggerTimer.isActive()) {
m_trayIconTriggerTimer.start(150);
}
// Overcome Qt bug https://bugreports.qt.io/browse/QTBUG-69698
// Store last issued tray icon activation reason to properly
// capture doubleclick events
m_trayIconTriggerReason = reason;
}
void MainWindow::processTrayIconTrigger()
{
if (m_trayIconTriggerReason == QSystemTrayIcon::DoubleClick) {
// Always toggle window on double click
toggleWindow(); toggleWindow();
} else if (m_trayIconTriggerReason == QSystemTrayIcon::Trigger
|| m_trayIconTriggerReason == QSystemTrayIcon::MiddleClick) {
// On single/middle click focus the window if it is not hidden
// and did not have focus less than a second ago, otherwise toggle
if (isHidden() || (Clock::currentSecondsSinceEpoch() - m_lastFocusOutTime) <= 1) {
toggleWindow();
} else {
bringToFront();
}
} }
} }

View File

@ -85,6 +85,7 @@ private slots:
void showAboutDialog(); void showAboutDialog();
void showUpdateCheckStartup(); void showUpdateCheckStartup();
void showUpdateCheckDialog(); void showUpdateCheckDialog();
void focusWindowChanged(QWindow* focusWindow);
void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested); void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested);
void openDonateUrl(); void openDonateUrl();
void openBugReportUrl(); void openBugReportUrl();
@ -107,6 +108,7 @@ private slots:
void showGroupContextMenu(const QPoint& globalPos); void showGroupContextMenu(const QPoint& globalPos);
void applySettingsChanges(); void applySettingsChanges();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason); void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
void processTrayIconTrigger();
void lockDatabasesAfterInactivity(); void lockDatabasesAfterInactivity();
void forgetTouchIDAfterInactivity(); void forgetTouchIDAfterInactivity();
void handleScreenLock(); void handleScreenLock();
@ -146,6 +148,9 @@ private:
bool m_appExitCalled; bool m_appExitCalled;
bool m_appExiting; bool m_appExiting;
uint m_lastFocusOutTime;
QTimer m_trayIconTriggerTimer;
QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
}; };
/** /**

View File

@ -152,11 +152,6 @@ void EditEntryWidget::setupMain()
m_mainUi->expirePresets->setMenu(createPresetsMenu()); m_mainUi->expirePresets->setMenu(createPresetsMenu());
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*))); connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
QAction* action = new QAction(this);
action->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(action, SIGNAL(triggered()), this, SLOT(commitEntry()));
this->addAction(action);
m_mainUi->passwordGenerator->hide(); m_mainUi->passwordGenerator->hide();
m_mainUi->passwordGenerator->reset(); m_mainUi->passwordGenerator->reset();
} }
@ -285,7 +280,6 @@ void EditEntryWidget::setupEntryUpdate()
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString))); connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
#endif #endif
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified())); connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified())); connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified())); connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
@ -1111,8 +1105,9 @@ void EditEntryWidget::updateCurrentAttribute()
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
{ {
// Block signals to prevent extra calls // Block signals to prevent modified being set
m_advancedUi->protectAttributeButton->blockSignals(true); m_advancedUi->protectAttributeButton->blockSignals(true);
m_advancedUi->attributesEdit->blockSignals(true);
if (index.isValid()) { if (index.isValid()) {
QString key = m_attributesModel->keyByIndex(index); QString key = m_attributesModel->keyByIndex(index);
@ -1143,6 +1138,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
} }
m_advancedUi->protectAttributeButton->blockSignals(false); m_advancedUi->protectAttributeButton->blockSignals(false);
m_advancedUi->attributesEdit->blockSignals(false);
} }
void EditEntryWidget::protectCurrentAttribute(bool state) void EditEntryWidget::protectCurrentAttribute(bool state)

View File

@ -227,6 +227,9 @@ void EditGroupWidget::cancel()
tr("Entry has unsaved changes"), tr("Entry has unsaved changes"),
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard, MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel); MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return;
}
if (result == MessageBox::Save) { if (result == MessageBox::Save) {
apply(); apply();
setModified(false); setModified(false);

View File

@ -19,14 +19,15 @@
#ifndef KEEPASSX_APPKIT_H #ifndef KEEPASSX_APPKIT_H
#define KEEPASSX_APPKIT_H #define KEEPASSX_APPKIT_H
#include <QObject>
#include <unistd.h> #include <unistd.h>
extern "C" { class AppKit : public QObject
class AppKit
{ {
Q_OBJECT
public: public:
AppKit(); AppKit(QObject* parent = nullptr);
~AppKit(); ~AppKit();
pid_t lastActiveProcessId(); pid_t lastActiveProcessId();
@ -37,10 +38,11 @@ public:
bool isHidden(pid_t pid); bool isHidden(pid_t pid);
bool isDarkMode(); bool isDarkMode();
signals:
void lockDatabases();
private: private:
void *self; void *self;
}; };
} // extern "C"
#endif // KEEPASSX_APPKIT_H #endif // KEEPASSX_APPKIT_H

View File

@ -22,6 +22,10 @@
#import <AppKit/NSRunningApplication.h> #import <AppKit/NSRunningApplication.h>
@interface AppKitImpl : NSObject @interface AppKitImpl : NSObject
{
AppKit *m_appkit;
}
- (id) initWithObject:(AppKit *)appkit;
@property (strong) NSRunningApplication *lastActiveApplication; @property (strong) NSRunningApplication *lastActiveApplication;
@ -31,5 +35,6 @@
- (bool) hideProcess:(pid_t) pid; - (bool) hideProcess:(pid_t) pid;
- (bool) isHidden:(pid_t) pid; - (bool) isHidden:(pid_t) pid;
- (bool) isDarkMode; - (bool) isDarkMode;
- (void) userSwitchHandler:(NSNotification*) notification;
@end @end

View File

@ -22,19 +22,22 @@
@implementation AppKitImpl @implementation AppKitImpl
AppKit::AppKit() - (id) initWithObject:(AppKit *)appkit
{ {
self = [[AppKitImpl alloc] init]; self = [super init];
if (self) {
m_appkit = appkit;
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self) [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
selector:@selector(didDeactivateApplicationObserver:) selector:@selector(didDeactivateApplicationObserver:)
name:NSWorkspaceDidDeactivateApplicationNotification name:NSWorkspaceDidDeactivateApplicationNotification
object:nil]; object:nil];
}
AppKit::~AppKit() [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
{ selector:@selector(userSwitchHandler:)
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)]; name:NSWorkspaceSessionDidResignActiveNotification
[static_cast<id>(self) dealloc]; object:nil];
}
return self;
} }
// //
@ -104,10 +107,34 @@ AppKit::~AppKit()
&& NSOrderedSame == [style caseInsensitiveCompare:@"dark"] ); && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] );
} }
//
// Notification for user switch
//
- (void) userSwitchHandler:(NSNotification*) notification
{
if ([[notification name] isEqualToString:NSWorkspaceSessionDidResignActiveNotification] && m_appkit)
{
emit m_appkit->lockDatabases();
}
}
@end
// //
// ------------------------- C++ Trampolines ------------------------- // ------------------------- C++ Trampolines -------------------------
// //
AppKit::AppKit(QObject* parent) : QObject(parent)
{
self = [[AppKitImpl alloc] initWithObject:this];
}
AppKit::~AppKit()
{
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
[static_cast<id>(self) dealloc];
}
pid_t AppKit::lastActiveProcessId() pid_t AppKit::lastActiveProcessId()
{ {
return [static_cast<id>(self) lastActiveApplication].processIdentifier; return [static_cast<id>(self) lastActiveApplication].processIdentifier;
@ -142,5 +169,3 @@ bool AppKit::isDarkMode()
{ {
return [static_cast<id>(self) isDarkMode]; return [static_cast<id>(self) isDarkMode];
} }
@end

View File

@ -24,7 +24,7 @@ MacUtils* MacUtils::m_instance = nullptr;
MacUtils::MacUtils(QObject* parent) : QObject(parent) MacUtils::MacUtils(QObject* parent) : QObject(parent)
, m_appkit(new AppKit()) , m_appkit(new AppKit())
{ {
connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases()));
} }
MacUtils::~MacUtils() MacUtils::~MacUtils()

View File

@ -39,14 +39,16 @@ public:
bool isHidden(); bool isHidden();
bool isDarkMode(); bool isDarkMode();
signals:
void lockDatabases();
private: private:
explicit MacUtils(QObject* parent = nullptr); explicit MacUtils(QObject* parent = nullptr);
~MacUtils(); ~MacUtils();
private: private:
std::unique_ptr<AppKit> m_appkit; QScopedPointer<AppKit> m_appkit;
static MacUtils* m_instance; static MacUtils* m_instance;
void* self;
Q_DISABLE_COPY(MacUtils) Q_DISABLE_COPY(MacUtils)
}; };

View File

@ -1,4 +1,6 @@
if(WITH_XC_KEESHARE) if(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(keeshare_SOURCES set(keeshare_SOURCES
@ -15,9 +17,19 @@ if(WITH_XC_KEESHARE)
) )
add_library(keeshare STATIC ${keeshare_SOURCES}) add_library(keeshare STATIC ${keeshare_SOURCES})
if(WITH_XC_KEESHARE_SECURE) target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
# Try to find libquazip5, if found, enable secure sharing
find_package(QuaZip)
if(QUAZIP_FOUND)
set(WITH_XC_KEESHARE_SECURE ON PARENT_SCOPE)
target_include_directories(keeshare SYSTEM PRIVATE ${QUAZIP_INCLUDE_DIR})
target_link_libraries(keeshare PRIVATE ${QUAZIP_LIBRARIES})
else() else()
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
message(STATUS "KeeShare: Secure container support is DISABLED; quazip library not found")
endif() endif()
endif() else(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE OFF PARENT_SCOPE)
set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
endif(WITH_XC_KEESHARE)

View File

@ -46,8 +46,8 @@
#include <QStringBuilder> #include <QStringBuilder>
#if defined(WITH_XC_KEESHARE_SECURE) #if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip5/quazip.h> #include <quazip.h>
#include <quazip5/quazipfile.h> #include <quazipfile.h>
#endif #endif
namespace namespace

View File

@ -18,19 +18,35 @@
#include "FileKey.h" #include "FileKey.h"
#include <QFile>
#include "core/Tools.h" #include "core/Tools.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/Random.h" #include "crypto/Random.h"
#include <QFile>
#include <sodium.h>
#include <gcrypt.h>
#include <algorithm>
#include <cstring>
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273"); QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
constexpr int FileKey::SHA256_SIZE;
FileKey::FileKey() FileKey::FileKey()
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{ {
} }
FileKey::~FileKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
/** /**
* Read key file from device while trying to detect its file format. * Read key file from device while trying to detect its file format.
* *
@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
*/ */
QByteArray FileKey::rawKey() const QByteArray FileKey::rawKey() const
{ {
return m_key; if (!m_key) {
return {};
}
return QByteArray::fromRawData(m_key, SHA256_SIZE);
} }
/** /**
@ -223,12 +242,15 @@ bool FileKey::loadXml(QIODevice* device)
} }
} }
bool ok = false;
if (!xmlReader.error() && correctMeta && !data.isEmpty()) { if (!xmlReader.error() && correctMeta && !data.isEmpty()) {
m_key = data; std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
return true; ok = true;
} }
return false; sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
return ok;
} }
/** /**
@ -293,7 +315,8 @@ bool FileKey::loadBinary(QIODevice* device)
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) { if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
return false; return false;
} else { } else {
m_key = data; std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
return true; return true;
} }
} }
@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device)
} }
QByteArray key = QByteArray::fromHex(data); QByteArray key = QByteArray::fromHex(data);
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
if (key.size() != 32) { if (key.size() != 32) {
return false; return false;
} }
m_key = key; std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size()));
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
return true; return true;
} }
@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device)
cryptoHash.addData(buffer); cryptoHash.addData(buffer);
} while (!buffer.isEmpty()); } while (!buffer.isEmpty());
m_key = cryptoHash.result(); auto result = cryptoHash.result();
std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size()));
sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity()));
return true; return true;
} }

View File

@ -40,6 +40,7 @@ public:
}; };
FileKey(); FileKey();
~FileKey() override;
bool load(QIODevice* device); bool load(QIODevice* device);
bool load(const QString& fileName, QString* errorMsg = nullptr); bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override; QByteArray rawKey() const override;
@ -48,6 +49,8 @@ public:
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128); static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
private: private:
static constexpr int SHA256_SIZE = 32;
bool loadXml(QIODevice* device); bool loadXml(QIODevice* device);
bool loadXmlMeta(QXmlStreamReader& xmlReader); bool loadXmlMeta(QXmlStreamReader& xmlReader);
QByteArray loadXmlKey(QXmlStreamReader& xmlReader); QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
@ -55,7 +58,7 @@ private:
bool loadHex(QIODevice* device); bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device); bool loadHashed(QIODevice* device);
QByteArray m_key; char* m_key = nullptr;
Type m_type = None; Type m_type = None;
}; };

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,35 +16,51 @@
*/ */
#include "PasswordKey.h" #include "PasswordKey.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include <gcrypt.h>
#include <algorithm>
#include <cstring>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
constexpr int PasswordKey::SHA256_SIZE;
PasswordKey::PasswordKey() PasswordKey::PasswordKey()
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{ {
} }
PasswordKey::PasswordKey(const QString& password) PasswordKey::PasswordKey(const QString& password)
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{ {
setPassword(password); setPassword(password);
} }
PasswordKey::~PasswordKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey) QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
{ {
auto result = QSharedPointer<PasswordKey>::create(); auto result = QSharedPointer<PasswordKey>::create();
result->m_key = rawKey; std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
return result; return result;
} }
QByteArray PasswordKey::rawKey() const QByteArray PasswordKey::rawKey() const
{ {
return m_key; return QByteArray::fromRawData(m_key, SHA256_SIZE);
} }
void PasswordKey::setPassword(const QString& password) void PasswordKey::setPassword(const QString& password)
{ {
m_key = CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256); std::memcpy(m_key, CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256).data(), SHA256_SIZE);
} }

View File

@ -30,13 +30,16 @@ public:
PasswordKey(); PasswordKey();
explicit PasswordKey(const QString& password); explicit PasswordKey(const QString& password);
~PasswordKey() override;
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setPassword(const QString& password); void setPassword(const QString& password);
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey); static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
private: private:
QByteArray m_key; static constexpr int SHA256_SIZE = 32;
char* m_key = nullptr;
}; };
#endif // KEEPASSX_PASSWORDKEY_H #endif // KEEPASSX_PASSWORDKEY_H

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com> * Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -32,6 +32,10 @@
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QtConcurrent> #include <QtConcurrent>
#include <gcrypt.h>
#include <sodium.h>
#include <cstring>
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508"); QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
@ -45,9 +49,18 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
} }
} }
YkChallengeResponseKey::~YkChallengeResponseKey()
{
if (m_key) {
gcry_free(m_key);
m_keySize = 0;
m_key = nullptr;
}
}
QByteArray YkChallengeResponseKey::rawKey() const QByteArray YkChallengeResponseKey::rawKey() const
{ {
return m_key; return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
} }
/** /**
@ -67,14 +80,22 @@ bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int
emit userInteractionRequired(); emit userInteractionRequired();
} }
QByteArray key;
auto result = AsyncTask::runAndWaitForFuture( auto result = AsyncTask::runAndWaitForFuture(
[this, challenge]() { return YubiKey::instance()->challenge(m_slot, true, challenge, m_key); }); [this, challenge, &key]() { return YubiKey::instance()->challenge(m_slot, true, challenge, key); });
if (m_blocking) { if (m_blocking) {
emit userConfirmed(); emit userConfirmed();
} }
if (result == YubiKey::SUCCESS) { if (result == YubiKey::SUCCESS) {
if (m_key) {
gcry_free(m_key);
}
m_keySize = static_cast<std::size_t>(key.size());
m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
std::memcpy(m_key, key.data(), m_keySize);
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
return true; return true;
} }
} while (retries > 0); } while (retries > 0);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -32,6 +32,7 @@ public:
static QUuid UUID; static QUuid UUID;
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false); explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
~YkChallengeResponseKey() override;
QByteArray rawKey() const override; QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override; bool challenge(const QByteArray& challenge) override;
@ -52,7 +53,8 @@ signals:
void userConfirmed(); void userConfirmed();
private: private:
QByteArray m_key; char* m_key = nullptr;
std::size_t m_keySize = 0;
int m_slot; int m_slot;
bool m_blocking; bool m_blocking;
}; };

View File

@ -18,12 +18,13 @@ if(WITH_XC_BROWSER)
include_directories(${BROWSER_SOURCE_DIR}) include_directories(${BROWSER_SOURCE_DIR})
set(proxy_SOURCES set(proxy_SOURCES
../core/Alloc.cpp
keepassxc-proxy.cpp keepassxc-proxy.cpp
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp ${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
NativeMessagingHost.cpp) NativeMessagingHost.cpp)
add_library(proxy STATIC ${proxy_SOURCES}) add_library(proxy STATIC ${proxy_SOURCES})
target_link_libraries(proxy Qt5::Core Qt5::Network) target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy keepassxc-proxy.cpp) add_executable(keepassxc-proxy keepassxc-proxy.cpp)
target_link_libraries(keepassxc-proxy proxy) target_link_libraries(keepassxc-proxy proxy)

View File

@ -15,6 +15,7 @@
inline void debug(const char* message, ...) inline void debug(const char* message, ...)
{ {
Q_UNUSED(message);
// qWarning(...); // qWarning(...);
} }
@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const
NSString* authMessage = msg.toNSString(); // autoreleased NSString* authMessage = msg.toNSString(); // autoreleased
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:authMessage reply:^(BOOL success, NSError* error) { localizedReason:authMessage reply:^(BOOL success, NSError* error) {
Q_UNUSED(error);
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed; result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
CFRunLoopWakeUp(CFRunLoopGetCurrent()); CFRunLoopWakeUp(CFRunLoopGetCurrent());
}]; }];

View File

@ -186,8 +186,10 @@ void TestKdbx4::testFormat400Upgrade()
QCOMPARE(reader.version(), expectedVersion); QCOMPARE(reader.version(), expectedVersion);
QCOMPARE(targetDb->cipher(), cipherUuid); QCOMPARE(targetDb->cipher(), cipherUuid);
QCOMPARE(*targetDb->metadata()->customData(), *sourceDb->metadata()->customData()); QCOMPARE(targetDb->metadata()->customData()->value("CustomPublicData"),
QCOMPARE(*targetDb->rootGroup()->customData(), *sourceDb->rootGroup()->customData()); sourceDb->metadata()->customData()->value("CustomPublicData"));
QCOMPARE(targetDb->rootGroup()->customData()->value("CustomGroupData"),
sourceDb->rootGroup()->customData()->value("CustomGroupData"));
} }
// clang-format off // clang-format off
@ -346,20 +348,22 @@ void TestKdbx4::testCustomData()
const QString customDataKey2 = "CD2"; const QString customDataKey2 = "CD2";
const QString customData1 = "abcäöü"; const QString customData1 = "abcäöü";
const QString customData2 = "Hello World"; const QString customData2 = "Hello World";
const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size()
+ customData2.toUtf8().size();
// test custom database data // test custom database data
db.metadata()->customData()->set(customDataKey1, customData1); db.metadata()->customData()->set(customDataKey1, customData1);
db.metadata()->customData()->set(customDataKey2, customData2); db.metadata()->customData()->set(customDataKey2, customData2);
QCOMPARE(db.metadata()->customData()->size(), 2); auto lastModified = db.metadata()->customData()->value(CustomData::LastModified);
const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size()
+ customData2.toUtf8().size() + lastModified.toUtf8().size()
+ CustomData::LastModified.toUtf8().size();
QCOMPARE(db.metadata()->customData()->size(), 3);
QCOMPARE(db.metadata()->customData()->dataSize(), dataSize); QCOMPARE(db.metadata()->customData()->dataSize(), dataSize);
// test custom root group data // test custom root group data
Group* root = db.rootGroup(); Group* root = db.rootGroup();
root->customData()->set(customDataKey1, customData1); root->customData()->set(customDataKey1, customData1);
root->customData()->set(customDataKey2, customData2); root->customData()->set(customDataKey2, customData2);
QCOMPARE(root->customData()->size(), 2); QCOMPARE(root->customData()->size(), 3);
QCOMPARE(root->customData()->dataSize(), dataSize); QCOMPARE(root->customData()->dataSize(), dataSize);
// test copied custom group data // test copied custom group data
@ -378,9 +382,9 @@ void TestKdbx4::testCustomData()
// test custom data deletion // test custom data deletion
entry->customData()->set("additional item", "foobar"); entry->customData()->set("additional item", "foobar");
QCOMPARE(entry->customData()->size(), 3); QCOMPARE(entry->customData()->size(), 4);
entry->customData()->remove("additional item"); entry->customData()->remove("additional item");
QCOMPARE(entry->customData()->size(), 2); QCOMPARE(entry->customData()->size(), 3);
QCOMPARE(entry->customData()->dataSize(), dataSize); QCOMPARE(entry->customData()->dataSize(), dataSize);
// test custom data on cloned groups // test custom data on cloned groups

View File

@ -1164,6 +1164,65 @@ void TestMerge::testMetadata()
// will be used - exception is the target has no recycle bin activated // will be used - exception is the target has no recycle bin activated
} }
void TestMerge::testCustomdata()
{
QScopedPointer<Database> dbDestination(new Database());
QScopedPointer<Database> dbSource(createTestDatabase());
QScopedPointer<Database> dbDestination2(new Database());
QScopedPointer<Database> dbSource2(createTestDatabase());
m_clock->advanceSecond(1);
dbDestination->metadata()->customData()->set("toBeDeleted", "value");
dbDestination->metadata()->customData()->set("key3", "oldValue");
dbSource2->metadata()->customData()->set("key1", "value1");
dbSource2->metadata()->customData()->set("key2", "value2");
dbSource2->metadata()->customData()->set("key3", "newValue");
dbSource2->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
m_clock->advanceSecond(1);
dbSource->metadata()->customData()->set("key1", "value1");
dbSource->metadata()->customData()->set("key2", "value2");
dbSource->metadata()->customData()->set("key3", "newValue");
dbSource->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
dbDestination2->metadata()->customData()->set("notToBeDeleted", "value");
dbDestination2->metadata()->customData()->set("key3", "oldValue");
// Sanity check.
QVERIFY(!dbSource->metadata()->customData()->isEmpty());
QVERIFY(!dbSource2->metadata()->customData()->isEmpty());
m_clock->advanceSecond(1);
Merger merger(dbSource.data(), dbDestination.data());
merger.merge();
Merger merger2(dbSource2.data(), dbDestination2.data());
merger2.merge();
// Source is newer, data should be merged
QVERIFY(!dbDestination->metadata()->customData()->isEmpty());
QVERIFY(dbDestination->metadata()->customData()->contains("key1"));
QVERIFY(dbDestination->metadata()->customData()->contains("key2"));
QVERIFY(dbDestination->metadata()->customData()->contains("Browser"));
QVERIFY(!dbDestination->metadata()->customData()->contains("toBeDeleted"));
QCOMPARE(dbDestination->metadata()->customData()->value("key1"), QString("value1"));
QCOMPARE(dbDestination->metadata()->customData()->value("key2"), QString("value2"));
QCOMPARE(dbDestination->metadata()->customData()->value("Browser"), QString("n'8=3W@L^6d->d.]St_>]"));
QCOMPARE(dbDestination->metadata()->customData()->value("key3"), QString("newValue")); // Old value should be replaced
// Target is newer, no data is merged
QVERIFY(!dbDestination2->metadata()->customData()->isEmpty());
QVERIFY(!dbDestination2->metadata()->customData()->contains("key1"));
QVERIFY(!dbDestination2->metadata()->customData()->contains("key2"));
QVERIFY(!dbDestination2->metadata()->customData()->contains("Browser"));
QVERIFY(dbDestination2->metadata()->customData()->contains("notToBeDeleted"));
QCOMPARE(dbDestination2->metadata()->customData()->value("key3"), QString("oldValue")); // Old value should not be replaced
}
void TestMerge::testDeletedEntry() void TestMerge::testDeletedEntry()
{ {
QScopedPointer<Database> dbDestination(createTestDatabase()); QScopedPointer<Database> dbDestination(createTestDatabase());

View File

@ -59,6 +59,7 @@ private slots:
void testMergeCustomIcons(); void testMergeCustomIcons();
void testMergeDuplicateCustomIcons(); void testMergeDuplicateCustomIcons();
void testMetadata(); void testMetadata();
void testCustomdata();
void testDeletedEntry(); void testDeletedEntry();
void testDeletedGroup(); void testDeletedGroup();
void testDeletedRevertedEntry(); void testDeletedRevertedEntry();

View File

@ -1286,11 +1286,11 @@ void TestGui::testTrayRestoreHide()
QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test..."); QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test...");
} }
m_mainWindow->hideWindow();
QVERIFY(!m_mainWindow->isVisible());
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>(); auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
QVERIFY(m_mainWindow->isVisible()); QVERIFY(trayIcon);
trayIcon->activated(QSystemTrayIcon::Trigger);
QTRY_VERIFY(!m_mainWindow->isVisible());
trayIcon->activated(QSystemTrayIcon::Trigger); trayIcon->activated(QSystemTrayIcon::Trigger);
QTRY_VERIFY(m_mainWindow->isVisible()); QTRY_VERIFY(m_mainWindow->isVisible());
@ -1298,8 +1298,17 @@ void TestGui::testTrayRestoreHide()
trayIcon->activated(QSystemTrayIcon::Trigger); trayIcon->activated(QSystemTrayIcon::Trigger);
QTRY_VERIFY(!m_mainWindow->isVisible()); QTRY_VERIFY(!m_mainWindow->isVisible());
trayIcon->activated(QSystemTrayIcon::Trigger); trayIcon->activated(QSystemTrayIcon::MiddleClick);
QTRY_VERIFY(m_mainWindow->isVisible()); QTRY_VERIFY(m_mainWindow->isVisible());
trayIcon->activated(QSystemTrayIcon::MiddleClick);
QTRY_VERIFY(!m_mainWindow->isVisible());
trayIcon->activated(QSystemTrayIcon::DoubleClick);
QTRY_VERIFY(m_mainWindow->isVisible());
trayIcon->activated(QSystemTrayIcon::DoubleClick);
QTRY_VERIFY(!m_mainWindow->isVisible());
} }
int TestGui::addCannedEntries() int TestGui::addCannedEntries()