mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge pull request #608 from varjolintu/feature/chromeKeePassXC
Support for browser extension(s) with Native Messaging
This commit is contained in:
commit
a5a5c6723e
@ -44,6 +44,7 @@ option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
option(WITH_XC_BROWSER "Include browser extension support." OFF)
|
||||
|
||||
# Process ui files automatically from source files
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
@ -200,11 +201,13 @@ endif()
|
||||
|
||||
if(MINGW)
|
||||
set(CLI_INSTALL_DIR ".")
|
||||
set(PROXY_INSTALL_DIR ".")
|
||||
set(BIN_INSTALL_DIR ".")
|
||||
set(PLUGIN_INSTALL_DIR ".")
|
||||
set(DATA_INSTALL_DIR "share")
|
||||
elseif(APPLE AND WITH_APP_BUNDLE)
|
||||
set(CLI_INSTALL_DIR "/usr/local/bin")
|
||||
set(PROXY_INSTALL_DIR "/usr/local/bin")
|
||||
set(BIN_INSTALL_DIR ".")
|
||||
set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns")
|
||||
set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources")
|
||||
@ -212,6 +215,7 @@ else()
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PROXY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassxc")
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc")
|
||||
|
10
Dockerfile
10
Dockerfile
@ -25,7 +25,8 @@ RUN set -x \
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
|
||||
&& add-apt-repository ppa:phoerious/keepassxc
|
||||
&& add-apt-repository ppa:phoerious/keepassxc \
|
||||
&& LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update -y \
|
||||
@ -46,7 +47,8 @@ RUN set -x \
|
||||
libxtst-dev \
|
||||
mesa-common-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev
|
||||
libykpers-1-dev \
|
||||
libsodium-dev
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
|
||||
@ -59,6 +61,10 @@ RUN set -x \
|
||||
libfuse2 \
|
||||
wget
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
VOLUME /keepassxc/src
|
||||
VOLUME /keepassxc/out
|
||||
WORKDIR /keepassxc
|
||||
|
12
INSTALL.md
12
INSTALL.md
@ -6,7 +6,7 @@ You can visit the online version of this document at the following link:
|
||||
|
||||
https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source
|
||||
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
|
||||
Windows, Mac, or Linux computer using the pre-built binaries.
|
||||
|
||||
Build Dependencies
|
||||
@ -25,6 +25,7 @@ The following libraries are required:
|
||||
* zlib
|
||||
* libmicrohttpd
|
||||
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
|
||||
* libsodium (>= 1.0.12, optional for keepassxc-browser support)
|
||||
|
||||
|
||||
Prepare the Building Environment
|
||||
@ -40,7 +41,7 @@ Build Steps
|
||||
To compile from source, open a **Terminal (on Linux/MacOS)** or a **MSYS2-MinGW shell (on Windows)**<br/>
|
||||
**Note:** on Windows make sure you are using a **MINGW shell** by checking the label before the current path
|
||||
|
||||
First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
|
||||
First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
|
||||
or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
@ -66,10 +67,10 @@ cd build
|
||||
cmake -DWITH_TESTS=OFF ...and other options - see below...
|
||||
make
|
||||
```
|
||||
These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
|
||||
These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
|
||||
(Note the cmake notes/options below.)
|
||||
|
||||
**Cmake Notes:**
|
||||
**Cmake Notes:**
|
||||
|
||||
* Common cmake parameters
|
||||
|
||||
@ -86,7 +87,8 @@ 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_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (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_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_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
|
||||
|
45
README.md
45
README.md
@ -1,19 +1,19 @@
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
|
||||
[![TeamCity Build Status](https://ci.keepassxc.org/app/rest/builds/buildType:\(id:KeepassXC_TeamCityCi\)/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
|
||||
## About KeePassXC
|
||||
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
|
||||
[KeePassX](https://www.keepassx.org/).
|
||||
Our goal is to extend and improve it with new features and bugfixes
|
||||
to provide a feature-rich, fully cross-platform and modern
|
||||
[KeePassX](https://www.keepassx.org/).
|
||||
Our goal is to extend and improve it with new features and bugfixes
|
||||
to provide a feature-rich, fully cross-platform and modern
|
||||
open-source password manager.
|
||||
|
||||
## Installation
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
|
||||
KeePassXC on your Windows, Mac, or Linux computer using pre-compiled binaries
|
||||
from the [downloads page](https://keepassxc.org/download).
|
||||
|
||||
Additionally, individual Linux distributions may ship their own versions,
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
|
||||
KeePassXC on your Windows, Mac, or Linux computer using pre-compiled binaries
|
||||
from the [downloads page](https://keepassxc.org/download).
|
||||
|
||||
Additionally, individual Linux distributions may ship their own versions,
|
||||
so please check out your distribution's package list to see if KeePassXC is available.
|
||||
|
||||
## Additional features compared to KeePassX
|
||||
@ -29,24 +29,25 @@ so please check out your distribution's package list to see if KeePassXC is avai
|
||||
- Using website favicons as entry icons
|
||||
- Merging of databases
|
||||
- Automatic reload when the database changed on disk
|
||||
- Browser integration with KeePassHTTP-Connector for
|
||||
[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
|
||||
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
|
||||
[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#note-about-keepasshttp)
|
||||
- Browser integration with KeePassHTTP-Connector for
|
||||
[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
|
||||
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
|
||||
[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#Note_about_KeePassHTTP)
|
||||
- Browser integration with keepassxc-browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/iopaggbpplllidnfmcghoonnokmjoicf)
|
||||
- Many bug fixes
|
||||
|
||||
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
|
||||
|
||||
## Building KeePassXC
|
||||
|
||||
Detailed instructions are available in the [Build and Install](./INSTALL.md)
|
||||
Detailed instructions are available in the [Build and Install](./INSTALL.md)
|
||||
page or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).
|
||||
|
||||
## Contributing
|
||||
|
||||
We are always looking for suggestions how to improve our application.
|
||||
If you find any bugs or have an idea for a new feature, please let us know by
|
||||
opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
|
||||
We are always looking for suggestions how to improve our application.
|
||||
If you find any bugs or have an idea for a new feature, please let us know by
|
||||
opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
|
||||
on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
|
||||
|
||||
You can of course also directly contribute your own code. We are happy to accept your pull requests.
|
||||
@ -57,11 +58,11 @@ Please read the [CONTRIBUTING document](.github/CONTRIBUTING.md) for further inf
|
||||
The KeePassHTTP protocol is not a highly secure protocol.
|
||||
It has a certain flaw which could allow an attacker to decrypt your passwords
|
||||
should they manage to impersonate the web browser extension from a remote address.
|
||||
<!--intercept communication between a KeePassHTTP server
|
||||
<!--intercept communication between a KeePassHTTP server
|
||||
and PassIFox/chromeIPass over a network connection -->
|
||||
(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
|
||||
(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
|
||||
|
||||
To minimize the risk, KeePassXC strictly limits communication between itself
|
||||
and the browser plugin to your local computer (localhost).
|
||||
This makes your passwords quite safe,
|
||||
To minimize the risk, KeePassXC strictly limits communication between itself
|
||||
and the browser plugin to your local computer (localhost).
|
||||
This makes your passwords quite safe,
|
||||
but as with all open source software, use it at your own risk!
|
||||
|
@ -16,7 +16,18 @@
|
||||
|
||||
# TIP: check this Dockerfile using this online tool: https://www.fromlatest.io
|
||||
|
||||
FROM ubuntu:trusty
|
||||
FROM ubuntu:14.04
|
||||
|
||||
ENV QT5_VERSION=53
|
||||
ENV QT5_PPA_VERSION=${QT5_VERSION}2
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update -y \
|
||||
&& apt-get -y install software-properties-common
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
|
||||
&& LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get -y update \
|
||||
@ -24,13 +35,21 @@ RUN set -x \
|
||||
git build-essential clang-3.6 libclang-common-3.6-dev clang-format-3.6 cmake3 make \
|
||||
curl ca-certificates gnupg2 \
|
||||
libgcrypt20-dev zlib1g-dev libyubikey-dev libykpers-1-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
qtbase5-dev \
|
||||
libqt5x11extras5-dev \
|
||||
qt${QT5_VERSION}base \
|
||||
qt${QT5_VERSION}tools \
|
||||
qt${QT5_VERSION}x11extras \
|
||||
qt${QT5_VERSION}translations \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
xvfb \
|
||||
libsodium-dev
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
|
||||
RUN set -x \
|
||||
&& echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
267
cmake/Findsodium.cmake
Normal file
267
cmake/Findsodium.cmake
Normal file
@ -0,0 +1,267 @@
|
||||
# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>
|
||||
#
|
||||
# To the extent possible under law, the author(s) have dedicated all
|
||||
# copyright and related and neighboring rights to this software to the
|
||||
# public domain worldwide. This software is distributed without any warranty.
|
||||
#
|
||||
# You should have received a copy of the CC0 Public Domain Dedication
|
||||
# along with this software. If not, see
|
||||
#
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
#
|
||||
########################################################################
|
||||
# Tries to find the local libsodium installation.
|
||||
#
|
||||
# On Windows the sodium_DIR environment variable is used as a default
|
||||
# hint which can be overridden by setting the corresponding cmake variable.
|
||||
#
|
||||
# Once done the following variables will be defined:
|
||||
#
|
||||
# sodium_FOUND
|
||||
# sodium_INCLUDE_DIR
|
||||
# sodium_LIBRARY_DEBUG
|
||||
# sodium_LIBRARY_RELEASE
|
||||
#
|
||||
#
|
||||
# Furthermore an imported "sodium" target is created.
|
||||
#
|
||||
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU"
|
||||
OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
set(_GCC_COMPATIBLE 1)
|
||||
endif()
|
||||
|
||||
# static library option
|
||||
option(sodium_USE_STATIC_LIBS "enable to statically link against sodium")
|
||||
if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))
|
||||
unset(sodium_LIBRARY CACHE)
|
||||
unset(sodium_LIBRARY_DEBUG CACHE)
|
||||
unset(sodium_LIBRARY_RELEASE CACHE)
|
||||
unset(sodium_DLL_DEBUG CACHE)
|
||||
unset(sodium_DLL_RELEASE CACHE)
|
||||
set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable")
|
||||
endif()
|
||||
|
||||
|
||||
########################################################################
|
||||
# UNIX
|
||||
if (UNIX)
|
||||
# import pkg-config
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(sodium_PKG QUIET libsodium)
|
||||
endif()
|
||||
|
||||
if(sodium_USE_STATIC_LIBS)
|
||||
set(XPREFIX sodium_PKG_STATIC)
|
||||
else()
|
||||
set(XPREFIX sodium_PKG)
|
||||
endif()
|
||||
|
||||
find_path(sodium_INCLUDE_DIR sodium.h
|
||||
HINTS ${${XPREFIX}_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} sodium
|
||||
HINTS ${${XPREFIX}_LIBRARY_DIRS}
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} sodium
|
||||
HINTS ${${XPREFIX}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
|
||||
########################################################################
|
||||
# Windows
|
||||
elseif (WIN32)
|
||||
set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory")
|
||||
mark_as_advanced(sodium_DIR)
|
||||
|
||||
find_path(sodium_INCLUDE_DIR sodium.h
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
# detect target architecture
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.c" [=[
|
||||
#if defined _M_IX86
|
||||
#error ARCH_VALUE x86_32
|
||||
#elif defined _M_X64
|
||||
#error ARCH_VALUE x86_64
|
||||
#endif
|
||||
#error ARCH_VALUE unknown
|
||||
]=])
|
||||
try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.c"
|
||||
OUTPUT_VARIABLE _COMPILATION_LOG
|
||||
)
|
||||
string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}")
|
||||
|
||||
# construct library path
|
||||
if (_TARGET_ARCH STREQUAL "x86_32")
|
||||
string(APPEND _PLATFORM_PATH "Win32")
|
||||
elseif(_TARGET_ARCH STREQUAL "x86_64")
|
||||
string(APPEND _PLATFORM_PATH "x64")
|
||||
else()
|
||||
message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.")
|
||||
endif()
|
||||
string(APPEND _PLATFORM_PATH "/$$CONFIG$$")
|
||||
|
||||
if (MSVC_VERSION LESS 1900)
|
||||
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60")
|
||||
else()
|
||||
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50")
|
||||
endif()
|
||||
string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}")
|
||||
|
||||
if (sodium_USE_STATIC_LIBS)
|
||||
string(APPEND _PLATFORM_PATH "/static")
|
||||
else()
|
||||
string(APPEND _PLATFORM_PATH "/dynamic")
|
||||
endif()
|
||||
|
||||
string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}")
|
||||
string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}")
|
||||
|
||||
find_library(sodium_LIBRARY_DEBUG libsodium.lib
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE libsodium.lib
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
|
||||
)
|
||||
if (NOT sodium_USE_STATIC_LIBS)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
|
||||
find_library(sodium_DLL_DEBUG libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
|
||||
)
|
||||
find_library(sodium_DLL_RELEASE libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
|
||||
)
|
||||
endif()
|
||||
|
||||
elseif(_GCC_COMPATIBLE)
|
||||
if (sodium_USE_STATIC_LIBS)
|
||||
find_library(sodium_LIBRARY_DEBUG libsodium.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE libsodium.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
else()
|
||||
find_library(sodium_LIBRARY_DEBUG libsodium.dll.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE libsodium.dll.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
|
||||
file(GLOB _DLL
|
||||
LIST_DIRECTORIES false
|
||||
RELATIVE "${sodium_DIR}/bin"
|
||||
"${sodium_DIR}/bin/libsodium*.dll"
|
||||
)
|
||||
find_library(sodium_DLL_DEBUG ${_DLL} libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES bin
|
||||
)
|
||||
find_library(sodium_DLL_RELEASE ${_DLL} libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES bin
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
|
||||
endif()
|
||||
|
||||
|
||||
########################################################################
|
||||
# unsupported
|
||||
else()
|
||||
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
|
||||
endif()
|
||||
|
||||
|
||||
########################################################################
|
||||
# common stuff
|
||||
|
||||
# extract sodium version
|
||||
if (sodium_INCLUDE_DIR)
|
||||
set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h")
|
||||
if (EXISTS _VERSION_HEADER)
|
||||
file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT)
|
||||
string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1"
|
||||
sodium_VERSION "${_VERSION_HEADER_CONTENT}")
|
||||
set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# communicate results
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(sodium
|
||||
REQUIRED_VARS
|
||||
sodium_LIBRARY_RELEASE
|
||||
sodium_LIBRARY_DEBUG
|
||||
sodium_INCLUDE_DIR
|
||||
VERSION_VAR
|
||||
sodium_VERSION
|
||||
)
|
||||
|
||||
# mark file paths as advanced
|
||||
mark_as_advanced(sodium_INCLUDE_DIR)
|
||||
mark_as_advanced(sodium_LIBRARY_DEBUG)
|
||||
mark_as_advanced(sodium_LIBRARY_RELEASE)
|
||||
if (WIN32)
|
||||
mark_as_advanced(sodium_DLL_DEBUG)
|
||||
mark_as_advanced(sodium_DLL_RELEASE)
|
||||
endif()
|
||||
|
||||
# create imported target
|
||||
if(sodium_USE_STATIC_LIBS)
|
||||
set(_LIB_TYPE STATIC)
|
||||
else()
|
||||
set(_LIB_TYPE SHARED)
|
||||
endif()
|
||||
add_library(sodium ${_LIB_TYPE} IMPORTED)
|
||||
|
||||
set_target_properties(sodium PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
)
|
||||
|
||||
if (sodium_USE_STATIC_LIBS)
|
||||
set_target_properties(sodium PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC"
|
||||
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
|
||||
)
|
||||
else()
|
||||
if (UNIX)
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
|
||||
)
|
||||
elseif (WIN32)
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}"
|
||||
IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}"
|
||||
)
|
||||
if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND"))
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}"
|
||||
)
|
||||
endif()
|
||||
if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND"))
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}"
|
||||
IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}"
|
||||
IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
@ -29,6 +29,7 @@ parts:
|
||||
- -DWITH_XC_AUTOTYPE=ON
|
||||
- -DWITH_XC_HTTP=ON
|
||||
- -DWITH_XC_YUBIKEY=ON
|
||||
- -DWITH_XC_BROWSER=ON
|
||||
build-packages:
|
||||
- g++
|
||||
- libgcrypt20-dev
|
||||
@ -41,6 +42,7 @@ parts:
|
||||
- libxtst-dev
|
||||
- libyubikey-dev
|
||||
- libykpers-1-dev
|
||||
- libsodium-dev
|
||||
stage-packages:
|
||||
- dbus
|
||||
- qttranslations5-l10n # common translations
|
||||
@ -65,4 +67,3 @@ parts:
|
||||
- libqt5svg5 # for loading icon themes which are svg
|
||||
- locales-all
|
||||
- xdg-user-dirs
|
||||
|
||||
|
@ -182,12 +182,20 @@ add_feature_info(AutoType WITH_XC_AUTOTYPE "Automatic password typing")
|
||||
add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
|
||||
add_feature_info(keepassxc-browser WITH_XC_BROWSER "KeePassXC browser support with keepassxc-browser")
|
||||
|
||||
add_subdirectory(http)
|
||||
if(WITH_XC_HTTP)
|
||||
set(keepasshttp_LIB keepasshttp qhttp Qt5::Network)
|
||||
endif()
|
||||
|
||||
set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser)
|
||||
add_subdirectory(browser)
|
||||
add_subdirectory(proxy)
|
||||
if(WITH_XC_BROWSER)
|
||||
set(keepassxcbrowser_LIB keepassxcbrowser)
|
||||
endif()
|
||||
|
||||
add_subdirectory(autotype)
|
||||
add_subdirectory(cli)
|
||||
|
||||
@ -231,6 +239,7 @@ add_library(keepassx_core STATIC ${keepassx_SOURCES})
|
||||
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
target_link_libraries(keepassx_core
|
||||
${keepasshttp_LIB}
|
||||
${keepassxcbrowser_LIB}
|
||||
${autotype_LIB}
|
||||
${sshagent_LIB}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
@ -297,6 +306,28 @@ if(APPLE AND WITH_APP_BUNDLE)
|
||||
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Deploying app bundle")
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
set(PROXY_BINARY_DIR "${CMAKE_BINARY_DIR}/src/proxy/keepassxc-proxy")
|
||||
set(PROXY_APP_DIR "${PROGNAME}.app/Contents/MacOS/keepassxc-proxy")
|
||||
add_custom_command(TARGET ${PROGNAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PROXY_BINARY_DIR} ${PROXY_APP_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Copying keepassxc-proxy inside the application")
|
||||
|
||||
add_custom_command(TARGET ${PROGNAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore "@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore" ${PROXY_APP_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Changing linking of keepassxc-proxy QtCore")
|
||||
|
||||
add_custom_command(TARGET ${PROGNAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork "@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork" ${PROXY_APP_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Changing linking of keepassxc-proxy QtNetwork")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROGNAME}
|
||||
|
59
src/browser/BrowserAccessControlDialog.cpp
Executable file
59
src/browser/BrowserAccessControlDialog.cpp
Executable file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserAccessControlDialog.h"
|
||||
#include "ui_BrowserAccessControlDialog.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::BrowserAccessControlDialog())
|
||||
{
|
||||
this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
|
||||
ui->setupUi(this);
|
||||
connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
|
||||
connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
}
|
||||
|
||||
BrowserAccessControlDialog::~BrowserAccessControlDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void BrowserAccessControlDialog::setUrl(const QString& url)
|
||||
{
|
||||
ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
|
||||
"Please select whether you want to allow access.")).arg(QUrl(url).host()));
|
||||
}
|
||||
|
||||
void BrowserAccessControlDialog::setItems(const QList<Entry*>& items)
|
||||
{
|
||||
for (Entry* entry : items) {
|
||||
ui->itemsList->addItem(entry->title() + " - " + entry->username());
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowserAccessControlDialog::remember() const
|
||||
{
|
||||
return ui->rememberDecisionCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void BrowserAccessControlDialog::setRemember(bool r)
|
||||
{
|
||||
ui->rememberDecisionCheckBox->setChecked(r);
|
||||
}
|
48
src/browser/BrowserAccessControlDialog.h
Executable file
48
src/browser/BrowserAccessControlDialog.h
Executable file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERACCESSCONTROLDIALOG_H
|
||||
#define BROWSERACCESSCONTROLDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class Entry;
|
||||
|
||||
namespace Ui {
|
||||
class BrowserAccessControlDialog;
|
||||
}
|
||||
|
||||
class BrowserAccessControlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
|
||||
~BrowserAccessControlDialog();
|
||||
|
||||
void setUrl(const QString& url);
|
||||
void setItems(const QList<Entry*>& items);
|
||||
bool remember() const;
|
||||
void setRemember(bool r);
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::BrowserAccessControlDialog> ui;
|
||||
};
|
||||
|
||||
#endif // BROWSERACCESSCONTROLDIALOG_H
|
69
src/browser/BrowserAccessControlDialog.ui
Executable file
69
src/browser/BrowserAccessControlDialog.ui
Executable file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BrowserAccessControlDialog</class>
|
||||
<widget class="QDialog" name="BrowserAccessControlDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>keepassxc-browser Confirm Access</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="itemsList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rememberDecisionCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember this decision</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="allowButton">
|
||||
<property name="text">
|
||||
<string>Allow</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="denyButton">
|
||||
<property name="text">
|
||||
<string>Deny</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
546
src/browser/BrowserAction.cpp
Executable file
546
src/browser/BrowserAction.cpp
Executable file
@ -0,0 +1,546 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include "BrowserAction.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "sodium.h"
|
||||
#include "sodium/crypto_box.h"
|
||||
#include "sodium/randombytes.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
BrowserAction::BrowserAction(BrowserService& browserService) :
|
||||
m_mutex(QMutex::Recursive),
|
||||
m_browserService(browserService),
|
||||
m_associated(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::readResponse(const QJsonObject& json)
|
||||
{
|
||||
if (json.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
const QString action = json.value("action").toString();
|
||||
if (action.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
|
||||
if (m_clientPublicKey.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
} else if (!m_browserService.openDatabase()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
}
|
||||
|
||||
return handleAction(json);
|
||||
}
|
||||
|
||||
|
||||
// Private functions
|
||||
///////////////////////
|
||||
|
||||
QJsonObject BrowserAction::handleAction(const QJsonObject& json)
|
||||
{
|
||||
QString action = json.value("action").toString();
|
||||
if (action.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
|
||||
return handleChangePublicKeys(json, action);
|
||||
} else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
|
||||
return handleGetDatabaseHash(json, action);
|
||||
} else if (action.compare("associate", Qt::CaseSensitive) == 0) {
|
||||
return handleAssociate(json, action);
|
||||
} else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
|
||||
return handleTestAssociate(json, action);
|
||||
} else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
|
||||
return handleGetLogins(json, action);
|
||||
} else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
|
||||
return handleGeneratePassword(json, action);
|
||||
} else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
|
||||
return handleSetLogin(json, action);
|
||||
} else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
|
||||
return handleLockDatabase(json, action);
|
||||
}
|
||||
|
||||
// Action was not recognized
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString clientPublicKey = json.value("publicKey").toString();
|
||||
|
||||
if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
m_associated = false;
|
||||
unsigned char pk[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char sk[crypto_box_SECRETKEYBYTES];
|
||||
crypto_box_keypair(pk, sk);
|
||||
|
||||
const QString publicKey = getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
|
||||
const QString secretKey = getBase64FromKey(sk, crypto_box_SECRETKEYBYTES);
|
||||
m_clientPublicKey = clientPublicKey;
|
||||
m_publicKey = publicKey;
|
||||
m_secretKey = secretKey;
|
||||
|
||||
QJsonObject response = buildMessage(incrementNonce(nonce));
|
||||
response["action"] = action;
|
||||
response["publicKey"] = publicKey;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
|
||||
QJsonObject message;
|
||||
message["hash"] = hash;
|
||||
message["version"] = KEEPASSX_VERSION;
|
||||
return buildResponse(action, message, incrementNonce(nonce));
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString key = decrypted.value("key").toString();
|
||||
if (key.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
|
||||
const QString id = m_browserService.storeKey(key);
|
||||
if (id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
}
|
||||
|
||||
m_associated = true;
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString responseKey = decrypted.value("key").toString();
|
||||
const QString id = decrypted.value("id").toString();
|
||||
if (responseKey.isEmpty() || id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QString key = m_browserService.getKey(id);
|
||||
if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
m_associated = true;
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString url = decrypted.value("url").toString();
|
||||
if (url.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||
}
|
||||
|
||||
const QString id = decrypted.value("id").toString();
|
||||
const QString submit = decrypted.value("submitUrl").toString();
|
||||
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "");
|
||||
|
||||
if (users.isEmpty()) {
|
||||
return QJsonObject(); // No logins found. Not an error, return an empty JSON object.
|
||||
}
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["count"] = users.count();
|
||||
message["entries"] = users;
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString password = BrowserSettings::generatePassword();
|
||||
const QString bits = QString::number(BrowserSettings::getbits()); // For some reason this always returns 1140 bits?
|
||||
|
||||
if (nonce.isEmpty() || password.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
QJsonObject passwd;
|
||||
passwd["login"] = QString::number(password.length() * 8); //bits;
|
||||
passwd["password"] = password;
|
||||
arr.append(passwd);
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["entries"] = arr;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString url = decrypted.value("url").toString();
|
||||
if (url.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||
}
|
||||
|
||||
const QString id = decrypted.value("id").toString();
|
||||
const QString login = decrypted.value("login").toString();
|
||||
const QString password = decrypted.value("password").toString();
|
||||
const QString submitUrl = decrypted.value("submitUrl").toString();
|
||||
const QString uuid = decrypted.value("uuid").toString();
|
||||
const QString realm;
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
m_browserService.addEntry(id, login, password, url, submitUrl, realm);
|
||||
} else {
|
||||
m_browserService.updateEntry(id, uuid, login, password, url);
|
||||
}
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["count"] = QJsonValue::Null;
|
||||
message["entries"] = QJsonValue::Null;
|
||||
message["error"] = "";
|
||||
message["hash"] = hash;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.lockDatabase();
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = action;
|
||||
response["errorCode"] = QString::number(errorCode);
|
||||
response["error"] = getErrorMessage(errorCode);
|
||||
return response;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::buildMessage(const QString& nonce) const
|
||||
{
|
||||
QJsonObject message;
|
||||
message["version"] = KEEPASSX_VERSION;
|
||||
message["success"] = "true";
|
||||
message["nonce"] = nonce;
|
||||
return message;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = action;
|
||||
response["message"] = encryptMessage(message, nonce);
|
||||
response["nonce"] = nonce;
|
||||
return response;
|
||||
}
|
||||
|
||||
QString BrowserAction::getErrorMessage(const int errorCode) const
|
||||
{
|
||||
switch (errorCode) {
|
||||
case ERROR_KEEPASS_DATABASE_NOT_OPENED: return "Database not opened";
|
||||
case ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED: return "Database hash not available";
|
||||
case ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED: return "Client public key not received";
|
||||
case ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE: return "Cannot decrypt message";
|
||||
case ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED: return "Timeout or cannot connect to KeePassXC";
|
||||
case ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED: return "Action cancelled or denied";
|
||||
case ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE: return "Cannot encrypt message or public key not found. Is Native Messaging enabled in KeePassXC?";
|
||||
case ERROR_KEEPASS_ASSOCIATION_FAILED: return "KeePassXC association failed, try again";
|
||||
case ERROR_KEEPASS_KEY_CHANGE_FAILED: return "Key change was not successful";
|
||||
case ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED: return "Encryption key is not recognized";
|
||||
case ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND: return "No saved databases found";
|
||||
case ERROR_KEEPASS_INCORRECT_ACTION: return "Incorrect action";
|
||||
case ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED: return "Empty message received";
|
||||
case ERROR_KEEPASS_NO_URL_PROVIDED: return "No URL provided";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
QString BrowserAction::getDatabaseHash()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QByteArray hash = QCryptographicHash::hash(
|
||||
(m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
|
||||
QCryptographicHash::Sha256).toHex();
|
||||
return QString(hash);
|
||||
}
|
||||
|
||||
QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QString reply(QJsonDocument(message).toJson());
|
||||
if (!reply.isEmpty()) {
|
||||
return encrypt(reply, nonce);
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce, const QString& action)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QByteArray ba = decrypt(message, nonce);
|
||||
if (!ba.isEmpty()) {
|
||||
return getJsonObject(ba);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString BrowserAction::encrypt(const QString plaintext, const QString nonce)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QByteArray ma = plaintext.toUtf8();
|
||||
const QByteArray na = base64Decode(nonce);
|
||||
const QByteArray ca = base64Decode(m_clientPublicKey);
|
||||
const QByteArray sa = base64Decode(m_secretKey);
|
||||
|
||||
std::vector<unsigned char> m(ma.cbegin(), ma.cend());
|
||||
std::vector<unsigned char> n(na.cbegin(), na.cend());
|
||||
std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
|
||||
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
|
||||
|
||||
std::vector<unsigned char> e;
|
||||
e.resize(max_length);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
|
||||
QByteArray res = getQByteArray(e.data(), (crypto_box_MACBYTES + ma.length()));
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QByteArray BrowserAction::decrypt(const QString encrypted, const QString nonce)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QByteArray ma = base64Decode(encrypted);
|
||||
const QByteArray na = base64Decode(nonce);
|
||||
const QByteArray ca = base64Decode(m_clientPublicKey);
|
||||
const QByteArray sa = base64Decode(m_secretKey);
|
||||
|
||||
std::vector<unsigned char> m(ma.cbegin(), ma.cend());
|
||||
std::vector<unsigned char> n(na.cbegin(), na.cend());
|
||||
std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
|
||||
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
|
||||
|
||||
std::vector<unsigned char> d;
|
||||
d.resize(max_length);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (crypto_box_open_easy(d.data(), m.data(), ma.length(), n.data(), ck.data(), sk.data()) == 0) {
|
||||
return getQByteArray(d.data(), std::char_traits<char>::length(reinterpret_cast<const char *>(d.data())));
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QString BrowserAction::getBase64FromKey(const uchar* array, const uint len)
|
||||
{
|
||||
return getQByteArray(array, len).toBase64();
|
||||
}
|
||||
|
||||
QByteArray BrowserAction::getQByteArray(const uchar* array, const uint len) const
|
||||
{
|
||||
QByteArray qba;
|
||||
qba.reserve(len);
|
||||
for (uint i = 0; i < len; ++i) {
|
||||
qba.append(static_cast<char>(array[i]));
|
||||
}
|
||||
return qba;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getJsonObject(const uchar* pArray, const uint len) const
|
||||
{
|
||||
QByteArray arr = getQByteArray(pArray, len);
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getJsonObject(const QByteArray ba) const
|
||||
{
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(ba, &err));
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
QByteArray BrowserAction::base64Decode(const QString str)
|
||||
{
|
||||
return QByteArray::fromBase64(str.toUtf8());
|
||||
}
|
||||
|
||||
QString BrowserAction::incrementNonce(const QString& nonce)
|
||||
{
|
||||
const QByteArray nonceArray = base64Decode(nonce);
|
||||
std::vector<unsigned char> n(nonceArray.cbegin(), nonceArray.cend());
|
||||
|
||||
sodium_increment(n.data(), n.size());
|
||||
return getQByteArray(n.data(), n.size()).toBase64();
|
||||
}
|
||||
|
||||
void BrowserAction::removeSharedEncryptionKeys()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.removeSharedEncryptionKeys();
|
||||
}
|
||||
|
||||
void BrowserAction::removeStoredPermissions()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.removeStoredPermissions();
|
||||
}
|
97
src/browser/BrowserAction.h
Executable file
97
src/browser/BrowserAction.h
Executable file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERACTION_H
|
||||
#define BROWSERACTION_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include "BrowserService.h"
|
||||
|
||||
class BrowserAction : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum {
|
||||
ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
|
||||
ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
|
||||
ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
|
||||
ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
|
||||
ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
|
||||
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
|
||||
ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
|
||||
ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
|
||||
ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
|
||||
ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
|
||||
ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
|
||||
ERROR_KEEPASS_INCORRECT_ACTION = 12,
|
||||
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
|
||||
ERROR_KEEPASS_NO_URL_PROVIDED = 14
|
||||
};
|
||||
|
||||
public:
|
||||
BrowserAction(BrowserService& browserService);
|
||||
~BrowserAction() = default;
|
||||
|
||||
QJsonObject readResponse(const QJsonObject& json);
|
||||
|
||||
public slots:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
QJsonObject handleAction(const QJsonObject& json);
|
||||
QJsonObject handleChangePublicKeys(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGetLogins(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
|
||||
|
||||
QJsonObject buildMessage(const QString& nonce) const;
|
||||
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
|
||||
QString getErrorMessage(const int errorCode) const;
|
||||
QString getDatabaseHash();
|
||||
|
||||
QString encryptMessage(const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject decryptMessage(const QString& message, const QString& nonce, const QString& action = QString());
|
||||
QString encrypt(const QString plaintext, const QString nonce);
|
||||
QByteArray decrypt(const QString encrypted, const QString nonce);
|
||||
|
||||
QString getBase64FromKey(const uchar* array, const uint len);
|
||||
QByteArray getQByteArray(const uchar* array, const uint len) const;
|
||||
QJsonObject getJsonObject(const uchar* pArray, const uint len) const;
|
||||
QJsonObject getJsonObject(const QByteArray ba) const;
|
||||
QByteArray base64Decode(const QString str);
|
||||
QString incrementNonce(const QString& nonce);
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
BrowserService& m_browserService;
|
||||
QString m_clientPublicKey;
|
||||
QString m_publicKey;
|
||||
QString m_secretKey;
|
||||
bool m_associated;
|
||||
};
|
||||
|
||||
#endif // BROWSERACTION_H
|
77
src/browser/BrowserClients.cpp
Executable file
77
src/browser/BrowserClients.cpp
Executable file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QJsonValue>
|
||||
#include <QJsonParseError>
|
||||
#include "BrowserClients.h"
|
||||
|
||||
BrowserClients::BrowserClients(BrowserService& browserService) :
|
||||
m_mutex(QMutex::Recursive),
|
||||
m_browserService(browserService)
|
||||
{
|
||||
m_clients.reserve(1000);
|
||||
}
|
||||
|
||||
QJsonObject BrowserClients::readResponse(const QByteArray& arr)
|
||||
{
|
||||
QJsonObject json;
|
||||
const QJsonObject message = byteArrayToJson(arr);
|
||||
const QString clientID = getClientID(message);
|
||||
|
||||
if (!clientID.isEmpty()) {
|
||||
const ClientPtr client = getClient(clientID);
|
||||
if (client->browserAction) {
|
||||
json = client->browserAction->readResponse(message);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QJsonObject BrowserClients::byteArrayToJson(const QByteArray& arr) const
|
||||
{
|
||||
QJsonObject json;
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
|
||||
if (doc.isObject()) {
|
||||
json = doc.object();
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QString BrowserClients::getClientID(const QJsonObject& json) const
|
||||
{
|
||||
return json["clientID"].toString();
|
||||
}
|
||||
|
||||
BrowserClients::ClientPtr BrowserClients::getClient(const QString& clientID)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (const auto &i : m_clients) {
|
||||
if (i->clientID.compare(clientID, Qt::CaseSensitive) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// clientID not found, create a new client
|
||||
QSharedPointer<BrowserAction> ba = QSharedPointer<BrowserAction>::create(m_browserService);
|
||||
ClientPtr client = ClientPtr::create(clientID, ba);
|
||||
m_clients.push_back(client);
|
||||
return m_clients.back();
|
||||
}
|
56
src/browser/BrowserClients.h
Executable file
56
src/browser/BrowserClients.h
Executable file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERCLIENTS_H
|
||||
#define BROWSERCLIENTS_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include <QVector>
|
||||
#include <QSharedPointer>
|
||||
#include <QLocalSocket>
|
||||
#include "BrowserAction.h"
|
||||
|
||||
class BrowserClients
|
||||
{
|
||||
struct Client {
|
||||
Client(const QString& id, QSharedPointer<BrowserAction> ba) : clientID(id), browserAction(ba) {}
|
||||
QString clientID;
|
||||
QSharedPointer<BrowserAction> browserAction;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<Client> ClientPtr;
|
||||
|
||||
public:
|
||||
BrowserClients(BrowserService& browserService);
|
||||
~BrowserClients() = default;
|
||||
|
||||
QJsonObject readResponse(const QByteArray& arr);
|
||||
|
||||
private:
|
||||
QJsonObject byteArrayToJson(const QByteArray& arr) const;
|
||||
QString getClientID(const QJsonObject& json) const;
|
||||
ClientPtr getClient(const QString& clientID);
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
QVector<ClientPtr> m_clients;
|
||||
BrowserService& m_browserService;
|
||||
};
|
||||
|
||||
#endif // BROWSERCLIENTS_H
|
109
src/browser/BrowserEntryConfig.cpp
Normal file
109
src/browser/BrowserEntryConfig.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserEntryConfig.h"
|
||||
#include <QtCore>
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
|
||||
static const char KEEPASSBROWSER_NAME[] = "keepassxc-browser Settings"; //TODO: duplicated string (also in Service.cpp)
|
||||
|
||||
|
||||
BrowserEntryConfig::BrowserEntryConfig(QObject* parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList BrowserEntryConfig::allowedHosts() const
|
||||
{
|
||||
return m_allowedHosts.toList();
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::setAllowedHosts(const QStringList& allowedHosts)
|
||||
{
|
||||
m_allowedHosts = allowedHosts.toSet();
|
||||
}
|
||||
|
||||
QStringList BrowserEntryConfig::deniedHosts() const
|
||||
{
|
||||
return m_deniedHosts.toList();
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::setDeniedHosts(const QStringList& deniedHosts)
|
||||
{
|
||||
m_deniedHosts = deniedHosts.toSet();
|
||||
}
|
||||
|
||||
bool BrowserEntryConfig::isAllowed(const QString& host) const
|
||||
{
|
||||
return m_allowedHosts.contains(host);
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::allow(const QString& host)
|
||||
{
|
||||
m_allowedHosts.insert(host);
|
||||
m_deniedHosts.remove(host);
|
||||
}
|
||||
|
||||
bool BrowserEntryConfig::isDenied(const QString& host) const
|
||||
{
|
||||
return m_deniedHosts.contains(host);
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::deny(const QString& host)
|
||||
{
|
||||
m_deniedHosts.insert(host);
|
||||
m_allowedHosts.remove(host);
|
||||
}
|
||||
|
||||
QString BrowserEntryConfig::realm() const
|
||||
{
|
||||
return m_realm;
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::setRealm(const QString& realm)
|
||||
{
|
||||
m_realm = realm;
|
||||
}
|
||||
|
||||
bool BrowserEntryConfig::load(const Entry* entry)
|
||||
{
|
||||
QString s = entry->attributes()->value(KEEPASSBROWSER_NAME);
|
||||
if (s.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8());
|
||||
if (doc.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantMap map = doc.object().toVariantMap();
|
||||
for (QVariantMap::const_iterator iter = map.cbegin(); iter != map.cend(); ++iter) {
|
||||
setProperty(iter.key().toLatin1(), iter.value());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::save(Entry* entry)
|
||||
{
|
||||
QVariantMap v = qo2qv(this);
|
||||
QJsonObject o = QJsonObject::fromVariantMap(v);
|
||||
QByteArray json = QJsonDocument(o).toJson(QJsonDocument::Compact);
|
||||
entry->attributes()->set(KEEPASSBROWSER_NAME, json);
|
||||
}
|
60
src/browser/BrowserEntryConfig.h
Normal file
60
src/browser/BrowserEntryConfig.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERENTRYCONFIG_H
|
||||
#define BROWSERENTRYCONFIG_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QSet>
|
||||
#include "Variant.h"
|
||||
|
||||
class Entry;
|
||||
|
||||
class BrowserEntryConfig : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
|
||||
Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
|
||||
Q_PROPERTY(QString Realm READ realm WRITE setRealm )
|
||||
|
||||
public:
|
||||
BrowserEntryConfig(QObject* object = 0);
|
||||
|
||||
bool load(const Entry* entry);
|
||||
void save(Entry* entry);
|
||||
bool isAllowed(const QString& host) const;
|
||||
void allow(const QString& host);
|
||||
bool isDenied(const QString& host) const;
|
||||
void deny(const QString& host);
|
||||
QString realm() const;
|
||||
void setRealm(const QString& realm);
|
||||
|
||||
private:
|
||||
QStringList allowedHosts() const;
|
||||
void setAllowedHosts(const QStringList& allowedHosts);
|
||||
QStringList deniedHosts() const;
|
||||
void setDeniedHosts(const QStringList& deniedHosts);
|
||||
|
||||
QSet<QString> m_allowedHosts;
|
||||
QSet<QString> m_deniedHosts;
|
||||
QString m_realm;
|
||||
};
|
||||
|
||||
#endif // BROWSERENTRYCONFIG_H
|
104
src/browser/BrowserOptionDialog.cpp
Executable file
104
src/browser/BrowserOptionDialog.cpp
Executable file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserOptionDialog.h"
|
||||
#include "ui_BrowserOptionDialog.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "core/FilePath.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
BrowserOptionDialog::BrowserOptionDialog(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_ui(new Ui::BrowserOptionDialog())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
connect(m_ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys()));
|
||||
connect(m_ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions()));
|
||||
|
||||
m_ui->warningWidget->showMessage(tr("The following options can be dangerous!\nChange them only if you know what you are doing."), MessageWidget::Warning);
|
||||
m_ui->warningWidget->setIcon(FilePath::instance()->icon("status", "dialog-warning"));
|
||||
m_ui->warningWidget->setCloseButtonVisible(false);
|
||||
|
||||
m_ui->tabWidget->setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||
connect(m_ui->enableBrowserSupport, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool)));
|
||||
|
||||
m_ui->customProxyLocation->setEnabled(m_ui->useCustomProxy->isChecked());
|
||||
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocation, SLOT(setEnabled(bool)));
|
||||
}
|
||||
|
||||
BrowserOptionDialog::~BrowserOptionDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::loadSettings()
|
||||
{
|
||||
BrowserSettings settings;
|
||||
m_ui->enableBrowserSupport->setChecked(settings.isEnabled());
|
||||
|
||||
m_ui->showNotification->setChecked(settings.showNotification());
|
||||
m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
|
||||
m_ui->unlockDatabase->setChecked(settings.unlockDatabase());
|
||||
m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
|
||||
|
||||
if (settings.sortByUsername()) {
|
||||
m_ui->sortByUsername->setChecked(true);
|
||||
} else {
|
||||
m_ui->sortByTitle->setChecked(true);
|
||||
}
|
||||
|
||||
m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess());
|
||||
m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate());
|
||||
m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases());
|
||||
m_ui->supportKphFields->setChecked(settings.supportKphFields());
|
||||
m_ui->supportBrowserProxy->setChecked(settings.supportBrowserProxy());
|
||||
m_ui->useCustomProxy->setChecked(settings.useCustomProxy());
|
||||
m_ui->customProxyLocation->setText(settings.customProxyLocation());
|
||||
m_ui->updateBinaryPath->setChecked(settings.updateBinaryPath());
|
||||
m_ui->chromeSupport->setChecked(settings.chromeSupport());
|
||||
m_ui->chromiumSupport->setChecked(settings.chromiumSupport());
|
||||
m_ui->firefoxSupport->setChecked(settings.firefoxSupport());
|
||||
m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport());
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::saveSettings()
|
||||
{
|
||||
BrowserSettings settings;
|
||||
settings.setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||
settings.setShowNotification(m_ui->showNotification->isChecked());
|
||||
settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
|
||||
settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked());
|
||||
settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
|
||||
settings.setSortByUsername(m_ui->sortByUsername->isChecked());
|
||||
|
||||
settings.setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
|
||||
settings.setUseCustomProxy(m_ui->useCustomProxy->isChecked());
|
||||
settings.setCustomProxyLocation(m_ui->customProxyLocation->text());
|
||||
|
||||
settings.setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
|
||||
settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
|
||||
settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
|
||||
settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked());
|
||||
settings.setSupportKphFields(m_ui->supportKphFields->isChecked());
|
||||
|
||||
settings.setChromeSupport(m_ui->chromeSupport->isChecked());
|
||||
settings.setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
||||
settings.setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
||||
settings.setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
||||
}
|
50
src/browser/BrowserOptionDialog.h
Executable file
50
src/browser/BrowserOptionDialog.h
Executable file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSEROPTIONDIALOG_H
|
||||
#define BROWSEROPTIONDIALOG_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace Ui {
|
||||
class BrowserOptionDialog;
|
||||
}
|
||||
|
||||
class BrowserOptionDialog : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserOptionDialog(QWidget* parent = nullptr);
|
||||
~BrowserOptionDialog();
|
||||
|
||||
public slots:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
signals:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::BrowserOptionDialog> m_ui;
|
||||
};
|
||||
|
||||
#endif // BROWSEROPTIONDIALOG_H
|
327
src/browser/BrowserOptionDialog.ui
Executable file
327
src/browser/BrowserOptionDialog.ui
Executable file
@ -0,0 +1,327 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BrowserOptionDialog</class>
|
||||
<widget class="QWidget" name="BrowserOptionDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>577</width>
|
||||
<height>404</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableBrowserSupport">
|
||||
<property name="toolTip">
|
||||
<string>This is required for accessing your databases with keepassxc-browser</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable KeepassXC browser extension</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showNotification">
|
||||
<property name="text">
|
||||
<string>Sh&ow a notification when credentials are requested</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bestMatchOnly">
|
||||
<property name="toolTip">
|
||||
<string>Only returns the best matches for a specific URL instead of all entries for the whole domain.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Return only best-matching entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="unlockDatabase">
|
||||
<property name="text">
|
||||
<string>Re&quest to unlock the database if it is locked</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="matchUrlScheme">
|
||||
<property name="toolTip">
|
||||
<string>Only entries with the same scheme (http://, https://, ...) are returned.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Match URL schemes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByUsername">
|
||||
<property name="text">
|
||||
<string>Sort matching entries by &username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByTitle">
|
||||
<property name="text">
|
||||
<string>Sort &matching entries by title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeSharedEncryptionKeys">
|
||||
<property name="text">
|
||||
<string>R&emove all shared encryption keys from active database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeStoredPermissions">
|
||||
<property name="text">
|
||||
<string>Re&move all stored permissions from entries in active database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Supported browsers</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="browserLabel1">
|
||||
<property name="text">
|
||||
<string>Native messaging requires certain .json files to be installed. Already installed browsers are automatically detected.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="browserLabel2">
|
||||
<property name="text">
|
||||
<string>Enable KeePassXC native messaging extension for these browsers:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="chromeSupport">
|
||||
<property name="text">
|
||||
<string>Chrome</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="chromiumSupport">
|
||||
<property name="text">
|
||||
<string>Chromium</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="firefoxSupport">
|
||||
<property name="text">
|
||||
<string>Firefox</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="vivaldiSupport">
|
||||
<property name="text">
|
||||
<string>Vivaldi</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="warningWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowAccess">
|
||||
<property name="text">
|
||||
<string>Always allow &access to entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowUpdate">
|
||||
<property name="text">
|
||||
<string>Always allow &updating entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchInAllDatabases">
|
||||
<property name="toolTip">
|
||||
<string>Only the selected database has to be connected with a client.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Searc&h in all opened databases for matching entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportKphFields">
|
||||
<property name="toolTip">
|
||||
<string>Automatically creating or updating string fields is not supported.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Return advanced string fields which start with "KPH: "</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="updateBinaryPath">
|
||||
<property name="toolTip">
|
||||
<string>Updates KeePassXC or keepassxc-proxy binary path automatically to native messaging scripts on startup.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Update KeePassXC binary path automatically to native messaging scripts on startup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportBrowserProxy">
|
||||
<property name="toolTip">
|
||||
<string>Support a proxy application between KeePassXC and browser extension.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Enable support for proxy application between KeePassXC and browser extension</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useCustomProxy">
|
||||
<property name="toolTip">
|
||||
<string>Use a custom proxy location if you installed a proxy manually.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Use a custom proxy location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="customProxyLocation">
|
||||
<property name="maxLength">
|
||||
<number>999</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
734
src/browser/BrowserService.cpp
Normal file
734
src/browser/BrowserService.cpp
Normal file
@ -0,0 +1,734 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QInputDialog>
|
||||
#include <QProgressDialog>
|
||||
#include <QMessageBox>
|
||||
#include "BrowserService.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "BrowserEntryConfig.h"
|
||||
#include "BrowserAccessControlDialog.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/EntrySearcher.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Uuid.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
|
||||
|
||||
// de887cc3-0363-43b8-974b-5911b8816224
|
||||
static const unsigned char KEEPASSXCBROWSER_UUID_DATA[] = {
|
||||
0xde, 0x88, 0x7c, 0xc3, 0x03, 0x63, 0x43, 0xb8,
|
||||
0x97, 0x4b, 0x59, 0x11, 0xb8, 0x81, 0x62, 0x24
|
||||
};
|
||||
static const Uuid KEEPASSXCBROWSER_UUID = Uuid(QByteArray::fromRawData(reinterpret_cast<const char *>(KEEPASSXCBROWSER_UUID_DATA), sizeof(KEEPASSXCBROWSER_UUID_DATA)));
|
||||
static const char KEEPASSXCBROWSER_NAME[] = "keepassxc-browser Settings";
|
||||
static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
|
||||
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "keepassxc-browser Passwords";
|
||||
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
||||
|
||||
BrowserService::BrowserService(DatabaseTabWidget* parent) :
|
||||
m_dbTabWidget(parent),
|
||||
m_dialogActive(false)
|
||||
{
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
|
||||
connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
|
||||
connect(m_dbTabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), this, SLOT(activateDatabaseChanged(DatabaseWidget*)));
|
||||
}
|
||||
|
||||
bool BrowserService::isDatabaseOpened() const
|
||||
{
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BrowserService::openDatabase()
|
||||
{
|
||||
if (!BrowserSettings::unlockDatabase()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
m_dbTabWidget->activateWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
void BrowserService::lockDatabase()
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
dbWidget->lock();
|
||||
}
|
||||
}
|
||||
|
||||
QString BrowserService::getDatabaseRootUuid()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return rootGroup->uuid().toHex();
|
||||
}
|
||||
|
||||
QString BrowserService::getDatabaseRecycleBinUuid()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
Group* recycleBin = db->metadata()->recycleBin();
|
||||
if (!recycleBin) {
|
||||
return QString();
|
||||
}
|
||||
return recycleBin->uuid().toHex();
|
||||
}
|
||||
|
||||
Entry* BrowserService::getConfigEntry(bool create)
|
||||
{
|
||||
Entry* entry = nullptr;
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
|
||||
if (!entry && create) {
|
||||
entry = new Entry();
|
||||
entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
|
||||
entry->setUuid(KEEPASSXCBROWSER_UUID);
|
||||
entry->setAutoTypeEnabled(false);
|
||||
entry->setGroup(db->rootGroup());
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (entry && entry->group() == db->metadata()->recycleBin()) {
|
||||
if (!create) {
|
||||
return nullptr;
|
||||
} else {
|
||||
entry->setGroup(db->rootGroup());
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
QString BrowserService::storeKey(const QString& key)
|
||||
{
|
||||
QString id;
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "storeKey", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QString, id),
|
||||
Q_ARG(const QString&, key));
|
||||
return id;
|
||||
}
|
||||
|
||||
Entry* config = getConfigEntry(true);
|
||||
if (!config) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool contains = false;
|
||||
QMessageBox::StandardButton dialogResult = QMessageBox::No;
|
||||
|
||||
do {
|
||||
bool ok = false;
|
||||
id = QInputDialog::getText(0, tr("KeePassXC: New key association request"),
|
||||
tr("You have received an association "
|
||||
"request for the above key.\n"
|
||||
"If you would like to allow it access "
|
||||
"to your KeePassXC database,\n"
|
||||
"give it a unique name to identify and accept it."),
|
||||
QLineEdit::Normal, QString(), &ok);
|
||||
if (!ok || id.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
contains = config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
||||
dialogResult = QMessageBox::warning(0, tr("KeePassXC: Overwrite existing key?"),
|
||||
tr("A shared encryption key with the name \"%1\" already exists.\nDo you want to overwrite it?").arg(id),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
} while (contains && dialogResult == QMessageBox::No);
|
||||
|
||||
config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true);
|
||||
return id;
|
||||
}
|
||||
|
||||
QString BrowserService::getKey(const QString& id)
|
||||
{
|
||||
Entry* config = getConfigEntry();
|
||||
if (!config) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
||||
}
|
||||
|
||||
// No need to use KeepassHttpProtocol. Just return a JSON array.
|
||||
QJsonArray BrowserService::findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm)
|
||||
{
|
||||
QJsonArray result;
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "findMatchingEntries", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QJsonArray, result),
|
||||
Q_ARG(const QString&, id),
|
||||
Q_ARG(const QString&, url),
|
||||
Q_ARG(const QString&, submitUrl),
|
||||
Q_ARG(const QString&, realm));
|
||||
return result;
|
||||
}
|
||||
|
||||
const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess();
|
||||
const QString host = QUrl(url).host();
|
||||
const QString submitHost = QUrl(submitUrl).host();
|
||||
|
||||
// Check entries for authorization
|
||||
QList<Entry*> pwEntriesToConfirm;
|
||||
QList<Entry*> pwEntries;
|
||||
for (Entry* entry : searchEntries(url)) {
|
||||
switch (checkAccess(entry, host, submitHost, realm)) {
|
||||
case Denied:
|
||||
continue;
|
||||
|
||||
case Unknown:
|
||||
if (alwaysAllowAccess) {
|
||||
pwEntries.append(entry);
|
||||
} else {
|
||||
pwEntriesToConfirm.append(entry);
|
||||
}
|
||||
break;
|
||||
|
||||
case Allowed:
|
||||
pwEntries.append(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm entries
|
||||
if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
|
||||
pwEntries.append(pwEntriesToConfirm);
|
||||
}
|
||||
|
||||
if (pwEntries.isEmpty()) {
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
// Sort results
|
||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||
|
||||
// Fill the list
|
||||
for (Entry* entry : pwEntries) {
|
||||
result << prepareEntry(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BrowserService::addEntry(const QString&, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm)
|
||||
{
|
||||
Group* group = findCreateAddEntryGroup();
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = new Entry();
|
||||
entry->setUuid(Uuid::random());
|
||||
entry->setTitle(QUrl(url).host());
|
||||
entry->setUrl(url);
|
||||
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->setGroup(group);
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
const QString submitHost = QUrl(submitUrl).host();
|
||||
BrowserEntryConfig config;
|
||||
config.allow(host);
|
||||
|
||||
if (!submitHost.isEmpty()) {
|
||||
config.allow(submitHost);
|
||||
}
|
||||
if (!realm.isEmpty()) {
|
||||
config.setRealm(realm);
|
||||
}
|
||||
config.save(entry);
|
||||
}
|
||||
|
||||
void BrowserService::updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "updateEntry", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, id),
|
||||
Q_ARG(const QString&, uuid),
|
||||
Q_ARG(const QString&, login),
|
||||
Q_ARG(const QString&, password),
|
||||
Q_ARG(const QString&, url));
|
||||
}
|
||||
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = db->resolveEntry(Uuid::fromHex(uuid));
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString username = entry->username();
|
||||
if (username.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
|
||||
QMessageBox::StandardButton dialogResult = QMessageBox::No;
|
||||
if (!BrowserSettings::alwaysAllowUpdate()) {
|
||||
dialogResult = QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?")
|
||||
.arg(QUrl(url).host()).arg(username),
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
}
|
||||
|
||||
if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
|
||||
entry->beginUpdate();
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->endUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname)
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
for (Entry* entry : EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) {
|
||||
QString title = entry->title();
|
||||
QString url = entry->url();
|
||||
|
||||
// Filter to match hostname in Title and Url fields
|
||||
if ((!title.isEmpty() && hostname.contains(title))
|
||||
|| (!url.isEmpty() && hostname.contains(url))
|
||||
|| (matchUrlScheme(title) && hostname.endsWith(QUrl(title).host()))
|
||||
|| (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) ) {
|
||||
entries.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::searchEntries(const QString& text)
|
||||
{
|
||||
// Get the list of databases to search
|
||||
QList<Database*> databases;
|
||||
if (BrowserSettings::searchInAllDatabases()) {
|
||||
const int count = m_dbTabWidget->count();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
||||
if (Database* db = dbWidget->database()) {
|
||||
databases << db;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Database* db = getDatabase()) {
|
||||
databases << db;
|
||||
}
|
||||
|
||||
// Search entries matching the hostname
|
||||
QString hostname = QUrl(text).host();
|
||||
QList<Entry*> entries;
|
||||
do {
|
||||
for (Database* db : databases) {
|
||||
entries << searchEntries(db, hostname);
|
||||
}
|
||||
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
void BrowserService::removeSharedEncryptionKeys()
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
|
||||
tr("The active database is locked!\n"
|
||||
"Please unlock the selected database or choose another one which is unlocked."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = getConfigEntry();
|
||||
if (!entry) {
|
||||
QMessageBox::information(0, tr("KeePassXC: Settings not available!"),
|
||||
tr("The active database does not contain a settings entry."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList keysToRemove;
|
||||
for (const QString& key : entry->attributes()->keys()) {
|
||||
if (key.startsWith(ASSOCIATE_KEY_PREFIX)) {
|
||||
keysToRemove << key;
|
||||
}
|
||||
}
|
||||
|
||||
if (keysToRemove.isEmpty()) {
|
||||
QMessageBox::information(0, tr("KeePassXC: No keys found"),
|
||||
tr("No shared encryption keys found in KeePassXC Settings."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
entry->beginUpdate();
|
||||
for (const QString& key : keysToRemove) {
|
||||
entry->attributes()->remove(key);
|
||||
}
|
||||
entry->endUpdate();
|
||||
|
||||
const int count = keysToRemove.count();
|
||||
QMessageBox::information(0, tr("KeePassXC: Removed keys from database"),
|
||||
tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
|
||||
QMessageBox::Ok);
|
||||
|
||||
}
|
||||
|
||||
void BrowserService::removeStoredPermissions()
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
|
||||
tr("The active database is locked!\n"
|
||||
"Please unlock the selected database or choose another one which is unlocked."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
Database* db = m_dbTabWidget->currentDatabaseWidget()->database();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Entry*> entries = db->rootGroup()->entriesRecursive();
|
||||
|
||||
QProgressDialog progress(tr("Removing stored permissions…"), tr("Abort"), 0, entries.count());
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
uint counter = 0;
|
||||
for (Entry* entry : entries) {
|
||||
if (progress.wasCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->attributes()->contains(KEEPASSXCBROWSER_NAME)) {
|
||||
entry->beginUpdate();
|
||||
entry->attributes()->remove(KEEPASSXCBROWSER_NAME);
|
||||
entry->endUpdate();
|
||||
++counter;
|
||||
}
|
||||
progress.setValue(progress.value() + 1);
|
||||
}
|
||||
progress.reset();
|
||||
|
||||
if (counter > 0) {
|
||||
QMessageBox::information(0, tr("KeePassXC: Removed permissions"),
|
||||
tr("Successfully removed permissions from %n entry(s).", "", counter),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
QMessageBox::information(0, tr("KeePassXC: No entry with permissions found!"),
|
||||
tr("The active database does not contain an entry with permissions."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl)
|
||||
{
|
||||
QUrl url(entryUrl);
|
||||
if (url.scheme().isEmpty()) {
|
||||
url.setScheme("http");
|
||||
}
|
||||
|
||||
const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
|
||||
const QString baseSubmitUrl = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
QMultiMap<int, const Entry*> priorities;
|
||||
for (const Entry* entry : pwEntries) {
|
||||
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
|
||||
}
|
||||
|
||||
QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName";
|
||||
std::sort(pwEntries.begin(), pwEntries.end(), [&priorities, &field](const Entry* left, const Entry* right) {
|
||||
int res = priorities.key(left) - priorities.key(right);
|
||||
if (res == 0) {
|
||||
return QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0;
|
||||
}
|
||||
return res < 0;
|
||||
});
|
||||
|
||||
return pwEntries;
|
||||
}
|
||||
|
||||
bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm)
|
||||
{
|
||||
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dialogActive = true;
|
||||
BrowserAccessControlDialog accessControlDialog;
|
||||
accessControlDialog.setUrl(url);
|
||||
accessControlDialog.setItems(pwEntriesToConfirm);
|
||||
|
||||
int res = accessControlDialog.exec();
|
||||
if (accessControlDialog.remember()) {
|
||||
for (Entry* entry : pwEntriesToConfirm) {
|
||||
BrowserEntryConfig config;
|
||||
config.load(entry);
|
||||
if (res == QDialog::Accepted) {
|
||||
config.allow(host);
|
||||
if (!submitHost.isEmpty() && host != submitHost)
|
||||
config.allow(submitHost);
|
||||
} else if (res == QDialog::Rejected) {
|
||||
config.deny(host);
|
||||
if (!submitHost.isEmpty() && host != submitHost) {
|
||||
config.deny(submitHost);
|
||||
}
|
||||
}
|
||||
if (!realm.isEmpty()) {
|
||||
config.setRealm(realm);
|
||||
}
|
||||
config.save(entry);
|
||||
}
|
||||
}
|
||||
|
||||
m_dialogActive = false;
|
||||
if (res == QDialog::Accepted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
||||
{
|
||||
QJsonObject res;
|
||||
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
|
||||
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
|
||||
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
|
||||
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuid().toHex());
|
||||
|
||||
if (BrowserSettings::supportKphFields()) {
|
||||
const EntryAttributes* attr = entry->attributes();
|
||||
QJsonArray stringFields;
|
||||
for (const QString& key : attr->keys()) {
|
||||
if (key.startsWith(QLatin1String("KPH: "))) {
|
||||
QJsonObject sField;
|
||||
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
||||
stringFields << sField;
|
||||
}
|
||||
}
|
||||
res["stringFields"] = stringFields;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
BrowserService::Access BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
|
||||
{
|
||||
BrowserEntryConfig config;
|
||||
if (!config.load(entry)) {
|
||||
return Unknown;
|
||||
}
|
||||
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
|
||||
return Allowed;
|
||||
}
|
||||
if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
|
||||
return Denied;
|
||||
}
|
||||
if (!realm.isEmpty() && config.realm() != realm) {
|
||||
return Denied;
|
||||
}
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
Group* BrowserService::findCreateAddEntryGroup()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); //TODO: setting to decide where new keys are created
|
||||
|
||||
for (const Group* g : rootGroup->groupsRecursive(true)) {
|
||||
if (g->name() == groupName) {
|
||||
return db->resolveGroup(g->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
Group* group = new Group();
|
||||
group->setUuid(Uuid::random());
|
||||
group->setName(groupName);
|
||||
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
group->setParent(rootGroup);
|
||||
return group;
|
||||
}
|
||||
|
||||
int BrowserService::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const
|
||||
{
|
||||
QUrl url(entry->url());
|
||||
if (url.scheme().isEmpty()) {
|
||||
url.setScheme("http");
|
||||
}
|
||||
const QString entryURL = url.toString(QUrl::StripTrailingSlash);
|
||||
const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (submitUrl == entryURL) {
|
||||
return 100;
|
||||
}
|
||||
if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
|
||||
return 90;
|
||||
}
|
||||
if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
|
||||
return 80;
|
||||
}
|
||||
if (entryURL == host) {
|
||||
return 70;
|
||||
}
|
||||
if (entryURL == baseSubmitUrl) {
|
||||
return 60;
|
||||
}
|
||||
if (entryURL.startsWith(submitUrl)) {
|
||||
return 50;
|
||||
}
|
||||
if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
|
||||
return 40;
|
||||
}
|
||||
if (submitUrl.startsWith(entryURL)) {
|
||||
return 30;
|
||||
}
|
||||
if (submitUrl.startsWith(baseEntryURL)) {
|
||||
return 20;
|
||||
}
|
||||
if (entryURL.startsWith(host)) {
|
||||
return 10;
|
||||
}
|
||||
if (host.startsWith(entryURL)) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BrowserService::matchUrlScheme(const QString& url)
|
||||
{
|
||||
QUrl address(url);
|
||||
return !address.scheme().isEmpty();
|
||||
}
|
||||
|
||||
bool BrowserService::removeFirstDomain(QString& hostname)
|
||||
{
|
||||
int pos = hostname.indexOf(".");
|
||||
if (pos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't remove the second-level domain if it's the only one
|
||||
if (hostname.count(".") > 1) {
|
||||
hostname = hostname.mid(pos + 1);
|
||||
return !hostname.isEmpty();
|
||||
}
|
||||
|
||||
// Nothing removed
|
||||
return false;
|
||||
}
|
||||
|
||||
Database* BrowserService::getDatabase()
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
||||
if (Database* db = dbWidget->database()) {
|
||||
return db;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
emit databaseLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
emit databaseUnlocked();
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
auto currentMode = dbWidget->currentMode();
|
||||
if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
|
||||
emit databaseUnlocked();
|
||||
} else {
|
||||
emit databaseLocked();
|
||||
}
|
||||
}
|
||||
}
|
82
src/browser/BrowserService.h
Normal file
82
src/browser/BrowserService.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERSERVICE_H
|
||||
#define BROWSERSERVICE_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QObject>
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
enum { max_length = 16*1024 };
|
||||
|
||||
class BrowserService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserService(DatabaseTabWidget* parent);
|
||||
|
||||
bool isDatabaseOpened() const;
|
||||
bool openDatabase();
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
Entry* getConfigEntry(bool create = false);
|
||||
QString getKey(const QString& id);
|
||||
void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
QList<Entry*> searchEntries(Database* db, const QString& hostname);
|
||||
QList<Entry*> searchEntries(const QString& text);
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
public slots:
|
||||
QJsonArray findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
QString storeKey(const QString& key);
|
||||
void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url);
|
||||
void databaseLocked(DatabaseWidget* dbWidget);
|
||||
void databaseUnlocked(DatabaseWidget* dbWidget);
|
||||
void activateDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
void lockDatabase();
|
||||
|
||||
signals:
|
||||
void databaseLocked();
|
||||
void databaseUnlocked();
|
||||
void databaseChanged();
|
||||
|
||||
private:
|
||||
enum Access { Denied, Unknown, Allowed};
|
||||
|
||||
private:
|
||||
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
|
||||
bool confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm);
|
||||
QJsonObject prepareEntry(const Entry* entry);
|
||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
||||
Group* findCreateAddEntryGroup();
|
||||
int sortPriority(const Entry* entry, const QString &host, const QString& submitUrl, const QString& baseSubmitUrl) const;
|
||||
bool matchUrlScheme(const QString& url);
|
||||
bool removeFirstDomain(QString& hostname);
|
||||
Database* getDatabase();
|
||||
|
||||
private:
|
||||
DatabaseTabWidget* const m_dbTabWidget;
|
||||
bool m_dialogActive;
|
||||
};
|
||||
|
||||
#endif // BROWSERSERVICE_H
|
377
src/browser/BrowserSettings.cpp
Executable file
377
src/browser/BrowserSettings.cpp
Executable file
@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserSettings.h"
|
||||
#include "core/Config.h"
|
||||
|
||||
PasswordGenerator BrowserSettings::m_passwordGenerator;
|
||||
PassphraseGenerator BrowserSettings::m_passPhraseGenerator;
|
||||
HostInstaller BrowserSettings::m_hostInstaller;
|
||||
|
||||
bool BrowserSettings::isEnabled()
|
||||
{
|
||||
return config()->get("Browser/Enabled", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setEnabled(bool enabled)
|
||||
{
|
||||
config()->set("Browser/Enabled", enabled);
|
||||
}
|
||||
|
||||
bool BrowserSettings::showNotification()
|
||||
{
|
||||
return config()->get("Browser/ShowNotification", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setShowNotification(bool showNotification)
|
||||
{
|
||||
config()->set("Browser/ShowNotification", showNotification);
|
||||
}
|
||||
|
||||
bool BrowserSettings::bestMatchOnly()
|
||||
{
|
||||
return config()->get("Browser/BestMatchOnly", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setBestMatchOnly(bool bestMatchOnly)
|
||||
{
|
||||
config()->set("Browser/BestMatchOnly", bestMatchOnly);
|
||||
}
|
||||
|
||||
bool BrowserSettings::unlockDatabase()
|
||||
{
|
||||
return config()->get("Browser/UnlockDatabase", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setUnlockDatabase(bool unlockDatabase)
|
||||
{
|
||||
config()->set("Browser/UnlockDatabase", unlockDatabase);
|
||||
}
|
||||
|
||||
bool BrowserSettings::matchUrlScheme()
|
||||
{
|
||||
return config()->get("Browser/MatchUrlScheme", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setMatchUrlScheme(bool matchUrlScheme)
|
||||
{
|
||||
config()->set("Browser/MatchUrlScheme", matchUrlScheme);
|
||||
}
|
||||
|
||||
bool BrowserSettings::sortByUsername()
|
||||
{
|
||||
return config()->get("Browser/SortByUsername", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setSortByUsername(bool sortByUsername)
|
||||
{
|
||||
config()->set("Browser/SortByUsername", sortByUsername);
|
||||
}
|
||||
|
||||
bool BrowserSettings::sortByTitle()
|
||||
{
|
||||
return !sortByUsername();
|
||||
}
|
||||
|
||||
void BrowserSettings::setSortByTitle(bool sortByUsertitle)
|
||||
{
|
||||
setSortByUsername(!sortByUsertitle);
|
||||
}
|
||||
|
||||
bool BrowserSettings::alwaysAllowAccess()
|
||||
{
|
||||
return config()->get("Browser/AlwaysAllowAccess", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setAlwaysAllowAccess(bool alwaysAllowAccess)
|
||||
{
|
||||
config()->set("Browser/AlwaysAllowAccess", alwaysAllowAccess);
|
||||
}
|
||||
|
||||
bool BrowserSettings::alwaysAllowUpdate()
|
||||
{
|
||||
return config()->get("Browser/AlwaysAllowUpdate", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setAlwaysAllowUpdate(bool alwaysAllowUpdate)
|
||||
{
|
||||
config()->set("Browser/AlwaysAllowUpdate", alwaysAllowUpdate);
|
||||
}
|
||||
|
||||
bool BrowserSettings::searchInAllDatabases()
|
||||
{
|
||||
return config()->get("Browser/SearchInAllDatabases", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setSearchInAllDatabases(bool searchInAllDatabases)
|
||||
{
|
||||
config()->set("Browser/SearchInAllDatabases", searchInAllDatabases);
|
||||
}
|
||||
|
||||
bool BrowserSettings::supportKphFields()
|
||||
{
|
||||
return config()->get("Browser/SupportKphFields", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setSupportKphFields(bool supportKphFields)
|
||||
{
|
||||
config()->set("Browser/SupportKphFields", supportKphFields);
|
||||
}
|
||||
|
||||
bool BrowserSettings::supportBrowserProxy()
|
||||
{
|
||||
return config()->get("Browser/SupportBrowserProxy", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setSupportBrowserProxy(bool enabled)
|
||||
{
|
||||
config()->set("Browser/SupportBrowserProxy", enabled);
|
||||
}
|
||||
|
||||
bool BrowserSettings::useCustomProxy()
|
||||
{
|
||||
return config()->get("Browser/UseCustomProxy", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setUseCustomProxy(bool enabled)
|
||||
{
|
||||
config()->set("Browser/UseCustomProxy", enabled);
|
||||
}
|
||||
|
||||
QString BrowserSettings::customProxyLocation()
|
||||
{
|
||||
return config()->get("Browser/CustomProxyLocation", " ").toString();
|
||||
}
|
||||
|
||||
void BrowserSettings::setCustomProxyLocation(QString location)
|
||||
{
|
||||
config()->set("Browser/CustomProxyLocation", location);
|
||||
}
|
||||
|
||||
bool BrowserSettings::updateBinaryPath()
|
||||
{
|
||||
return config()->get("Browser/UpdateBinaryPath", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setUpdateBinaryPath(bool enabled)
|
||||
{
|
||||
config()->set("Browser/UpdateBinaryPath", enabled);
|
||||
}
|
||||
|
||||
bool BrowserSettings::chromeSupport() {
|
||||
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);
|
||||
}
|
||||
|
||||
void BrowserSettings::setChromeSupport(bool enabled) {
|
||||
m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation());
|
||||
}
|
||||
|
||||
bool BrowserSettings::chromiumSupport() {
|
||||
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROMIUM);
|
||||
}
|
||||
|
||||
void BrowserSettings::setChromiumSupport(bool enabled) {
|
||||
m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation());
|
||||
}
|
||||
|
||||
bool BrowserSettings::firefoxSupport() {
|
||||
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::FIREFOX);
|
||||
}
|
||||
|
||||
void BrowserSettings::setFirefoxSupport(bool enabled) {
|
||||
m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation());
|
||||
}
|
||||
|
||||
bool BrowserSettings::vivaldiSupport() {
|
||||
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::VIVALDI);
|
||||
}
|
||||
|
||||
void BrowserSettings::setVivaldiSupport(bool enabled) {
|
||||
m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordUseNumbers()
|
||||
{
|
||||
return config()->get("generator/Numbers", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordUseNumbers(bool useNumbers)
|
||||
{
|
||||
config()->set("generator/Numbers", useNumbers);
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordUseLowercase()
|
||||
{
|
||||
return config()->get("generator/LowerCase", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordUseLowercase(bool useLowercase)
|
||||
{
|
||||
config()->set("generator/LowerCase", useLowercase);
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordUseUppercase()
|
||||
{
|
||||
return config()->get("generator/UpperCase", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordUseUppercase(bool useUppercase)
|
||||
{
|
||||
config()->set("generator/UpperCase", useUppercase);
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordUseSpecial()
|
||||
{
|
||||
return config()->get("generator/SpecialChars", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordUseSpecial(bool useSpecial)
|
||||
{
|
||||
config()->set("generator/SpecialChars", useSpecial);
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordUseEASCII()
|
||||
{
|
||||
return config()->get("generator/EASCII", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordUseEASCII(bool useEASCII)
|
||||
{
|
||||
config()->set("generator/EASCII", useEASCII);
|
||||
}
|
||||
|
||||
int BrowserSettings::passPhraseWordCount()
|
||||
{
|
||||
return config()->get("generator/WordCount", 6).toInt();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPassPhraseWordCount(int wordCount)
|
||||
{
|
||||
config()->set("generator/WordCount", wordCount);
|
||||
}
|
||||
|
||||
QString BrowserSettings::passPhraseWordSeparator()
|
||||
{
|
||||
return config()->get("generator/WordSeparator", " ").toString();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPassPhraseWordSeparator(QString separator)
|
||||
{
|
||||
config()->set("generator/WordSeparator", separator);
|
||||
}
|
||||
|
||||
int BrowserSettings::generatorType()
|
||||
{
|
||||
return config()->get("generator/Type", 0).toInt();
|
||||
}
|
||||
|
||||
void BrowserSettings::setGeneratorType(int type)
|
||||
{
|
||||
config()->set("generator/Type", type);
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordEveryGroup()
|
||||
{
|
||||
return config()->get("generator/EnsureEvery", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordEveryGroup(bool everyGroup)
|
||||
{
|
||||
config()->get("generator/EnsureEvery", everyGroup);
|
||||
}
|
||||
|
||||
bool BrowserSettings::passwordExcludeAlike()
|
||||
{
|
||||
return config()->get("generator/ExcludeAlike", true).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordExcludeAlike(bool excludeAlike)
|
||||
{
|
||||
config()->set("generator/ExcludeAlike", excludeAlike);
|
||||
}
|
||||
|
||||
int BrowserSettings::passwordLength()
|
||||
{
|
||||
return config()->get("generator/Length", 20).toInt();
|
||||
}
|
||||
|
||||
void BrowserSettings::setPasswordLength(int length)
|
||||
{
|
||||
config()->set("generator/Length", length);
|
||||
m_passwordGenerator.setLength(length);
|
||||
}
|
||||
|
||||
PasswordGenerator::CharClasses BrowserSettings::passwordCharClasses()
|
||||
{
|
||||
PasswordGenerator::CharClasses classes;
|
||||
if (passwordUseLowercase()) {
|
||||
classes |= PasswordGenerator::LowerLetters;
|
||||
}
|
||||
if (passwordUseUppercase()) {
|
||||
classes |= PasswordGenerator::UpperLetters;
|
||||
}
|
||||
if (passwordUseNumbers()) {
|
||||
classes |= PasswordGenerator::Numbers;
|
||||
}
|
||||
if (passwordUseSpecial()) {
|
||||
classes |= PasswordGenerator::SpecialCharacters;
|
||||
}
|
||||
if (passwordUseEASCII()) {
|
||||
classes |= PasswordGenerator::EASCII;
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
PasswordGenerator::GeneratorFlags BrowserSettings::passwordGeneratorFlags()
|
||||
{
|
||||
PasswordGenerator::GeneratorFlags flags;
|
||||
if (passwordExcludeAlike()) {
|
||||
flags |= PasswordGenerator::ExcludeLookAlike;
|
||||
}
|
||||
if (passwordEveryGroup()) {
|
||||
flags |= PasswordGenerator::CharFromEveryGroup;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
QString BrowserSettings::generatePassword()
|
||||
{
|
||||
if (generatorType() == 0) {
|
||||
m_passwordGenerator.setLength(passwordLength());
|
||||
m_passwordGenerator.setCharClasses(passwordCharClasses());
|
||||
m_passwordGenerator.setFlags(passwordGeneratorFlags());
|
||||
return m_passwordGenerator.generatePassword();
|
||||
} else {
|
||||
m_passPhraseGenerator.setDefaultWordList();
|
||||
m_passPhraseGenerator.setWordCount(passPhraseWordCount());
|
||||
m_passPhraseGenerator.setWordSeparator(passPhraseWordSeparator());
|
||||
return m_passPhraseGenerator.generatePassphrase();
|
||||
}
|
||||
}
|
||||
|
||||
int BrowserSettings::getbits()
|
||||
{
|
||||
return m_passwordGenerator.getbits();
|
||||
}
|
||||
|
||||
void BrowserSettings::updateBinaryPaths(QString customProxyLocation)
|
||||
{
|
||||
bool isProxy = supportBrowserProxy();
|
||||
m_hostInstaller.updateBinaryPaths(isProxy, customProxyLocation);
|
||||
}
|
105
src/browser/BrowserSettings.h
Executable file
105
src/browser/BrowserSettings.h
Executable file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERSETTINGS_H
|
||||
#define BROWSERSETTINGS_H
|
||||
|
||||
#include "core/PasswordGenerator.h"
|
||||
#include "core/PassphraseGenerator.h"
|
||||
#include "HostInstaller.h"
|
||||
|
||||
class BrowserSettings
|
||||
{
|
||||
public:
|
||||
static bool isEnabled();
|
||||
static void setEnabled(bool enabled);
|
||||
|
||||
static bool showNotification(); //TODO!!
|
||||
static void setShowNotification(bool showNotification);
|
||||
static bool bestMatchOnly(); //TODO!!
|
||||
static void setBestMatchOnly(bool bestMatchOnly);
|
||||
static bool unlockDatabase(); //TODO!!
|
||||
static void setUnlockDatabase(bool unlockDatabase);
|
||||
static bool matchUrlScheme();
|
||||
static void setMatchUrlScheme(bool matchUrlScheme);
|
||||
static bool sortByUsername();
|
||||
static void setSortByUsername(bool sortByUsername = true);
|
||||
static bool sortByTitle();
|
||||
static void setSortByTitle(bool sortByUsertitle = true);
|
||||
static bool alwaysAllowAccess();
|
||||
static void setAlwaysAllowAccess(bool alwaysAllowAccess);
|
||||
static bool alwaysAllowUpdate();
|
||||
static void setAlwaysAllowUpdate(bool alwaysAllowUpdate);
|
||||
static bool searchInAllDatabases();//TODO!!
|
||||
static void setSearchInAllDatabases(bool searchInAllDatabases);
|
||||
static bool supportKphFields();
|
||||
static void setSupportKphFields(bool supportKphFields);
|
||||
|
||||
static bool supportBrowserProxy();
|
||||
static void setSupportBrowserProxy(bool enabled);
|
||||
static bool useCustomProxy();
|
||||
static void setUseCustomProxy(bool enabled);
|
||||
static QString customProxyLocation();
|
||||
static void setCustomProxyLocation(QString location);
|
||||
static bool updateBinaryPath();
|
||||
static void setUpdateBinaryPath(bool enabled);
|
||||
static bool chromeSupport();
|
||||
static void setChromeSupport(bool enabled);
|
||||
static bool chromiumSupport();
|
||||
static void setChromiumSupport(bool enabled);
|
||||
static bool firefoxSupport();
|
||||
static void setFirefoxSupport(bool enabled);
|
||||
static bool vivaldiSupport();
|
||||
static void setVivaldiSupport(bool enabled);
|
||||
|
||||
static bool passwordUseNumbers();
|
||||
static void setPasswordUseNumbers(bool useNumbers);
|
||||
static bool passwordUseLowercase();
|
||||
static void setPasswordUseLowercase(bool useLowercase);
|
||||
static bool passwordUseUppercase();
|
||||
static void setPasswordUseUppercase(bool useUppercase);
|
||||
static bool passwordUseSpecial();
|
||||
static void setPasswordUseSpecial(bool useSpecial);
|
||||
static bool passwordUseEASCII();
|
||||
static void setPasswordUseEASCII(bool useEASCII);
|
||||
static int passPhraseWordCount();
|
||||
static void setPassPhraseWordCount(int wordCount);
|
||||
static QString passPhraseWordSeparator();
|
||||
static void setPassPhraseWordSeparator(QString separator);
|
||||
static int generatorType();
|
||||
static void setGeneratorType(int type);
|
||||
static bool passwordEveryGroup();
|
||||
static void setPasswordEveryGroup(bool everyGroup);
|
||||
static bool passwordExcludeAlike();
|
||||
static void setPasswordExcludeAlike(bool excludeAlike);
|
||||
static int passwordLength();
|
||||
static void setPasswordLength(int length);
|
||||
static PasswordGenerator::CharClasses passwordCharClasses();
|
||||
static PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
|
||||
static QString generatePassword();
|
||||
static int getbits();
|
||||
static void updateBinaryPaths(QString customProxyLocation = QString());
|
||||
|
||||
private:
|
||||
static PasswordGenerator m_passwordGenerator;
|
||||
static PassphraseGenerator m_passPhraseGenerator;
|
||||
static HostInstaller m_hostInstaller;
|
||||
};
|
||||
|
||||
#endif // BROWSERSETTINGS_H
|
37
src/browser/CMakeLists.txt
Executable file
37
src/browser/CMakeLists.txt
Executable file
@ -0,0 +1,37 @@
|
||||
# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
# Copyright (C) 2017 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
find_package(sodium 1.0.12 REQUIRED)
|
||||
|
||||
set(keepassxcbrowser_SOURCES
|
||||
BrowserAccessControlDialog.cpp
|
||||
BrowserAction.cpp
|
||||
BrowserClients.cpp
|
||||
BrowserEntryConfig.cpp
|
||||
BrowserOptionDialog.cpp
|
||||
BrowserService.cpp
|
||||
BrowserSettings.cpp
|
||||
HostInstaller.cpp
|
||||
NativeMessagingBase.cpp
|
||||
NativeMessagingHost.cpp
|
||||
Variant.cpp
|
||||
)
|
||||
|
||||
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
|
||||
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium)
|
||||
endif()
|
230
src/browser/HostInstaller.cpp
Normal file
230
src/browser/HostInstaller.cpp
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HostInstaller.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QCoreApplication>
|
||||
#include <QMessageBox>
|
||||
|
||||
const QString HostInstaller::HOST_NAME = "org.keepassxc.keepassxc_browser";
|
||||
const QStringList HostInstaller::ALLOWED_ORIGINS = QStringList()
|
||||
<< "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/"
|
||||
<< "chrome-extension://fhakpkpdnjecjfceboihdjpfmgajebii/"
|
||||
<< "chrome-extension://jaikbblhommnkeialomogohhdlndpfbi/";
|
||||
|
||||
const QStringList HostInstaller::ALLOWED_EXTENSIONS = QStringList()
|
||||
<< "keepassxc-browser@keepassxc.org";
|
||||
|
||||
#if defined(Q_OS_OSX)
|
||||
const QString HostInstaller::TARGET_DIR_CHROME = "/Library/Application Support/Google/Chrome/NativeMessagingHosts";
|
||||
const QString HostInstaller::TARGET_DIR_CHROMIUM = "/Library/Application Support/Chromium/NativeMessagingHosts";
|
||||
const QString HostInstaller::TARGET_DIR_FIREFOX = "/Library/Application Support/Mozilla/NativeMessagingHosts";
|
||||
const QString HostInstaller::TARGET_DIR_VIVALDI = "/Library/Application Support/Vivaldi/NativeMessagingHosts";
|
||||
#elif defined(Q_OS_LINUX)
|
||||
const QString HostInstaller::TARGET_DIR_CHROME = "/.config/google-chrome/NativeMessagingHosts";
|
||||
const QString HostInstaller::TARGET_DIR_CHROMIUM = "/.config/chromium/NativeMessagingHosts";
|
||||
const QString HostInstaller::TARGET_DIR_FIREFOX = "/.mozilla/native-messaging-hosts";
|
||||
const QString HostInstaller::TARGET_DIR_VIVALDI = "/.config/vivaldi/NativeMessagingHosts";
|
||||
#elif defined(Q_OS_WIN)
|
||||
const QString HostInstaller::TARGET_DIR_CHROME = "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
|
||||
const QString HostInstaller::TARGET_DIR_CHROMIUM = "HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
|
||||
const QString HostInstaller::TARGET_DIR_FIREFOX = "HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
|
||||
const QString HostInstaller::TARGET_DIR_VIVALDI = "HKEY_CURRENT_USER\\Software\\Vivaldi\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
|
||||
#endif
|
||||
|
||||
HostInstaller::HostInstaller()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HostInstaller::checkIfInstalled(SupportedBrowsers browser)
|
||||
{
|
||||
QString fileName = getPath(browser);
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
return registryEntryFound(settings);
|
||||
#else
|
||||
return QFile::exists(fileName);
|
||||
#endif
|
||||
}
|
||||
|
||||
void HostInstaller::installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy, const QString& location)
|
||||
{
|
||||
if (enabled) {
|
||||
#ifdef Q_OS_WIN
|
||||
// Create a registry key
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
if (!registryEntryFound(settings)) {
|
||||
settings.setValue("Default", getPath(browser));
|
||||
}
|
||||
#endif
|
||||
// Always create the script file
|
||||
QJsonObject script = constructFile(browser, proxy, location);
|
||||
if (!saveFile(browser, script)) {
|
||||
QMessageBox::critical(0, tr("KeePassXC: Cannot save file!"),
|
||||
tr("Cannot save the native messaging script file."), QMessageBox::Ok);
|
||||
}
|
||||
} else {
|
||||
// Remove the script file
|
||||
QString fileName = getPath(browser);
|
||||
QFile::remove(fileName);
|
||||
#ifdef Q_OS_WIN
|
||||
// Remove the registry entry
|
||||
QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
|
||||
if (registryEntryFound(settings)) {
|
||||
settings.remove("Default");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
|
||||
installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString HostInstaller::getTargetPath(SupportedBrowsers browser) const
|
||||
{
|
||||
switch (browser) {
|
||||
case SupportedBrowsers::CHROME: return HostInstaller::TARGET_DIR_CHROME;
|
||||
case SupportedBrowsers::CHROMIUM: return HostInstaller::TARGET_DIR_CHROMIUM;
|
||||
case SupportedBrowsers::FIREFOX: return HostInstaller::TARGET_DIR_FIREFOX;
|
||||
case SupportedBrowsers::VIVALDI: return HostInstaller::TARGET_DIR_VIVALDI;
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QString HostInstaller::getBrowserName(SupportedBrowsers browser) const
|
||||
{
|
||||
switch (browser) {
|
||||
case SupportedBrowsers::CHROME: return "chrome";
|
||||
case SupportedBrowsers::CHROMIUM: return "chromium";
|
||||
case SupportedBrowsers::FIREFOX: return "firefox";
|
||||
case SupportedBrowsers::VIVALDI: return "vivaldi";
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QString HostInstaller::getPath(SupportedBrowsers browser) const
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
// If portable settings file exists save the JSON scripts to application folder
|
||||
QString userPath;
|
||||
QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini";
|
||||
if (QFile::exists(portablePath)) {
|
||||
userPath = QCoreApplication::applicationDirPath();
|
||||
} else {
|
||||
userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
|
||||
}
|
||||
|
||||
QString winPath = QString("%1/%2_%3.json").arg(userPath, HostInstaller::HOST_NAME, getBrowserName(browser));
|
||||
winPath.replace("/","\\");
|
||||
return winPath;
|
||||
#else
|
||||
QString path = getTargetPath(browser);
|
||||
return QString("%1%2/%3.json").arg(QDir::homePath(), path, HostInstaller::HOST_NAME);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString HostInstaller::getInstallDir(SupportedBrowsers browser) const
|
||||
{
|
||||
QString path = getTargetPath(browser);
|
||||
#ifdef Q_OS_WIN
|
||||
return QCoreApplication::applicationDirPath();
|
||||
#else
|
||||
return QString("%1%2").arg(QDir::homePath(), path);
|
||||
#endif
|
||||
}
|
||||
|
||||
QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location)
|
||||
{
|
||||
QString path;
|
||||
if (proxy) {
|
||||
if (!location.isEmpty()) {
|
||||
path = location;
|
||||
} else {
|
||||
path = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath();
|
||||
path.append("/keepassxc-proxy");
|
||||
#ifdef Q_OS_WIN
|
||||
path.append(".exe");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
path = QFileInfo(QCoreApplication::applicationFilePath()).absoluteFilePath();
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
path.replace("/","\\");
|
||||
#endif
|
||||
|
||||
QJsonObject script;
|
||||
script["name"] = HostInstaller::HOST_NAME;
|
||||
script["description"] = "KeePassXC integration with native messaging support";
|
||||
script["path"] = path;
|
||||
script["type"] = "stdio";
|
||||
|
||||
QJsonArray arr;
|
||||
if (browser == SupportedBrowsers::FIREFOX) {
|
||||
for (const QString extension : HostInstaller::ALLOWED_EXTENSIONS) {
|
||||
arr.append(extension);
|
||||
}
|
||||
script["allowed_extensions"] = arr;
|
||||
} else {
|
||||
for (const QString origin : HostInstaller::ALLOWED_ORIGINS) {
|
||||
arr.append(origin);
|
||||
}
|
||||
script["allowed_origins"] = arr;
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
bool HostInstaller::registryEntryFound(const QSettings& settings)
|
||||
{
|
||||
return !settings.value("Default").isNull();
|
||||
}
|
||||
|
||||
bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& script)
|
||||
{
|
||||
QString path = getPath(browser);
|
||||
QString installDir = getInstallDir(browser);
|
||||
QDir dir(installDir);
|
||||
if (!dir.exists()) {
|
||||
QDir().mkpath(installDir);
|
||||
}
|
||||
|
||||
QFile scriptFile(path);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc(script);
|
||||
qint64 bytesWritten = scriptFile.write(doc.toJson());
|
||||
if (bytesWritten < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
63
src/browser/HostInstaller.h
Normal file
63
src/browser/HostInstaller.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef HOSTINSTALLER_H
|
||||
#define HOSTINSTALLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QSettings>
|
||||
|
||||
class HostInstaller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum SupportedBrowsers : int {
|
||||
CHROME = 0,
|
||||
CHROMIUM = 1,
|
||||
FIREFOX = 2,
|
||||
VIVALDI = 3
|
||||
};
|
||||
|
||||
public:
|
||||
HostInstaller();
|
||||
bool checkIfInstalled(SupportedBrowsers browser);
|
||||
void installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy = false, const QString& location = "");
|
||||
void updateBinaryPaths(const bool& proxy, const QString& location = "");
|
||||
|
||||
private:
|
||||
QString getTargetPath(SupportedBrowsers browser) const;
|
||||
QString getBrowserName(SupportedBrowsers browser) const;
|
||||
QString getPath(SupportedBrowsers browser) const;
|
||||
QString getInstallDir(SupportedBrowsers browser) const;
|
||||
QJsonObject constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location);
|
||||
bool registryEntryFound(const QSettings& settings);
|
||||
bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
|
||||
|
||||
private:
|
||||
static const QString HOST_NAME;
|
||||
static const QStringList ALLOWED_EXTENSIONS;
|
||||
static const QStringList ALLOWED_ORIGINS;
|
||||
static const QString TARGET_DIR_CHROME;
|
||||
static const QString TARGET_DIR_CHROMIUM;
|
||||
static const QString TARGET_DIR_FIREFOX;
|
||||
static const QString TARGET_DIR_VIVALDI;
|
||||
};
|
||||
|
||||
#endif // HOSTINSTALLER_H
|
141
src/browser/NativeMessagingBase.cpp
Normal file
141
src/browser/NativeMessagingBase.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "NativeMessagingBase.h"
|
||||
#include <QStandardPaths>
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
|
||||
#include <sys/types.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
NativeMessagingBase::NativeMessagingBase()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#else
|
||||
m_notifier.reset(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this));
|
||||
connect(m_notifier.data(), SIGNAL(activated(int)), this, SLOT(newNativeMessage()));
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingBase::newNativeMessage()
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
|
||||
struct kevent ev[1];
|
||||
struct timespec ts = { 5, 0 };
|
||||
|
||||
int fd = kqueue();
|
||||
if (fd == -1) {
|
||||
m_notifier->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
EV_SET(ev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, nullptr);
|
||||
if (kevent(fd, ev, 1, nullptr, 0, &ts) == -1) {
|
||||
m_notifier->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = kevent(fd, NULL, 0, ev, 1, &ts);
|
||||
if (ret < 1) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
int fd = epoll_create(5);
|
||||
struct epoll_event event;
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = 0;
|
||||
if (epoll_ctl(fd, EPOLL_CTL_ADD, 0, &event) != 0) {
|
||||
m_notifier->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (epoll_wait(fd, &event, 1, 5000) < 1) {
|
||||
m_notifier->setEnabled(false);
|
||||
::close(fd);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
readLength();
|
||||
#ifndef Q_OS_WIN
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingBase::readNativeMessages()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
quint32 length = 0;
|
||||
while (m_running.load() && !std::cin.eof()) {
|
||||
length = 0;
|
||||
std::cin.read(reinterpret_cast<char*>(&length), 4);
|
||||
readStdIn(length);
|
||||
QThread::msleep(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NativeMessagingBase::jsonToString(const QJsonObject& json) const
|
||||
{
|
||||
return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
void NativeMessagingBase::sendReply(const QJsonObject& json)
|
||||
{
|
||||
if (!json.isEmpty()) {
|
||||
sendReply(jsonToString(json));
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingBase::sendReply(const QString& reply)
|
||||
{
|
||||
if (!reply.isEmpty()) {
|
||||
uint len = reply.length();
|
||||
std::cout << char(((len>>0) & 0xFF)) << char(((len>>8) & 0xFF)) << char(((len>>16) & 0xFF)) << char(((len>>24) & 0xFF));
|
||||
std::cout << reply.toStdString() << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
QString NativeMessagingBase::getLocalServerPath() const
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/kpxc_server";
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
// Use XDG_RUNTIME_DIR instead of /tmp/ if it's available
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + "/kpxc_server";
|
||||
return path.isEmpty() ? "/tmp/kpxc_server" : path;
|
||||
#else // Q_OS_MAC and others
|
||||
return "/tmp/kpxc_server";
|
||||
#endif
|
||||
}
|
61
src/browser/NativeMessagingBase.h
Normal file
61
src/browser/NativeMessagingBase.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGINGBASE_H
|
||||
#define NATIVEMESSAGINGBASE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QFuture>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QMutex>
|
||||
#include <QSocketNotifier>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QAtomicInteger>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
class NativeMessagingBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NativeMessagingBase();
|
||||
~NativeMessagingBase() = default;
|
||||
|
||||
protected slots:
|
||||
void newNativeMessage();
|
||||
|
||||
protected:
|
||||
virtual void readLength() = 0;
|
||||
virtual void readStdIn(const quint32 length) = 0;
|
||||
void readNativeMessages();
|
||||
QString jsonToString(const QJsonObject& json) const;
|
||||
void sendReply(const QJsonObject& json);
|
||||
void sendReply(const QString& reply);
|
||||
QString getLocalServerPath() const;
|
||||
|
||||
protected:
|
||||
QAtomicInteger<quint8> m_running;
|
||||
QSharedPointer<QSocketNotifier> m_notifier;
|
||||
QFuture<void> m_future;
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGBASE_H
|
200
src/browser/NativeMessagingHost.cpp
Executable file
200
src/browser/NativeMessagingHost.cpp
Executable file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QtNetwork>
|
||||
#include <iostream>
|
||||
#include "sodium.h"
|
||||
#include "NativeMessagingHost.h"
|
||||
#include "BrowserSettings.h"
|
||||
|
||||
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent) :
|
||||
NativeMessagingBase(),
|
||||
m_mutex(QMutex::Recursive),
|
||||
m_browserClients(m_browserService),
|
||||
m_browserService(parent)
|
||||
{
|
||||
m_localServer.reset(new QLocalServer(this));
|
||||
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
m_running.store(false);
|
||||
|
||||
if (BrowserSettings::isEnabled() && !m_running) {
|
||||
run();
|
||||
}
|
||||
|
||||
connect(&m_browserService, SIGNAL(databaseLocked()), this, SLOT(databaseLocked()));
|
||||
connect(&m_browserService, SIGNAL(databaseUnlocked()), this, SLOT(databaseUnlocked()));
|
||||
}
|
||||
|
||||
NativeMessagingHost::~NativeMessagingHost()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
int NativeMessagingHost::init()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return sodium_init();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::run()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_running.load() && init() == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
|
||||
if (BrowserSettings::updateBinaryPath()) {
|
||||
BrowserSettings::updateBinaryPaths(BrowserSettings::useCustomProxy() ? BrowserSettings::customProxyLocation() : "");
|
||||
}
|
||||
|
||||
m_running.store(true);
|
||||
#ifdef Q_OS_WIN
|
||||
m_future = QtConcurrent::run(this, static_cast<void(NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
|
||||
#endif
|
||||
|
||||
if (BrowserSettings::supportBrowserProxy()) {
|
||||
QString serverPath = getLocalServerPath();
|
||||
QFile::remove(serverPath);
|
||||
m_localServer->listen(serverPath);
|
||||
connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(newLocalConnection()));
|
||||
} else {
|
||||
m_localServer->close();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::stop()
|
||||
{
|
||||
databaseLocked();
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_socketList.clear();
|
||||
m_running.testAndSetOrdered(true, false);
|
||||
m_future.waitForFinished();
|
||||
m_localServer->close();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readLength()
|
||||
{
|
||||
quint32 length = 0;
|
||||
std::cin.read(reinterpret_cast<char*>(&length), 4);
|
||||
if (!std::cin.eof() && length > 0)
|
||||
{
|
||||
readStdIn(length);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readStdIn(const quint32 length)
|
||||
{
|
||||
if (length > 0) {
|
||||
QByteArray arr;
|
||||
arr.reserve(length);
|
||||
|
||||
for (quint32 i = 0; i < length; ++i) {
|
||||
arr.append(getchar());
|
||||
}
|
||||
|
||||
if (arr.length() > 0) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
sendReply(m_browserClients.readResponse(arr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::newLocalConnection()
|
||||
{
|
||||
QLocalSocket* socket = m_localServer->nextPendingConnection();
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(disconnectSocket()));
|
||||
}
|
||||
|
||||
void NativeMessagingHost::newLocalMessage()
|
||||
{
|
||||
QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
|
||||
|
||||
if (!socket || socket->bytesAvailable() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray arr = socket->readAll();
|
||||
if (arr.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_socketList.contains(socket)) {
|
||||
m_socketList.push_back(socket);
|
||||
}
|
||||
|
||||
QString reply = jsonToString(m_browserClients.readResponse(arr));
|
||||
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
|
||||
QByteArray arr = reply.toUtf8();
|
||||
socket->write(arr.constData(), arr.length());
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::sendReplyToAllClients(const QJsonObject& json)
|
||||
{
|
||||
QString reply = jsonToString(json);
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (const auto socket : m_socketList) {
|
||||
if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
|
||||
QByteArray arr = reply.toUtf8();
|
||||
socket->write(arr.constData(), arr.length());
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::disconnectSocket()
|
||||
{
|
||||
QLocalSocket* socket(qobject_cast<QLocalSocket*>(QObject::sender()));
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (auto s : m_socketList) {
|
||||
if (s == socket) {
|
||||
m_socketList.removeOne(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::removeSharedEncryptionKeys()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.removeSharedEncryptionKeys();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::removeStoredPermissions()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.removeStoredPermissions();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::databaseLocked()
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = "database-locked";
|
||||
sendReplyToAllClients(response);
|
||||
}
|
||||
|
||||
void NativeMessagingHost::databaseUnlocked()
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = "database-unlocked";
|
||||
sendReplyToAllClients(response);
|
||||
}
|
67
src/browser/NativeMessagingHost.h
Executable file
67
src/browser/NativeMessagingHost.h
Executable file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGINGHOST_H
|
||||
#define NATIVEMESSAGINGHOST_H
|
||||
|
||||
#include "NativeMessagingBase.h"
|
||||
#include "BrowserClients.h"
|
||||
#include "BrowserService.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
|
||||
class NativeMessagingHost : public NativeMessagingBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
typedef QList<QLocalSocket*> SocketList;
|
||||
|
||||
public:
|
||||
explicit NativeMessagingHost(DatabaseTabWidget* parent = 0);
|
||||
~NativeMessagingHost();
|
||||
int init();
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
public slots:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
signals:
|
||||
void quit();
|
||||
|
||||
private:
|
||||
void readLength();
|
||||
void readStdIn(const quint32 length);
|
||||
void sendReplyToAllClients(const QJsonObject& json);
|
||||
|
||||
private slots:
|
||||
void databaseLocked();
|
||||
void databaseUnlocked();
|
||||
void newLocalConnection();
|
||||
void newLocalMessage();
|
||||
void disconnectSocket();
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
BrowserClients m_browserClients;
|
||||
BrowserService m_browserService;
|
||||
QSharedPointer<QLocalServer> m_localServer;
|
||||
SocketList m_socketList;
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGHOST_H
|
37
src/browser/Variant.cpp
Normal file
37
src/browser/Variant.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Variant.h"
|
||||
|
||||
QVariantMap qo2qv(const QObject* object, const QStringList& ignoredProperties)
|
||||
{
|
||||
QVariantMap result;
|
||||
const QMetaObject* metaobject = object->metaObject();
|
||||
int count = metaobject->propertyCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
QMetaProperty metaproperty = metaobject->property(i);
|
||||
const char* name = metaproperty.name();
|
||||
|
||||
if (ignoredProperties.contains(QLatin1String(name)) || (!metaproperty.isReadable())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QVariant value = object->property(name);
|
||||
result[QLatin1String(name)] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
25
src/browser/Variant.h
Normal file
25
src/browser/Variant.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef VARIANT_H
|
||||
#define VARIANT_H
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
QVariantMap qo2qv(const QObject* object, const QStringList& ignoredProperties = QStringList(QString(QLatin1String("objectName"))));
|
||||
|
||||
#endif // VARIANT_H
|
@ -16,6 +16,7 @@
|
||||
#cmakedefine WITH_XC_AUTOTYPE
|
||||
#cmakedefine WITH_XC_YUBIKEY
|
||||
#cmakedefine WITH_XC_SSHAGENT
|
||||
#cmakedefine WITH_XC_BROWSER
|
||||
|
||||
#cmakedefine KEEPASSXC_DIST
|
||||
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"
|
||||
|
@ -28,8 +28,7 @@ PassphraseGenerator::PassphraseGenerator()
|
||||
: m_wordCount(0)
|
||||
, m_separator(' ')
|
||||
{
|
||||
const QString path = filePath()->dataPath("wordlists/eff_large.wordlist");
|
||||
setWordList(path);
|
||||
|
||||
}
|
||||
|
||||
double PassphraseGenerator::calculateEntropy(QString passphrase)
|
||||
@ -46,12 +45,12 @@ double PassphraseGenerator::calculateEntropy(QString passphrase)
|
||||
void PassphraseGenerator::setWordCount(int wordCount)
|
||||
{
|
||||
if (wordCount > 0) {
|
||||
m_wordCount = wordCount;
|
||||
m_wordCount = wordCount;
|
||||
} else {
|
||||
// safe default if something goes wrong
|
||||
m_wordCount = 7;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void PassphraseGenerator::setWordList(QString path)
|
||||
@ -75,6 +74,12 @@ void PassphraseGenerator::setWordList(QString path)
|
||||
}
|
||||
}
|
||||
|
||||
void PassphraseGenerator::setDefaultWordList()
|
||||
{
|
||||
const QString path = filePath()->dataPath("wordlists/eff_large.wordlist");
|
||||
setWordList(path);
|
||||
}
|
||||
|
||||
void PassphraseGenerator::setWordSeparator(QString separator) {
|
||||
m_separator = separator;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
double calculateEntropy(QString passphrase);
|
||||
void setWordCount(int wordCount);
|
||||
void setWordList(QString path);
|
||||
void setDefaultWordList();
|
||||
void setWordSeparator(QString separator);
|
||||
bool isValid() const;
|
||||
|
||||
@ -43,4 +44,4 @@ private:
|
||||
Q_DISABLE_COPY(PassphraseGenerator)
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_PASSPHRASEGENERATOR_H
|
||||
#endif // KEEPASSX_PASSPHRASEGENERATOR_H
|
||||
|
@ -90,6 +90,9 @@ AboutDialog::AboutDialog(QWidget* parent)
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
extensions += "\n- SSH Agent";
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
extensions += "\n- Native messaging browser extension";
|
||||
#endif
|
||||
|
||||
if (extensions.isEmpty())
|
||||
extensions = " None";
|
||||
|
@ -54,6 +54,12 @@
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#include "browser/NativeMessagingHost.h"
|
||||
#include "browser/BrowserSettings.h"
|
||||
#include "browser/BrowserOptionDialog.h"
|
||||
#endif
|
||||
|
||||
#include "gui/SettingsWidget.h"
|
||||
#include "gui/PasswordGeneratorWidget.h"
|
||||
|
||||
@ -104,6 +110,54 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER
|
||||
class BrowserPlugin: public ISettingsPage
|
||||
{
|
||||
public:
|
||||
BrowserPlugin(DatabaseTabWidget* tabWidget) {
|
||||
m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget));
|
||||
}
|
||||
|
||||
~BrowserPlugin() {
|
||||
|
||||
}
|
||||
|
||||
QString name() override
|
||||
{
|
||||
return QObject::tr("Browser extension with native messaging");
|
||||
}
|
||||
|
||||
QIcon icon() override
|
||||
{
|
||||
return FilePath::instance()->icon("apps", "internet-web-browser");
|
||||
}
|
||||
|
||||
QWidget* createWidget() override {
|
||||
BrowserOptionDialog* dlg = new BrowserOptionDialog();
|
||||
QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_nativeMessagingHost.data(), SLOT(removeSharedEncryptionKeys()));
|
||||
QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_nativeMessagingHost.data(), SLOT(removeStoredPermissions()));
|
||||
return dlg;
|
||||
}
|
||||
|
||||
void loadSettings(QWidget* widget) override
|
||||
{
|
||||
qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
|
||||
}
|
||||
|
||||
void saveSettings(QWidget* widget) override
|
||||
{
|
||||
qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
|
||||
if (BrowserSettings::isEnabled()) {
|
||||
m_nativeMessagingHost->run();
|
||||
} else {
|
||||
m_nativeMessagingHost->stop();
|
||||
}
|
||||
}
|
||||
private:
|
||||
QSharedPointer<NativeMessagingHost> m_nativeMessagingHost;
|
||||
};
|
||||
#endif
|
||||
|
||||
const QString MainWindow::BaseWindowTitle = "KeePassXC";
|
||||
|
||||
MainWindow::MainWindow()
|
||||
@ -134,6 +188,9 @@ MainWindow::MainWindow()
|
||||
SSHAgent::init(this);
|
||||
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
|
||||
#endif
|
||||
|
||||
setWindowIcon(filePath()->applicationIcon());
|
||||
m_ui->globalMessageWidget->setHidden(true);
|
||||
|
@ -80,7 +80,8 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
|
||||
m_ui->comboBoxWordList->setVisible(false);
|
||||
m_ui->labelWordList->setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
m_dicewareGenerator->setDefaultWordList();
|
||||
loadSettings();
|
||||
reset();
|
||||
}
|
||||
@ -164,7 +165,7 @@ void PasswordGeneratorWidget::keyPressEvent(QKeyEvent* e)
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::regeneratePassword()
|
||||
{
|
||||
{
|
||||
if (m_ui->tabWidget->currentIndex() == Password) {
|
||||
if (m_passwordGenerator->isValid()) {
|
||||
QString password = m_passwordGenerator->generatePassword();
|
||||
|
15
src/main.cpp
15
src/main.cpp
@ -69,12 +69,18 @@ int main(int argc, char** argv)
|
||||
"keyfile");
|
||||
QCommandLineOption pwstdinOption("pw-stdin",
|
||||
QCoreApplication::translate("main", "read password of the database from stdin"));
|
||||
// This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method
|
||||
QCommandLineOption parentWindowOption(QStringList() << "pw"
|
||||
<< "parent-window",
|
||||
QCoreApplication::translate("main", "Parent window handle"),
|
||||
"handle");
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser.addOption(configOption);
|
||||
parser.addOption(keyfileOption);
|
||||
parser.addOption(pwstdinOption);
|
||||
parser.addOption(parentWindowOption);
|
||||
|
||||
parser.process(app);
|
||||
const QStringList fileNames = parser.positionalArguments();
|
||||
@ -86,7 +92,7 @@ int main(int argc, char** argv)
|
||||
qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
QApplication::setQuitOnLastWindowClosed(false);
|
||||
|
||||
if (!Crypto::init()) {
|
||||
@ -116,7 +122,7 @@ int main(int argc, char** argv)
|
||||
QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));
|
||||
QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString)));
|
||||
QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection);
|
||||
|
||||
|
||||
// start minimized if configured
|
||||
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
|
||||
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
|
||||
@ -126,7 +132,7 @@ int main(int argc, char** argv)
|
||||
if (!(minimizeOnStartup && minimizeToTray)) {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
|
||||
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
|
||||
const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
|
||||
for (const QString& filename: fileNames) {
|
||||
@ -138,13 +144,12 @@ int main(int argc, char** argv)
|
||||
|
||||
const bool pwstdin = parser.isSet(pwstdinOption);
|
||||
for (const QString& filename: fileNames) {
|
||||
if (!filename.isEmpty() && QFile::exists(filename)) {
|
||||
if (!filename.isEmpty() && QFile::exists(filename) && !filename.endsWith(".json", Qt::CaseInsensitive)) {
|
||||
QString password;
|
||||
if (pwstdin) {
|
||||
static QTextStream in(stdin, QIODevice::ReadOnly);
|
||||
password = in.readLine();
|
||||
}
|
||||
mainWindow.openDatabase(filename, password, parser.value(keyfileOption));
|
||||
}
|
||||
}
|
||||
|
||||
|
34
src/proxy/CMakeLists.txt
Executable file
34
src/proxy/CMakeLists.txt
Executable file
@ -0,0 +1,34 @@
|
||||
# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
# Copyright (C) 2017 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
include_directories(${BROWSER_SOURCE_DIR})
|
||||
|
||||
set(proxy_SOURCES
|
||||
keepassxc-proxy.cpp
|
||||
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
|
||||
NativeMessagingHost.cpp)
|
||||
|
||||
add_library(proxy STATIC ${proxy_SOURCES})
|
||||
target_link_libraries(proxy Qt5::Core Qt5::Network)
|
||||
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
|
||||
target_link_libraries(keepassxc-proxy proxy)
|
||||
|
||||
install(TARGETS keepassxc-proxy
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${PROXY_INSTALL_DIR} COMPONENT Runtime)
|
||||
|
||||
endif()
|
95
src/proxy/NativeMessagingHost.cpp
Executable file
95
src/proxy/NativeMessagingHost.cpp
Executable file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include "NativeMessagingHost.h"
|
||||
|
||||
NativeMessagingHost::NativeMessagingHost() : NativeMessagingBase()
|
||||
{
|
||||
m_localSocket = new QLocalSocket();
|
||||
m_localSocket->connectToServer(getLocalServerPath());
|
||||
#ifdef Q_OS_WIN
|
||||
m_running.store(true);
|
||||
m_future = QtConcurrent::run(this, static_cast<void(NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
|
||||
#endif
|
||||
connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
|
||||
connect(m_localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket()));
|
||||
connect(m_localSocket, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), this, SLOT(socketStateChanged(QLocalSocket::LocalSocketState)));
|
||||
}
|
||||
|
||||
NativeMessagingHost::~NativeMessagingHost()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
m_future.waitForFinished();
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readLength()
|
||||
{
|
||||
quint32 length = 0;
|
||||
std::cin.read(reinterpret_cast<char*>(&length), 4);
|
||||
if (!std::cin.eof() && length > 0) {
|
||||
readStdIn(length);
|
||||
} else {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::readStdIn(const quint32 length)
|
||||
{
|
||||
if (length > 0) {
|
||||
QByteArray arr;
|
||||
arr.reserve(length);
|
||||
|
||||
for (quint32 i = 0; i < length; ++i) {
|
||||
arr.append(getchar());
|
||||
}
|
||||
|
||||
if (arr.length() > 0 && m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
|
||||
m_localSocket->write(arr.constData(), arr.length());
|
||||
m_localSocket->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::newLocalMessage()
|
||||
{
|
||||
if (!m_localSocket || m_localSocket->bytesAvailable() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray arr = m_localSocket->readAll();
|
||||
if (!arr.isEmpty()) {
|
||||
sendReply(arr);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMessagingHost::deleteSocket()
|
||||
{
|
||||
if (m_notifier) {
|
||||
m_notifier->setEnabled(false);
|
||||
}
|
||||
m_localSocket->deleteLater();
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
void NativeMessagingHost::socketStateChanged(QLocalSocket::LocalSocketState socketState)
|
||||
{
|
||||
if (socketState == QLocalSocket::UnconnectedState || socketState == QLocalSocket::ClosingState) {
|
||||
m_running.testAndSetOrdered(true, false);
|
||||
}
|
||||
}
|
43
src/proxy/NativeMessagingHost.h
Executable file
43
src/proxy/NativeMessagingHost.h
Executable file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NATIVEMESSAGINGHOST_H
|
||||
#define NATIVEMESSAGINGHOST_H
|
||||
|
||||
#include "NativeMessagingBase.h"
|
||||
|
||||
class NativeMessagingHost : public NativeMessagingBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NativeMessagingHost();
|
||||
~NativeMessagingHost();
|
||||
|
||||
public slots:
|
||||
void newLocalMessage();
|
||||
void deleteSocket();
|
||||
void socketStateChanged(QLocalSocket::LocalSocketState socketState);
|
||||
|
||||
private:
|
||||
void readLength();
|
||||
void readStdIn(const quint32 length);
|
||||
|
||||
private:
|
||||
QLocalSocket* m_localSocket;
|
||||
};
|
||||
|
||||
#endif // NATIVEMESSAGINGHOST_H
|
66
src/proxy/keepassxc-proxy.cpp
Normal file
66
src/proxy/keepassxc-proxy.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <iostream>
|
||||
#include "NativeMessagingHost.h"
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#include <initializer_list>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// (C) Gist: https://gist.github.com/azadkuh/a2ac6869661ebd3f8588
|
||||
void ignoreUnixSignals(std::initializer_list<int> ignoreSignals) {
|
||||
for (int sig : ignoreSignals) {
|
||||
signal(sig, SIG_IGN);
|
||||
}
|
||||
}
|
||||
|
||||
void catchUnixSignals(std::initializer_list<int> quitSignals) {
|
||||
auto handler = [](int sig) -> void {
|
||||
std::cerr << sig;
|
||||
QCoreApplication::quit();
|
||||
};
|
||||
|
||||
sigset_t blocking_mask;
|
||||
sigemptyset(&blocking_mask);
|
||||
for (auto sig : quitSignals) {
|
||||
sigaddset(&blocking_mask, sig);
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_mask = blocking_mask;
|
||||
sa.sa_flags = 0;
|
||||
|
||||
for (auto sig : quitSignals) {
|
||||
sigaction(sig, &sa, nullptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX)
|
||||
catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP});
|
||||
#endif
|
||||
NativeMessagingHost host;
|
||||
return a.exec();
|
||||
}
|
Loading…
Reference in New Issue
Block a user