diff --git a/CMakeLists.txt b/CMakeLists.txt index 869ba9e5a..1533d1de7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,15 +20,6 @@ if (ENABLE_WASM) set(CMAKE_EXECUTABLE_SUFFIX ".js") endif (ENABLE_WASM) -OPTION (ENABLE_SYNC "Enable 'task sync' support" ON) - -if (ENABLE_SYNC) - set (USE_GNUTLS ON CACHE BOOL "Build gnutls support." FORCE) -else (ENABLE_SYNC) - set (USE_GNUTLS OFF CACHE BOOL "Build gnutls support." FORCE) - message (WARNING "ENABLE_SYNC=OFF. Not building sync support.") -endif (ENABLE_SYNC) - message ("-- Looking for libshared") if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src) message ("-- Found libshared") @@ -71,20 +62,6 @@ SET (TASK_BINDIR bin CACHE STRING "Installation directory for the bi # rust libs require these set (TASK_LIBRARIES dl pthread) -if (USE_GNUTLS) - message ("-- Looking for GnuTLS") - find_package (GnuTLS) - if (GNUTLS_FOUND) - set (HAVE_LIBGNUTLS true) - set (TASK_INCLUDE_DIRS ${TASK_INCLUDE_DIRS} ${GNUTLS_INCLUDE_DIR}) - set (TASK_LIBRARIES ${TASK_LIBRARIES} ${GNUTLS_LIBRARIES}) - endif (GNUTLS_FOUND) -endif (USE_GNUTLS) - -if (ENABLE_SYNC AND NOT GNUTLS_FOUND) - message (FATAL_ERROR "Cannot find GnuTLS. Use -DENABLE_SYNC=OFF to build Taskwarrior without sync support. See INSTALL for more information.") -endif (ENABLE_SYNC AND NOT GNUTLS_FOUND) - check_function_exists (timegm HAVE_TIMEGM) check_function_exists (get_current_dir_name HAVE_GET_CURRENT_DIR_NAME) check_function_exists (wordexp HAVE_WORDEXP) diff --git a/ChangeLog b/ChangeLog index 84cbd00cc..be0b7da04 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +- [BREAKING CHANGE] sync is now performed against taskchampion-sync-server, + instead of taskd. The following config options are no longer supported: + - `debug.tls` + - `taskd.ca` + - `taskd.certificate` + - `taskd.ciphers` + - `taskd.credentials` + - `taskd.key` + - `taskd.server` + - `taskd.trust` + +The Taskwarrior build no longer requires GnuTLS. + ------ current release --------------------------- 2.6.2 - diff --git a/INSTALL b/INSTALL index 8640bd1f7..4e885f645 100644 --- a/INSTALL +++ b/INSTALL @@ -20,7 +20,6 @@ You will need a C++ compiler that supports full C++17, which includes: You will need the following libraries: - libuuid (not needed for OSX) - - gnutls (can be optional - see '"sync" command' below) Basic Installation @@ -89,20 +88,6 @@ get absolute installation directories: CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5 -"sync" command --------------- - -By default, GnuTLS support is required, which enables the "sync" command. -For Debian based distributions, installing "libgnutls-dev" is sufficient. - -In order to build Taskwarrior without "sync" support, call cmake with the -"-DENABLE_SYNC=OFF" flag: - - $ cmake . -DENABLE_SYNC=OFF - -and proceed as described in "Basic Installation". - - Uninstallation -------------- @@ -161,7 +146,7 @@ OpenBSD WASM Using the Emscripten compiler, you can achieve it like this: - cmake -DCMAKE_CXX_COMPILER=emcc -DENABLE_SYNC=OFF -DCMAKE_BUILD_TYPE=release -DENABLE_WASM=ON \ + cmake -DCMAKE_CXX_COMPILER=emcc -DCMAKE_BUILD_TYPE=release -DENABLE_WASM=ON \ -DCMAKE_EXE_LINKER_FLAGS="-m32 -s NO_DYNAMIC_EXECUTION=1 -s WASM=1 -s NO_EXIT_RUNTIME=1 -s INVOKE_RUN=0" \ -DCMAKE_CXX_FLAGS_RELEASE="-O2 -m32" diff --git a/README.md b/README.md index 57d197f20..ddb93cf58 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ There are many binary packages available, but to install from source requires: * make * C++ compiler, currently gcc 7.1+ or clang 5.0+ for full C++17 support * libuuid -* GnuTLS (optional, required for sync) Download the tarball, and expand it: diff --git a/cmake.h.in b/cmake.h.in index ff74c832b..6586a7f03 100644 --- a/cmake.h.in +++ b/cmake.h.in @@ -38,9 +38,6 @@ #cmakedefine GNUHURD #cmakedefine UNKNOWN -/* Found the GnuTLS library */ -#cmakedefine HAVE_LIBGNUTLS - /* Found tm_gmtoff */ #cmakedefine HAVE_TM_GMTOFF diff --git a/doc/man/task-sync.5.in b/doc/man/task-sync.5.in index f5cfb903f..5adda6ce1 100644 --- a/doc/man/task-sync.5.in +++ b/doc/man/task-sync.5.in @@ -1,51 +1,118 @@ .TH task-sync 5 2016-02-24 "${PACKAGE_STRING}" "User Manuals" .SH NAME -task-sync \- A discussion and tutorial for the various task(1) data -synchronization capabilities. +task-sync \- A discussion and tutorial for the various +.BR task (1) +data synchronization capabilities. .SH INTRODUCTION -Taskwarrior has several sync options, both external and built in. If you wish -to sync your data, choose one method only; mixing methods is going to lead to -problems. Each of the methods discussed have their own strengths. -.SH ALTERNATIVES -There are three alternatives for syncing data, which are: - -1) Version control systems, such as git, hg, svn +Taskwarrior can synchronize your tasks to a server. This has a few benefits: .br -2) File sharing systems, such as DropBox, Google Drive + - Makes your tasks accessible from multiple systems, called "replicas". .br -3) Using the Taskserver and the 'sync' command - - -.SH OPTION 1: VERSION CONTROL SYSTEMS -There are several good, distributed VCS systems (git, hg, ...) and centralized -VCS systems (svn, cvs ...), and they all function in a similar fashion for our -purposes. - -Setup is straightforward. You place your .task directory under revision -control. You then need to perform a regular commit/push/pull/update to make -sure that the data is propagated when needed. You can even do this using shell -scripts so that every task command is preceded by a 'pull' and followed by -a 'push'. - -Strengths: + - Provides a backup of your tasks. .br - - Good data transport mechanisms -.br - - Secure transport options + - Saves disk space. -Weaknesses: -.br - - You need proficiency with VCS tools -.br - - You will need to manually resolve conflicts frequently -.br - - You need to provide the mechanism for making sure copies are up to date +For example, you might want a replica of your tasks on your laptop and on your phone. +NOTE: A side-effect of synchronization is that once changes have been +synchronized, they cannot be undone. This means that each time synchronization +is run, it is no longer possible to undo previous operations. + +.SH CONFIGURATION + +To synchronize your tasks to a sync server, you will need the following +information from the server administrator: + +.br + - The server's URL ("origin", such as "https://tw.example.com") +.br + - A client key ("client_key") identifying your tasks +.br + - An encryption secret ("encryption_secret") used to encrypt and decrypt your tasks. This can be any secret string, and must match for all replicas using the same client key. + +Tools such as +.BR pwgen (1) +can generate suitable secret values. + +Configure Taskwarrior with these details: + + $ task config sync.server.origin + $ task config sync.server.client_key + $ task config sync.server.encryption_secret + +.SS Adding a Replica + +To add a new replica, configure a new, empty replica identically to +the existing replica, and run `task sync`. + +.SS When to Synchronize + +Taskwarrior can perform a sync operation at every garbage collection (gc) run. +This is the default, and is appropriate for local synchronization. + +For synchronization to a server, a better solution is to run + + $ task sync + +periodically, such as via +.BR cron (8) . + +.SS Local Synchronization + +In order to take advantage of synchronization's side effect of saving disk +space without setting up a remote server, it is possible to sync tasks locally. +To configure local sync: + + $ task config sync.local.server_dir /path/to/sync + +The default configuration is to sync to a database in the task directory +("data.location"). + +.SH RUNNING TASKCHAMPION-SYNC-SERVER + +The Taskchampion sync server is an HTTP server supporting multiple users. +Users are identified by a client key, and users with different client keys are +entirely independent. Task data is encrypted by Taskwarrior, and the sync +server never sees un-encrypted data. + +To start the server, run it in your preferred HTTP hosting environment, using +`--port` to set the TCP port on which it should listen. It is recommended to +use TLS to protect communications with the server, but this is not required. + +The server stores its data in a database, the path to which is given by the +`--data-dir` argument, defaulting to "/var/lib/taskchampion-sync-server". + +For example: + + $ taskchampion-sync-server --port 8443 --data-dir /storage/taskdata + +.SS Adding a New User + +To add a new user to the server, invent a new client key with a tool like +`uuidgen` or an online UUID generator. There is no need to configure the server +for this new client key: the sync server will automatically create a new user +whenever presented with a new client key. Supply the key, along with the +origin, to the user for inclusion in their Taskwarrior config. The user should +invent their own "encryption_secret". + +.SH AVOIDING DUPLICATE RECURRING TASKS + +If you run multiple clients that sync to the same server, you will need to run +this command on your primary client (the one you use most often): + + $ task config recurrence on + +And on the other clients, run: + + $ task config recurrence off + +This protects you against the effects of a sync/duplication bug. + +.SH ALTERNATIVE: FILE SHARING SERVICES -.SH OPTION 2: FILE SHARING SERVICES There are many file sharing services, such as DropBox, Amazon S3, Google Drive, SkyDrive and more. This technique involves storing your .task directory in a shared directory under the control of the file hosting services. @@ -74,78 +141,6 @@ Weaknesses: - Tasks are not properly merged -.SH OPTION 3: TASKSERVER -The Taskserver was designed for this purpose to be secure, fast and conflict- -free, allowing data interchange between assorted Taskwarrior clients, and -tolerant of network connectivity problems. - -There is a 'sync' command built in to Taskwarrior (provided the GnuTLS library -is installed), and with a server account and client configuration, syncing is -done on demand. - -Setup is a matter of creating an account on a Taskserver (see your Taskserver -provider or operate your own - see -https://taskwarrior.org/docs/taskserver/setup.html) - -Once you have an account, you'll receive a certificate, key, and credentials. -You'll need to put the certificate and key somewhere like this: - - $ cp .cert.pem ~/.task - $ cp .key.pem ~/.task - -Then you configure Taskwarrior, using the provided details: - - $ task config taskd.certificate ~/.task/.cert.pem - $ task config taskd.key ~/.task/.key.pem - $ task config taskd.credentials // - $ task config taskd.server : - -If you are using a private server, you are likely also using a self-signed -certificate, which means you will need one of the following additional entries: - - $ task config taskd.ca ~/.task/ca.cert.pem - -The CA (Certificate Authority) will be used to verify the server certificate. - -After setup, you run a one-time sync initialization, like this: - - $ task sync init - -This will make sure your client and the server are properly in sync to begin -with. From this point on, you never run the 'initialize' command again, just -go about your business, and when you want to sync, run this: - - $ task sync - -You'll see a summary of how many tasks were uploaded and downloaded. You can -safely run the command as often as you like. When there are no changes to sync, -nothing happens. If you do not have connectivity, your task changes accumulate -so that when you next run 'sync' with proper connectivity, the changes are -properly handled, in the right order. - -If you run multiple clients that sync to the same server, you will need to run -this command on your primary client (the one you use most often): - - $ task config recurrence on - -And on the other clients, run: - - $ task config recurrence off - -This protects you against the effects of a sync/duplication bug. - -Strengths: -.br - - Secure communication -.br - - Minimal bandwidth -.br - - Tolerates connectivity outage - -Weaknesses: -.br - - You need to manage your own server, or gain access to a hosted server. - .SH "CREDITS & COPYRIGHTS" Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez. diff --git a/doc/man/task.1.in b/doc/man/task.1.in index 1468befe0..985efe432 100644 --- a/doc/man/task.1.in +++ b/doc/man/task.1.in @@ -538,13 +538,9 @@ Shows statistics of the tasks defined by the filter. Is affected by the context. Shows a report of aggregated task status by project. Is affected by the context. .TP -.B task sync [init] +.B task sync The sync command synchronizes data with the Taskserver, if configured. -The init subcommand should only ever be run once, and only on one client, because -it sends all data to the Taskserver. This allows all the subsequent sync commands -to only send small deltas. - Note: If you use multiple sync clients, make sure this setting (which is the default) is on your primary client: diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index e65901d80..90fc4790b 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -516,7 +516,7 @@ debug output can be useful. It can also help explain how the command line is being parsed, but the information is displayed in a developer-friendly, not a user-friendly way. -Turning debug on automatically sets debug.hooks=1, debug.parser=1 and debug.tls=2 +Turning debug on automatically sets debug.hooks=1 and debug.parser=1 if they do not already have assigned values. Defaults to "0". .TP @@ -531,11 +531,6 @@ Level 1 shows the final parse tree. Level 2 shows the parse tree from all phases of the parse. Level 3 shows expression evaluation details. -.TP -.B debug.tls=0 -Controls the GnuTLS diagnostic level. For 'sync' debugging. Level 0 means no -diagnostics. Level 9 is the highest. Level 2 is a good setting for debugging. - .TP .B obfuscate=0 When set to '1', will replace all report text with 'xxx'. @@ -1527,58 +1522,6 @@ context.home.rc.default.command=home_report These configuration settings are used to connect and sync tasks with the task server. -.TP -.B taskd.server=: -.RS -Specifies the hostname and port of the Taskserver. Hostname may be an IPv4 or -IPv6 address, or domain. Port is an integer. -.RE - -.TP -.B taskd.credentials=// -.RS -User identification for the Taskserver, which includes a private key. -.RE - -.TP -.B taskd.certificate= -.RS -Specifies the path to the client certificate used for identification with the -Taskserver. -.RE - -.TP -.B taskd.key= -.RS -Specifies the path to the client key used for encrypted communication with the -Taskserver. -.RE - -.TP -.B taskd.ca= -.RS -Specifies the path to the CA certificate in the event that your Taskserver is -using a self-signed certificate. Optional. -.RE - -.TP -.B taskd.trust=strict|ignore hostname|allow all -.RS -This settings allows you to override the trust level when server certificates -are validated. With "allow all", the server certificate is trusted -automatically. With "ignore hostname", the server certificate is verified but -the hostname is ignored. With "strict", the server certificate is verified. -Default is "strict", which requires full validation. -.RE - -.TP -.B taskd.ciphers=NORMAL -Override of the cipher selection. The set of ciphers used by TLS may be -controlled by both server and client. There must be some overlap between -client and server supported ciphers, or communication cannot occur. -Default is "NORMAL". See GnuTLS documentation for full details. -.RE - .SH "CREDITS & COPYRIGHTS" Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez. diff --git a/docs/contrib/build.md b/docs/contrib/build.md index e9c5bed60..54eb9a1bd 100644 --- a/docs/contrib/build.md +++ b/docs/contrib/build.md @@ -17,7 +17,6 @@ You'll need these tools: You'll need these libraries: -- [GnuTLS](https://www.gnutls.org/) - libuuid (unless on Darwin/BSD) Specifically the development versions, `uuid-dev` on Debian, for example. diff --git a/docs/contrib/development.md b/docs/contrib/development.md index a69deb87f..2a8aad186 100644 --- a/docs/contrib/development.md +++ b/docs/contrib/development.md @@ -6,7 +6,6 @@ title: How to Build Taskwarrior * CMake 3.0 or later * gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support * libuuid (if not on macOS) - * gnutls (optional) * python 3 (optional, for running the test suite) ## Obtain and build code: diff --git a/docs/contrib/first_time.md b/docs/contrib/first_time.md index 6bc0de210..caaa29a59 100644 --- a/docs/contrib/first_time.md +++ b/docs/contrib/first_time.md @@ -30,13 +30,11 @@ That's just a term that means you need certain tools installed before proceeding Here are the tools that Taskwarrior needs: - Compiler: GCC 4.7 or newer, or Clang 3.4 or newer. -- Libraries: GnuTLS, and libuuid - Tools: Git, CMake, make, Python The procedure for installing this software is OS-dependent, but here are the commands you would use on Debian: $ sudo apt-get install gcc - $ sudo apt-get install libgnutls28-dev $ sudo apt-get install uuid-dev $ sudo apt-get install git $ sudo apt-get install cmake diff --git a/scripts/reproduce-dockerfile b/scripts/reproduce-dockerfile index 7210b47bd..167ea2f3d 100644 --- a/scripts/reproduce-dockerfile +++ b/scripts/reproduce-dockerfile @@ -5,7 +5,7 @@ FROM centos:8 RUN dnf update -y RUN yum install epel-release -y -RUN dnf install python38 vim git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime sudo man gdb -y +RUN dnf install python38 vim git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man gdb -y RUN useradd warrior RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior diff --git a/scripts/review-dockerfile b/scripts/review-dockerfile index 6fc560e38..862b389a5 100644 --- a/scripts/review-dockerfile +++ b/scripts/review-dockerfile @@ -9,7 +9,7 @@ RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org| RUN dnf update -y RUN yum install epel-release -y -RUN dnf install python38 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime sudo man -y +RUN dnf install python38 git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man -y RUN useradd warrior RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior diff --git a/scripts/vim/syntax/taskrc.vim b/scripts/vim/syntax/taskrc.vim index 980c7c7b5..c65b463b0 100644 --- a/scripts/vim/syntax/taskrc.vim +++ b/scripts/vim/syntax/taskrc.vim @@ -112,7 +112,6 @@ syn match taskrcGoodKey '^\s*\Vdateformat.report='he=e-1 syn match taskrcGoodKey '^\s*\Vdebug='he=e-1 syn match taskrcGoodKey '^\s*\Vdebug.hooks='he=e-1 syn match taskrcGoodKey '^\s*\Vdebug.parser='he=e-1 -syn match taskrcGoodKey '^\s*\Vdebug.tls='he=e-1 syn match taskrcGoodKey '^\s*\Vdefault.command='he=e-1 syn match taskrcGoodKey '^\s*\Vdefault.due='he=e-1 syn match taskrcGoodKey '^\s*\Vdefault.priority='he=e-1 @@ -164,8 +163,8 @@ syn match taskrcGoodKey '^\s*\Vrule.precedence.color='he=e-1 syn match taskrcGoodKey '^\s*\Vsearch.case.sensitive='he=e-1 syn match taskrcGoodKey '^\s*\Vsummary.all.projects='he=e-1 syn match taskrcGoodKey '^\s*\Vsugar='he=e-1 +syn match taskrcGoodKey '^\s*\Vsync.\(server.\(origin\|client_key\|encryption_secret\)\|local.server_dir\)='he=e-1 syn match taskrcGoodKey '^\s*\Vtag.indicator='he=e-1 -syn match taskrcGoodKey '^\s*\Vtaskd.\(server\|credentials\|certificate\|key\|ca\|trust\|ciphers\)='he=e-1 syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\|indicator\)='he=e-1 syn match taskrcGoodKey '^\s*\Vundo.style='he=e-1 syn match taskrcGoodKey '^\s*\Vurgency.active.coefficient='he=e-1 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67647e1bf..c126b6e0a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,6 @@ add_library (task STATIC CLI2.cpp CLI2.h Lexer.cpp Lexer.h TDB2.cpp TDB2.h Task.cpp Task.h - TLSClient.cpp TLSClient.h Variant.cpp Variant.h ViewTask.cpp ViewTask.h dependency.cpp @@ -52,9 +51,10 @@ add_executable (calc_executable calc.cpp) add_executable (lex_executable lex.cpp) # Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes. -target_link_libraries (task_executable task tc tc-rust commands columns libshared task libshared ${TASK_LIBRARIES}) -target_link_libraries (calc_executable task tc tc-rust commands columns libshared task libshared ${TASK_LIBRARIES}) -target_link_libraries (lex_executable task tc tc-rust commands columns libshared task libshared ${TASK_LIBRARIES}) +# Similarly for `tc`. +target_link_libraries (task_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES}) +target_link_libraries (calc_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES}) +target_link_libraries (lex_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES}) set_property (TARGET task_executable PROPERTY OUTPUT_NAME "task") diff --git a/src/Context.cpp b/src/Context.cpp index 97e55d2bc..c9614e8f5 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -272,21 +272,17 @@ std::string configurationDefaults = "list.all.tags=0 # Include old tag names in 'tags' command\n" "print.empty.columns=0 # Print columns which have no data for any task\n" "debug=0 # Display diagnostics\n" - "debug.tls=0 # Sync diagnostics\n" "sugar=1 # Syntactic sugar\n" "obfuscate=0 # Obfuscate data for error reporting\n" "fontunderline=1 # Uses underlines rather than -------\n" "\n" "# WARNING: Please read the documentation (man task-sync) before setting up\n" "# Taskwarrior for Taskserver synchronization.\n" - "#taskd.ca=\n" - "#taskd.certificate=\n" - "#taskd.credentials=//\n" - "#taskd.server=:\n" - "taskd.trust=strict\n" - "#taskd.trust=ignore hostname\n" - "#taskd.trust=allow all\n" - "taskd.ciphers=NORMAL\n" + "\n" + "#sync.server.client_key # Client key for sync to a server\n" + "#sync.server.encryption_secret # Encryption secret for sync to a server\n" + "#sync.server.origin # Origin of the sync server\n" + "#sync.local.server_dir # Directory for local sync\n" "\n" "# Aliases - alternate names for commands\n" "alias.rm=delete # Alias for the delete command\n" @@ -1354,16 +1350,13 @@ void Context::loadAliases () } //////////////////////////////////////////////////////////////////////////////// -// Using the general rc.debug setting automaticalls sets debug.tls, debug.hooks +// Using the general rc.debug setting automaticalls sets debug.hooks // and debug.parser, unless they already have values, which by default they do // not. void Context::propagateDebug () { if (config.getBoolean ("debug")) { - if (! config.has ("debug.tls")) - config.set ("debug.tls", 2); - if (! config.has ("debug.hooks")) config.set ("debug.hooks", 1); diff --git a/src/TDB2.cpp b/src/TDB2.cpp index 4411475f5..8f736db52 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -41,6 +41,7 @@ #include #include #include +#include "tc/Server.h" bool TDB2::debug_mode = false; static void dependency_scan (std::vector &); @@ -469,6 +470,12 @@ int TDB2::num_reverts_possible () return (int)replica.num_undo_points (); } +//////////////////////////////////////////////////////////////////////////////// +void TDB2::sync (tc::Server server, bool avoid_snapshots) +{ + replica.sync(std::move(server), avoid_snapshots); +} + //////////////////////////////////////////////////////////////////////////////// void TDB2::dump () { diff --git a/src/TDB2.h b/src/TDB2.h index 20b99d6be..30cfffff1 100644 --- a/src/TDB2.h +++ b/src/TDB2.h @@ -38,6 +38,10 @@ #include #include +namespace tc { +class Server; +} + // TDB2 Class represents all the files in the task database. class TDB2 { @@ -73,6 +77,8 @@ public: void dump (); + void sync (tc::Server server, bool avoid_snapshots); + private: tc::Replica replica; std::optional _working_set; diff --git a/src/TLSClient.cpp b/src/TLSClient.cpp deleted file mode 100644 index 7c9598c97..000000000 --- a/src/TLSClient.cpp +++ /dev/null @@ -1,576 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include - -#ifdef HAVE_LIBGNUTLS - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define HEADER_SIZE 4 -#define MAX_BUF 16384 - -#if GNUTLS_VERSION_NUMBER < 0x030406 -#if GNUTLS_VERSION_NUMBER >= 0x020a00 -static int verify_certificate_callback (gnutls_session_t); -#endif -#endif - -//////////////////////////////////////////////////////////////////////////////// -static void gnutls_log_function (int level, const char* message) -{ - std::cout << "c: " << level << ' ' << message; -} - -//////////////////////////////////////////////////////////////////////////////// -#if GNUTLS_VERSION_NUMBER < 0x030406 -#if GNUTLS_VERSION_NUMBER >= 0x020a00 -static int verify_certificate_callback (gnutls_session_t session) -{ - const TLSClient* client = (TLSClient*) gnutls_session_get_ptr (session); // All - return client->verify_certificate (); -} -#endif -#endif - -//////////////////////////////////////////////////////////////////////////////// -TLSClient::~TLSClient () -{ - gnutls_deinit (_session); // All - gnutls_certificate_free_credentials (_credentials); // All -#if GNUTLS_VERSION_NUMBER < 0x030300 - gnutls_global_deinit (); // All -#endif - - if (_socket) - { - shutdown (_socket, SHUT_RDWR); - close (_socket); - } -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::limit (int max) -{ - _limit = max; -} - -//////////////////////////////////////////////////////////////////////////////// -// Calling this method results in all subsequent socket traffic being sent to -// std::cout, labelled with 'c: ...'. -void TLSClient::debug (int level) -{ - if (level) - _debug = true; - - gnutls_global_set_log_function (gnutls_log_function); // All - gnutls_global_set_log_level (level); // All -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::trust (const enum trust_level value) -{ - _trust = value; - if (_debug) - { - if (_trust == allow_all) - std::cout << "c: INFO Server certificate will be trusted automatically.\n"; - else if (_trust == ignore_hostname) - std::cout << "c: INFO Server certificate will be verified but hostname ignored.\n"; - else - std::cout << "c: INFO Server certificate will be verified.\n"; - } -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::ciphers (const std::string& cipher_list) -{ - _ciphers = cipher_list; -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::init ( - const std::string& ca, - const std::string& cert, - const std::string& key) -{ - _ca = ca; - _cert = cert; - _key = key; - - int ret; -#if GNUTLS_VERSION_NUMBER < 0x030300 - ret = gnutls_global_init (); // All - if (ret < 0) - throw format ("TLS init error. {1}", gnutls_strerror (ret)); // All -#endif - - ret = gnutls_certificate_allocate_credentials (&_credentials); // All - if (ret < 0) - throw format ("TLS allocation error. {1}", gnutls_strerror (ret)); // All - -#if GNUTLS_VERSION_NUMBER >= 0x030014 - // Automatic loading of system installed CA certificates. - ret = gnutls_certificate_set_x509_system_trust (_credentials); // 3.0.20 - if (ret < 0) - throw format ("Bad System Trust. {1}", gnutls_strerror (ret)); // All -#endif - - if (_ca != "") - { - // The gnutls_certificate_set_x509_key_file call returns number of - // certificates parsed on success (including 0, when no certificate was - // found) and negative values on error - ret = gnutls_certificate_set_x509_trust_file (_credentials, _ca.c_str (), GNUTLS_X509_FMT_PEM); // All - if (ret == 0) - throw format ("CA file {1} contains no certificate.", _ca); - else if (ret < 0) - throw format ("Bad CA file: {1}", gnutls_strerror (ret)); // All - - } - - // TODO This may need 0x030111 protection. - if (_cert != "" && - _key != "" && - (ret = gnutls_certificate_set_x509_key_file (_credentials, _cert.c_str (), _key.c_str (), GNUTLS_X509_FMT_PEM)) < 0) // 3.1.11 - throw format ("Bad client CERT/KEY file. {1}", gnutls_strerror (ret)); // All - -#if GNUTLS_VERSION_NUMBER < 0x030406 -#if GNUTLS_VERSION_NUMBER >= 0x020a00 - // The automatic verification for the server certificate with - // gnutls_certificate_set_verify_function only works with gnutls - // >=2.9.10. So with older versions we should call the verify function - // manually after the gnutls handshake. - gnutls_certificate_set_verify_function (_credentials, verify_certificate_callback); // 2.10.0 -#endif -#endif - ret = gnutls_init (&_session, GNUTLS_CLIENT); // All - if (ret < 0) - throw format ("TLS client init error. {1}", gnutls_strerror (ret)); // All - - // Use default priorities unless overridden. - if (_ciphers == "") - _ciphers = "NORMAL"; - - const char *err; - ret = gnutls_priority_set_direct (_session, _ciphers.c_str (), &err); // All - if (ret < 0) - { - if (_debug && ret == GNUTLS_E_INVALID_REQUEST) - std::cout << "c: ERROR Priority error at: " << err << '\n'; - - throw format ("Error initializing TLS. {1}", gnutls_strerror (ret)); // All - } - - // Apply the x509 credentials to the current session. - ret = gnutls_credentials_set (_session, GNUTLS_CRD_CERTIFICATE, _credentials); // All - if (ret < 0) - throw format ("TLS credentials error. {1}", gnutls_strerror (ret)); // All -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::connect (const std::string& host, const std::string& port) -{ - _host = host; - _port = port; - - int ret; -#if GNUTLS_VERSION_NUMBER >= 0x030406 - // For _trust == TLSClient::allow_all we perform no action - if (_trust == TLSClient::ignore_hostname) - gnutls_session_set_verify_cert (_session, nullptr, 0); // 3.4.6 - else if (_trust == TLSClient::strict) - gnutls_session_set_verify_cert (_session, _host.c_str (), 0); // 3.4.6 -#endif - - // SNI. Only permitted when _host is a DNS name, not an IPv4/6 address. - std::string dummyAddress; - int dummyPort; - if (! isIPv4Address (_host, dummyAddress, dummyPort) && - ! isIPv6Address (_host, dummyAddress, dummyPort)) - { - ret = gnutls_server_name_set (_session, GNUTLS_NAME_DNS, _host.c_str (), _host.length ()); // All - if (ret < 0) - throw format ("TLS SNI error. {1}", gnutls_strerror (ret)); // All - } - - // Store the TLSClient instance, so that the verification callback can access - // it during the handshake below and call the verification method. - gnutls_session_set_ptr (_session, (void*) this); // All - - // use IPv4 or IPv6, does not matter. - struct addrinfo hints {}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; // use my IP - - struct addrinfo* res; - ret = ::getaddrinfo (host.c_str (), port.c_str (), &hints, &res); - if (ret != 0) - throw std::string (::gai_strerror (ret)); - - // Try them all, stop on success. - struct addrinfo* p; - for (p = res; p != nullptr; p = p->ai_next) - { - if ((_socket = ::socket (p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) - continue; - - // When a socket is closed, it remains unavailable for a while (netstat -an). - // Setting SO_REUSEADDR allows this program to assume control of a closed, - // but unavailable socket. - int on = 1; - if (::setsockopt (_socket, - SOL_SOCKET, - SO_REUSEADDR, - (const void*) &on, - sizeof (on)) == -1) - throw std::string (::strerror (errno)); - - if (::connect (_socket, p->ai_addr, p->ai_addrlen) == -1) - continue; - - break; - } - - free (res); - - if (p == nullptr) - throw format ("Could not connect to {1} {2}", host, port); - -#if GNUTLS_VERSION_NUMBER >= 0x030100 - gnutls_handshake_set_timeout (_session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); // 3.1.0 -#endif - -#if GNUTLS_VERSION_NUMBER >= 0x030109 - gnutls_transport_set_int (_session, _socket); // 3.1.9 -#else - gnutls_transport_set_ptr (_session, (gnutls_transport_ptr_t) (intptr_t) _socket); // All -#endif - - // Perform the TLS handshake - do - { - ret = gnutls_handshake (_session); // All - } - while (ret < 0 && gnutls_error_is_fatal (ret) == 0); // All - - if (ret < 0) - { -#if GNUTLS_VERSION_NUMBER >= 0x030406 - if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) - { - auto type = gnutls_certificate_type_get (_session); // All - auto status = gnutls_session_get_verify_cert_status (_session); // 3.4.6 - gnutls_datum_t out; - gnutls_certificate_verification_status_print (status, type, &out, 0); // 3.1.4 - - std::string error {(const char*) out.data}; - gnutls_free (out.data); // All - - throw format ("Handshake failed. {1}", error); // All - } -#else - throw format ("Handshake failed. {1}", gnutls_strerror (ret)); // All -#endif - } - -#if GNUTLS_VERSION_NUMBER < 0x020a00 - // The automatic verification for the server certificate with - // gnutls_certificate_set_verify_function does only work with gnutls - // >=2.10.0. So with older versions we should call the verify function - // manually after the gnutls handshake. - ret = verify_certificate (); - if (ret < 0) - { - if (_debug) - std::cout << "c: ERROR Certificate verification failed.\n"; - throw format ("Error initializing TLS. {1}", gnutls_strerror (ret)); // All - } -#endif - - if (_debug) - { -#if GNUTLS_VERSION_NUMBER >= 0x03010a - char* desc = gnutls_session_get_desc (_session); // 3.1.10 - std::cout << "c: INFO Handshake was completed: " << desc << '\n'; - gnutls_free (desc); -#else - std::cout << "c: INFO Handshake was completed.\n"; -#endif - } -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::bye () -{ - gnutls_bye (_session, GNUTLS_SHUT_RDWR); // All -} - -//////////////////////////////////////////////////////////////////////////////// -int TLSClient::verify_certificate () const -{ - if (_trust == TLSClient::allow_all) - return 0; - - if (_debug) - std::cout << "c: INFO Verifying certificate.\n"; - - // This verification function uses the trusted CAs in the credentials - // structure. So you must have installed one or more CA certificates. - unsigned int status = 0; - const char* hostname = _host.c_str(); -#if GNUTLS_VERSION_NUMBER >= 0x030104 - if (_trust == TLSClient::ignore_hostname) - hostname = nullptr; - - int ret = gnutls_certificate_verify_peers3 (_session, hostname, &status); // 3.1.4 - if (ret < 0) - { - if (_debug) - std::cout << "c: ERROR Certificate verification peers3 failed. " << gnutls_strerror (ret) << '\n'; // All - return GNUTLS_E_CERTIFICATE_ERROR; - } - - // status 16450 == 0100000001000010 - // GNUTLS_CERT_INVALID 1<<1 - // GNUTLS_CERT_SIGNER_NOT_FOUND 1<<6 - // GNUTLS_CERT_UNEXPECTED_OWNER 1<<14 Hostname does not match - - if (_debug && status) - std::cout << "c: ERROR Certificate status=" << status << '\n'; -#else - int ret = gnutls_certificate_verify_peers2 (_session, &status); // All - if (ret < 0) - { - if (_debug) - std::cout << "c: ERROR Certificate verification peers2 failed. " << gnutls_strerror (ret) << '\n'; // All - return GNUTLS_E_CERTIFICATE_ERROR; - } - - if (_debug && status) - std::cout << "c: ERROR Certificate status=" << status << '\n'; - - if ((status == 0) && (_trust != TLSClient::ignore_hostname)) - { - if (gnutls_certificate_type_get (_session) == GNUTLS_CRT_X509) // All - { - const gnutls_datum* cert_list; - unsigned int cert_list_size; - gnutls_x509_crt cert; - - cert_list = gnutls_certificate_get_peers (_session, &cert_list_size); // All - if (cert_list_size == 0) - { - if (_debug) - std::cout << "c: ERROR Certificate get peers failed. " << gnutls_strerror (ret) << '\n'; // All - return GNUTLS_E_CERTIFICATE_ERROR; - } - - ret = gnutls_x509_crt_init (&cert); // All - if (ret < 0) - { - if (_debug) - std::cout << "c: ERROR x509 init failed. " << gnutls_strerror (ret) << '\n'; // All - return GNUTLS_E_CERTIFICATE_ERROR; - } - - ret = gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER); // All - if (ret < 0) - { - if (_debug) - std::cout << "c: ERROR x509 cert import. " << gnutls_strerror (ret) << '\n'; // All - gnutls_x509_crt_deinit(cert); // All - return GNUTLS_E_CERTIFICATE_ERROR; - } - - if (gnutls_x509_crt_check_hostname (cert, hostname) == 0) // All - { - if (_debug) - std::cout << "c: ERROR x509 cert check hostname. " << gnutls_strerror (ret) << '\n'; // All - gnutls_x509_crt_deinit(cert); - return GNUTLS_E_CERTIFICATE_ERROR; - } - } - else - return GNUTLS_E_CERTIFICATE_ERROR; - } -#endif - -#if GNUTLS_VERSION_NUMBER >= 0x030104 - gnutls_certificate_type_t type = gnutls_certificate_type_get (_session); // All - gnutls_datum_t out; - ret = gnutls_certificate_verification_status_print (status, type, &out, 0); // 3.1.4 - if (ret < 0) - { - if (_debug) - std::cout << "c: ERROR certificate verification status. " << gnutls_strerror (ret) << '\n'; // All - return GNUTLS_E_CERTIFICATE_ERROR; - } - - if (_debug) - std::cout << "c: INFO " << out.data << '\n'; - gnutls_free (out.data); -#endif - - if (status != 0) - return GNUTLS_E_CERTIFICATE_ERROR; - - // Continue handshake. - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::send (const std::string& data) -{ - std::string packet = "XXXX" + data; - - // Encode the length. - unsigned long l = packet.length (); - packet[0] = l >>24; - packet[1] = l >>16; - packet[2] = l >>8; - packet[3] = l; - - unsigned int total = 0; - - int status; - do - { - status = gnutls_record_send (_session, packet.c_str () + total, packet.length () - total); // All - } - while ((status > 0 && (total += status) < packet.length ()) || - status == GNUTLS_E_INTERRUPTED || - status == GNUTLS_E_AGAIN); - - if (_debug) - std::cout << "c: INFO Sending 'XXXX" - << data.c_str () - << "' (" << total << " bytes)" - << std::endl; -} - -//////////////////////////////////////////////////////////////////////////////// -void TLSClient::recv (std::string& data) -{ - data = ""; // No appending of data. - int received = 0; - int total = 0; - - // Get the encoded length. - unsigned char header[HEADER_SIZE] {}; - do - { - received = gnutls_record_recv (_session, header + total, HEADER_SIZE - total); // All - } - while ((received > 0 && (total += received) < HEADER_SIZE) || - received == GNUTLS_E_INTERRUPTED || - received == GNUTLS_E_AGAIN); - - if (total < HEADER_SIZE) { - throw std::string ("Failed to receive header: ") + - (received < 0 ? gnutls_strerror(received) : "connection lost?"); - } - - // Decode the length. - unsigned long expected = (header[0]<<24) | - (header[1]<<16) | - (header[2]<<8) | - header[3]; - if (_debug) - std::cout << "c: INFO expecting " << expected << " bytes.\n"; - - if (_limit && expected >= (unsigned long) _limit) { - std::ostringstream err_str; - err_str << "Expected message size " << expected << " is larger than allowed limit " << _limit; - throw err_str.str (); - } - - // Arbitrary buffer size. - char buffer[MAX_BUF]; - - // Keep reading until no more data. Concatenate chunks of data if a) the - // read was interrupted by a signal, and b) if there is more data than - // fits in the buffer. - do - { - int chunk_size = 0; - do - { - received = gnutls_record_recv (_session, buffer + chunk_size, MAX_BUF - chunk_size); // All - if (received > 0) { - total += received; - chunk_size += received; - } - } - while ((received > 0 && (unsigned long) total < expected && chunk_size < MAX_BUF) || - received == GNUTLS_E_INTERRUPTED || - received == GNUTLS_E_AGAIN); - - // Other end closed the connection. - if (received == 0) - { - if (_debug) - std::cout << "c: INFO Peer has closed the TLS connection\n"; - break; - } - - // Something happened. - if (received < 0) - throw std::string (gnutls_strerror (received)); // All - - data.append (buffer, chunk_size); - - // Stop at defined limit. - if (_limit && total > _limit) - break; - } - while (received > 0 && total < (int) expected); - - if (_debug) - std::cout << "c: INFO Receiving 'XXXX" - << data.c_str () - << "' (" << total << " bytes)" - << std::endl; -} - -//////////////////////////////////////////////////////////////////////////////// -#endif diff --git a/src/TLSClient.h b/src/TLSClient.h deleted file mode 100644 index d30e9a674..000000000 --- a/src/TLSClient.h +++ /dev/null @@ -1,72 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDED_TLSCLIENT -#define INCLUDED_TLSCLIENT - -#ifdef HAVE_LIBGNUTLS - -#include -#include - -class TLSClient -{ -public: - enum trust_level { strict, ignore_hostname, allow_all }; - - TLSClient () = default; - ~TLSClient (); - void limit (int); - void debug (int); - void trust (const enum trust_level); - void ciphers (const std::string&); - void init (const std::string&, const std::string&, const std::string&); - void connect (const std::string&, const std::string&); - void bye (); - int verify_certificate() const; - - void send (const std::string&); - void recv (std::string&); - -private: - std::string _ca {""}; - std::string _cert {""}; - std::string _key {""}; - std::string _ciphers {""}; - std::string _host {""}; - std::string _port {""}; - gnutls_certificate_credentials_t _credentials {}; - gnutls_session_t _session {nullptr}; - int _socket {0}; - int _limit {0}; - bool _debug {false}; - enum trust_level _trust {strict}; -}; - -#endif -#endif - -//////////////////////////////////////////////////////////////////////////////// - diff --git a/src/commands/CmdDiagnostics.cpp b/src/commands/CmdDiagnostics.cpp index caf2c5a9e..6d274bfc3 100644 --- a/src/commands/CmdDiagnostics.cpp +++ b/src/commands/CmdDiagnostics.cpp @@ -39,10 +39,6 @@ #include #endif -#ifdef HAVE_LIBGNUTLS -#include -#endif - //////////////////////////////////////////////////////////////////////////////// CmdDiagnostics::CmdDiagnostics () { @@ -135,18 +131,6 @@ int CmdDiagnostics::execute (std::string& output) #endif << '\n'; - out << " libgnutls: " -#ifdef HAVE_LIBGNUTLS -#ifdef GNUTLS_VERSION - << GNUTLS_VERSION -#elif defined LIBGNUTLS_VERSION - << LIBGNUTLS_VERSION -#endif -#else - << "n/a" -#endif - << '\n'; - out << " Build type: " #ifdef CMAKE_BUILD_TYPE << CMAKE_BUILD_TYPE @@ -214,82 +198,6 @@ int CmdDiagnostics::execute (std::string& output) else if ((peditor = getenv ("EDITOR")) != nullptr) out << " $EDITOR: " << peditor << '\n'; - out << " Server: " - << Context::getContext ().config.get ("taskd.server") - << '\n'; - - auto ca_pem = Context::getContext ().config.get ("taskd.ca"); - out << " CA: "; - if (ca_pem != "") - { - File file_ca (ca_pem); - if (file_ca.exists ()) - out << ca_pem - << (file_ca.readable () ? ", readable, " : ", not readable, ") - << file_ca.size () - << " bytes\n"; - else - out << "not found\n"; - } - else - out << "-\n"; - - auto cert_pem = Context::getContext ().config.get ("taskd.certificate"); - out << "Certificate: "; - if (cert_pem != "") - { - File file_cert (cert_pem); - if (file_cert.exists ()) - out << cert_pem - << (file_cert.readable () ? ", readable, " : ", not readable, ") - << file_cert.size () - << " bytes\n"; - else - out << "not found\n"; - } - else - out << "-\n"; - - auto key_pem = Context::getContext ().config.get ("taskd.key"); - out << " Key: "; - if (key_pem != "") - { - File file_key (key_pem); - if (file_key.exists ()) - out << key_pem - << (file_key.readable () ? ", readable, " : ", not readable, ") - << file_key.size () - << " bytes\n"; - else - out << "not found\n"; - } - else - out << "-\n"; - - auto trust_value = Context::getContext ().config.get ("taskd.trust"); - if (trust_value == "strict" || - trust_value == "ignore hostname" || - trust_value == "allow all") - out << " Trust: " << trust_value << '\n'; - else - out << " Trust: Bad value - see 'man taskrc'\n"; - - out << " Ciphers: " - << Context::getContext ().config.get ("taskd.ciphers") - << '\n'; - - // Get credentials, but mask out the key. - auto credentials = Context::getContext ().config.get ("taskd.credentials"); - auto last_slash = credentials.rfind ('/'); - if (last_slash != std::string::npos) - credentials = credentials.substr (0, last_slash) - + '/' - + std::string (credentials.length () - last_slash - 1, '*'); - - out << " Creds: " - << credentials - << "\n\n"; - // Display hook status. Path hookLocation; if (Context::getContext ().config.has ("hooks.location")) diff --git a/src/commands/CmdShow.cpp b/src/commands/CmdShow.cpp index 0098915dd..9625a5d68 100644 --- a/src/commands/CmdShow.cpp +++ b/src/commands/CmdShow.cpp @@ -145,7 +145,6 @@ int CmdShow::execute (std::string& output) " debug" " debug.hooks" " debug.parser" - " debug.tls" " default.command" " default.due" " default.project" @@ -193,14 +192,11 @@ int CmdShow::execute (std::string& output) " search.case.sensitive" " sugar" " summary.all.projects" + " sync.local.server_dir" + " sync.server.client_key" + " sync.server.encryption_secret" + " sync.server.origin" " tag.indicator" - " taskd.server" - " taskd.ca" - " taskd.certificate" - " taskd.ciphers" - " taskd.credentials" - " taskd.key" - " taskd.trust" " undo.style" " urgency.active.coefficient" " urgency.scheduled.coefficient" diff --git a/src/commands/CmdSync.cpp b/src/commands/CmdSync.cpp index c430e3f74..b72aae4d0 100644 --- a/src/commands/CmdSync.cpp +++ b/src/commands/CmdSync.cpp @@ -35,6 +35,7 @@ #include #include #include +#include "tc/Server.h" //////////////////////////////////////////////////////////////////////////////// CmdSync::CmdSync () @@ -53,10 +54,37 @@ CmdSync::CmdSync () } //////////////////////////////////////////////////////////////////////////////// -int CmdSync::execute (std::string&) +int CmdSync::execute (std::string& output) { - throw std::string ("Sync support is disabled during transition to TaskChampion."); - return 0; + int status = 0; + + tc::Server server; + std::string server_ident; + + // If no server is set up, quit. + std::string origin = Context::getContext ().config.get ("sync.server.origin"); + std::string client_key = Context::getContext ().config.get ("sync.server.client_key"); + std::string encryption_secret = Context::getContext ().config.get ("sync.server.encryption_secret"); + std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir"); + if (server_dir != "") { + server = tc::Server (server_dir); + server_ident = server_dir; + } else if (origin != "" && client_key != "" && encryption_secret != "") { + server = tc::Server (origin, client_key, encryption_secret); + server_ident = origin; + } else { + throw std::string ("Neither sync.server nor sync.local are configured."); + } + + std::stringstream out; + if (Context::getContext ().verbose ("sync")) + out << format ("Syncing with {1}", server_ident) + << '\n'; + + Context::getContext ().tdb2.sync(std::move(server), false); + + output = out.str (); + return status; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdSync.h b/src/commands/CmdSync.h index 40d467219..b823a04f9 100644 --- a/src/commands/CmdSync.h +++ b/src/commands/CmdSync.h @@ -30,18 +30,12 @@ #include #include #include -#include class CmdSync : public Command { public: CmdSync (); int execute (std::string&); - -#ifdef HAVE_LIBGNUTLS -private: - bool send (const std::string&, const std::string&, const std::string&, const std::string&, const enum TLSClient::trust_level, const Msg&, Msg&); -#endif }; #endif diff --git a/src/feedback.cpp b/src/feedback.cpp index 3464e9d1e..a7a3d54d0 100644 --- a/src/feedback.cpp +++ b/src/feedback.cpp @@ -207,8 +207,7 @@ void feedback_unblocked (const Task& task) /////////////////////////////////////////////////////////////////////////////// void feedback_backlog () { - if (Context::getContext ().config.get ("taskd.server") != "" && - Context::getContext ().verbose ("sync")) + if (Context::getContext ().verbose ("sync")) { int count = Context::getContext ().tdb2.num_local_changes (); if (count) diff --git a/src/tc/CMakeLists.txt b/src/tc/CMakeLists.txt index 3b0504f82..d4704984f 100644 --- a/src/tc/CMakeLists.txt +++ b/src/tc/CMakeLists.txt @@ -10,6 +10,7 @@ set (tc_SRCS ffi.h util.cpp util.h Replica.cpp Replica.h + Server.cpp Server.h WorkingSet.cpp WorkingSet.h Task.cpp Task.h) diff --git a/src/tc/Replica.cpp b/src/tc/Replica.cpp index 5ce91f8c3..b4a0b3b4d 100644 --- a/src/tc/Replica.cpp +++ b/src/tc/Replica.cpp @@ -28,6 +28,7 @@ #include #include "tc/Replica.h" #include "tc/Task.h" +#include "tc/Server.h" #include "tc/WorkingSet.h" #include "tc/util.h" @@ -142,6 +143,16 @@ tc::Task tc::Replica::import_task_with_uuid (const std::string &uuid) return Task (tctask); } +//////////////////////////////////////////////////////////////////////////////// +void tc::Replica::sync (Server server, bool avoid_snapshots) +{ + // The server remains owned by this function, per tc_replica_sync docs. + auto res = tc_replica_sync (&*inner, server.inner.get(), avoid_snapshots); + if (res != TC_RESULT_OK) { + throw replica_error (); + } +} + //////////////////////////////////////////////////////////////////////////////// void tc::Replica::undo (int32_t *undone_out) { diff --git a/src/tc/Replica.h b/src/tc/Replica.h index c0563d443..041f2bd62 100644 --- a/src/tc/Replica.h +++ b/src/tc/Replica.h @@ -38,6 +38,7 @@ namespace tc { class Task; class WorkingSet; + class Server; // a unique_ptr to a TCReplica which will automatically free the value when // it goes out of scope. @@ -91,7 +92,7 @@ namespace tc { tc::Task new_task (Status status, const std::string &description); tc::Task import_task_with_uuid (const std::string &uuid); // TODO: struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); -// TODO: TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); + void sync(Server server, bool avoid_snapshots); void undo (int32_t *undone_out); int64_t num_local_operations (); int64_t num_undo_points (); diff --git a/src/tc/Server.cpp b/src/tc/Server.cpp new file mode 100644 index 000000000..5b1346838 --- /dev/null +++ b/src/tc/Server.cpp @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022, Dustin J. Mitchell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "tc/Server.h" +#include "tc/util.h" + +using namespace tc::ffi; + +//////////////////////////////////////////////////////////////////////////////// +tc::Server::Server (const std::string &server_dir) +{ + TCString tc_server_dir = tc_string_borrow (server_dir.c_str ()); + TCString error; + auto tcserver = tc_server_new_local (tc_server_dir, &error); + if (!tcserver) { + auto errmsg = format ("Could not configure local server at {1}: {2}", + server_dir, tc_string_content (&error)); + tc_string_free (&error); + throw errmsg; + } + inner = unique_tcserver_ptr ( + tcserver, + [](TCServer* rep) { tc_server_free (rep); }); +} + +//////////////////////////////////////////////////////////////////////////////// +tc::Server::Server (const std::string &origin, const std::string &client_key, const std::string &encryption_secret) +{ + TCString tc_origin = tc_string_borrow (origin.c_str ()); + + TCString tc_client_key_str = tc_string_borrow (client_key.c_str ()); + + TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ()); + + TCUuid tc_client_key; + if (tc_uuid_from_str(tc_client_key_str, &tc_client_key) != TC_RESULT_OK) { + tc_string_free(&tc_origin); + tc_string_free(&tc_encryption_secret); + throw "client_key must be a valid UUID"; + } + + TCString error; + auto tcserver = tc_server_new_remote (tc_origin, tc_client_key, tc_encryption_secret, &error); + if (!tcserver) { + auto errmsg = format ("Could not configure connection to server at {1}: {2}", + origin, tc_string_content (&error)); + tc_string_free (&error); + throw errmsg; + } + inner = unique_tcserver_ptr ( + tcserver, + [](TCServer* rep) { tc_server_free (rep); }); +} + +//////////////////////////////////////////////////////////////////////////////// +tc::Server::Server (tc::Server &&other) noexcept +{ + // move inner from other + inner = unique_tcserver_ptr ( + other.inner.release (), + [](TCServer* rep) { tc_server_free (rep); }); +} + +//////////////////////////////////////////////////////////////////////////////// +tc::Server& tc::Server::operator= (tc::Server &&other) noexcept +{ + if (this != &other) { + // move inner from other + inner = unique_tcserver_ptr ( + other.inner.release (), + [](TCServer* rep) { tc_server_free (rep); }); + } + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/Server.h b/src/tc/Server.h new file mode 100644 index 000000000..a29f581a8 --- /dev/null +++ b/src/tc/Server.h @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022, Dustin J. Mitchell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_TC_SERVER +#define INCLUDED_TC_SERVER + +#include +#include +#include +#include +#include +#include "tc/ffi.h" + +namespace tc { + // a unique_ptr to a TCServer which will automatically free the value when + // it goes out of scope. + using unique_tcserver_ptr = std::unique_ptr< + tc::ffi::TCServer, + std::function>; + + // Server wraps the TCServer type, managing its memory, errors, and so on. + // + // Except as noted, method names match the suffix to `tc_replica_..`. + class Server + { + public: + // Construct a null server + Server () = default; + + // Construct a local server (tc_server_new_local). + Server (const std::string& server_dir); + + // Construct a remote server (tc_server_new_remote). + Server (const std::string &origin, const std::string &client_key, const std::string &encryption_secret); + + // This object "owns" inner, so copy is not allowed. + Server (const Server &) = delete; + Server &operator=(const Server &) = delete; + + // Explicit move constructor and assignment + Server (Server &&) noexcept; + Server &operator=(Server &&) noexcept; + + protected: + unique_tcserver_ptr inner; + + // Replica accesses the inner pointer to call tc_replica_sync + friend class Replica; + + // construct an error message from the given string. + std::string server_error (tc::ffi::TCString string); + }; +} + + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/rust/Cargo.lock b/src/tc/rust/Cargo.lock index cfe52a03d..6d0535ce6 100644 --- a/src/tc/rust/Cargo.lock +++ b/src/tc/rust/Cargo.lock @@ -767,9 +767,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.3.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" +checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ "getrandom", "serde", diff --git a/test/basetest/README b/test/basetest/README index e351f568b..0ff5c632d 100644 --- a/test/basetest/README +++ b/test/basetest/README @@ -1,9 +1,5 @@ Shell environment variables that affect how and what tests are executed: TASKW_SKIP -> Causes any test that needs Taskwarrior (task binary only) to be skipped (TestCase) -TASKD_SKIP -> Causes any test that needs Task Server (taskd) to be skipped (ServerTestCase) - -# NOTE: Tests that use both "task" and "taskd" (ServerTestCase) are not skipped when TASKW_SKIP is set. TASK_USE_PATH -> Causes tests to look for "task" in PATH instead of the default location -TASKD_USE_PATH -> Causes tests to look for "taskd" in PATH instead of the default location diff --git a/test/basetest/__init__.py b/test/basetest/__init__.py index a3934330d..08aa9ac92 100644 --- a/test/basetest/__init__.py +++ b/test/basetest/__init__.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- from .task import Task -from .taskd import Taskd -from .testing import TestCase, ServerTestCase +from .testing import TestCase # flake8:noqa # vim: ai sts=4 et sw=4 diff --git a/test/basetest/task.py b/test/basetest/task.py index dba76862c..f6afc9fea 100644 --- a/test/basetest/task.py +++ b/test/basetest/task.py @@ -21,21 +21,16 @@ class Task(object): This class can be instanciated multiple times if multiple taskw clients are needed. - This class can be given a Taskd instance for simplified configuration. - A taskw client should not be used after being destroyed. """ DEFAULT_TASK = task_binary_location() - def __init__(self, taskd=None, taskw=DEFAULT_TASK): - """Initialize a Task warrior (client) that can interact with a taskd - server. The task client runs in a temporary folder. + def __init__(self, taskw=DEFAULT_TASK): + """Initialize a Task warrior (client). The task client runs in a temporary folder. - :arg taskd: Taskd instance for client-server configuration :arg taskw: Task binary to use as client (defaults: task in PATH) """ self.taskw = taskw - self.taskd = taskd # Used to specify what command to launch (and to inject faketime) self._command = [self.taskw] @@ -56,10 +51,6 @@ class Task(object): "news.version=2.6.0\n" "".format(self.datadir)) - # Setup configuration to talk to taskd automatically - if self.taskd is not None: - self.bind_taskd_server(self.taskd) - # Hooks disabled until requested self.hooks = None @@ -88,49 +79,6 @@ class Task(object): # As well as TASKRC self.env["TASKRC"] = self.taskrc - def bind_taskd_server(self, taskd): - """Configure the present task client to talk to given taskd server - - Note that this can be performed automatically by passing taskd when - creating an instance of the current class. - """ - self.taskd = taskd - - cert = os.path.join(self.taskd.certpath, "test_client.cert.pem") - key = os.path.join(self.taskd.certpath, "test_client.key.pem") - self.config("taskd.certificate", cert) - self.config("taskd.key", key) - self.config("taskd.ca", self.taskd.ca_cert) - - address = ":".join((self.taskd.address, str(self.taskd.port))) - self.config("taskd.server", address) - - # Also configure the default user for given taskd server - self.set_taskd_user() - - def set_taskd_user(self, taskd_user=None, default=True): - """Assign a new user user to the present task client - - If default==False, a new user will be assigned instead of reusing the - default taskd user for the corresponding instance. - """ - if taskd_user is None: - if default: - user, org, userkey = self.taskd.default_user - else: - user, org, userkey = self.taskd.create_user() - else: - user, org, userkey = taskd_user - - credentials = "/".join((org, user, userkey)) - self.config("taskd.credentials", credentials) - - self.credentials = { - "user": user, - "org": org, - "userkey": userkey, - } - def config(self, var, value): """Run setup `var` as `value` in taskd config """ diff --git a/test/basetest/taskd.py b/test/basetest/taskd.py deleted file mode 100644 index c4b244bc9..000000000 --- a/test/basetest/taskd.py +++ /dev/null @@ -1,366 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, print_function -import errno -import os -import tempfile -import shutil -import signal -import atexit -from time import sleep -from subprocess import Popen, PIPE -from .utils import (find_unused_port, release_port, port_used, run_cmd_wait, - which, parse_datafile, DEFAULT_CERT_PATH, - taskd_binary_location) -from .exceptions import CommandError - -try: - from subprocess import DEVNULL -except ImportError: - DEVNULL = open(os.devnull, 'w') - - -class Taskd(object): - """Manage a taskd instance - - A temporary folder is used as data store of taskd. - This class can be instanciated multiple times if multiple taskd servers are - needed. - - This class implements mechanisms to automatically select an available port - and prevent assigning the same port to different instances. - - A server can be stopped and started multiple times, but should not be - started or stopped after being destroyed. - """ - DEFAULT_TASKD = taskd_binary_location() - TASKD_RUNNING = 0 - TASKD_NEVER_STARTED = 1 - TASKD_EXITED = 2 - TASKD_NOT_LISTENING = 3 - - def __init__(self, taskd=DEFAULT_TASKD, certpath=None, - address="localhost"): - """Initialize a Task server that runs in the background and stores data - in a temporary folder - - :arg taskd: Taskd binary to launch the server (defaults: taskd in PATH) - :arg certpath: Folder where to find all certificates needed for taskd - :arg address: Address to bind to - """ - self.taskd = taskd - self.usercount = 0 - - # Will hold the taskd subprocess if it's running - self.proc = None - self.datadir = tempfile.mkdtemp(prefix="taskd_") - self.tasklog = os.path.join(self.datadir, "taskd.log") - self.taskpid = os.path.join(self.datadir, "taskd.pid") - - # Ensure any instance is properly destroyed at session end - atexit.register(lambda: self.destroy()) - - self.reset_env() - - if certpath is None: - certpath = DEFAULT_CERT_PATH - self.certpath = certpath - - self.address = address - self.port = find_unused_port(self.address) - - # Keep all certificate paths public for access by TaskClients - self.client_cert = os.path.join(self.certpath, "client.cert.pem") - self.client_key = os.path.join(self.certpath, "client.key.pem") - self.server_cert = os.path.join(self.certpath, "server.cert.pem") - self.server_key = os.path.join(self.certpath, "server.key.pem") - self.server_crl = os.path.join(self.certpath, "server.crl.pem") - self.ca_cert = os.path.join(self.certpath, "ca.cert.pem") - - # Initialize taskd - cmd = (self.taskd, "init", "--data", self.datadir) - run_cmd_wait(cmd, env=self.env) - - self.config("server", "{0}:{1}".format(self.address, self.port)) - self.config("family", "IPv4") - self.config("log", self.tasklog) - self.config("pid.file", self.taskpid) - self.config("root", self.datadir) - self.config("client.allow", "^task [2-9]") - - # Setup all necessary certificates - self.config("client.cert", self.client_cert) - self.config("client.key", self.client_key) - self.config("server.cert", self.server_cert) - self.config("server.key", self.server_key) - self.config("server.crl", self.server_crl) - self.config("ca.cert", self.ca_cert) - - self.default_user = self.create_user() - - def __repr__(self): - txt = super(Taskd, self).__repr__() - return "{0} running from {1}>".format(txt[:-1], self.datadir) - - def reset_env(self): - """Set a new environment derived from the one used to launch the test - """ - # Copy all env variables to avoid clashing subprocess environments - self.env = os.environ.copy() - - # Make sure TASKDDATA points to the temporary folder - self.env["TASKDDATA"] = self.datadir - - def create_user(self, user=None, org=None): - """Create a user in the server and return the user credentials to use in a taskw client. - """ - if user is None: - # Create a unique user ID - uid = self.usercount - user = "test_user_{0}".format(uid) - - # Increment the user_id - self.usercount += 1 - - if org is None: - org = "default_org" - - self._add_entity("org", org, ignore_exists=True) - userkey = self._add_entity("user", org, user) - - return user, org, userkey - - def _add_entity(self, keyword, org, value=None, ignore_exists=False): - """Add an organization or user to the current server - - If a user creation is requested, the user unique ID is returned - """ - cmd = (self.taskd, "add", "--data", self.datadir, keyword, org) - - if value is not None: - cmd += (value,) - - try: - code, out, err = run_cmd_wait(cmd, env=self.env) - except CommandError as e: - match = False - for line in e.out.splitlines(): - if line.endswith("already exists.") and ignore_exists: - match = True - break - - # If the error was not "Already exists" report it - if not match: - raise - - if keyword == "user": - expected = "New user key: " - for line in out.splitlines(): - if line.startswith(expected): - return line.replace(expected, '') - - def config(self, var, value): - """Run setup `var` as `value` in taskd config - """ - cmd = (self.taskd, "config", "--force", "--data", self.datadir, var, - value) - run_cmd_wait(cmd, env=self.env) - - # If server is running send a SIGHUP to force config reload - if self.proc is not None: - try: - self.proc.send_signal(signal.SIGHUP) - except: - pass - - def status(self): - """Check the status of the server by checking if it's still running and - listening for connections - :returns: Taskd.TASKD_[NEVER_STARTED/EXITED/NOT_LISTENING/RUNNING] - """ - if self.proc is None: - return self.TASKD_NEVER_STARTED - - if self.returncode() is not None: - return self.TASKD_EXITED - - if not port_used(addr=self.address, port=self.port): - return self.TASKD_NOT_LISTENING - - return self.TASKD_RUNNING - - def returncode(self): - """If taskd finished, return its exit code, otherwise return None. - :returns: taskd's exit code or None - """ - return self.proc.poll() - - def start(self, minutes=5, tries_per_minute=2): - """Start the taskd server if it's not running. - If it's already running OSError will be raised - """ - if self.proc is None: - cmd = (self.taskd, "server", "--data", self.datadir) - self.proc = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=DEVNULL, - env=self.env) - else: - self.show_log_contents() - - raise OSError("Taskd server is still running or crashed") - - # Wait for server to listen by checking connectivity in the port - # Default is to wait up to 5 minutes checking once every 500ms - for i in range(minutes * 60 * tries_per_minute): - status = self.status() - - if status == self.TASKD_RUNNING: - return - - elif status == self.TASKD_NEVER_STARTED: - self.show_log_contents() - - raise OSError("Task server was never started. " - "This shouldn't happen!!") - - elif status == self.TASKD_EXITED: - # Collect output logs - out, err = self.proc.communicate() - - self.show_log_contents() - - raise OSError( - "Task server launched with '{0}' crashed or exited " - "prematurely. Exit code: {1}. " - "Listening on port: {2}. " - "Stdout: {3!r}, " - "Stderr: {4!r}.".format( - self.taskd, - self.returncode(), - self.port, - out, - err, - )) - - elif status == self.TASKD_NOT_LISTENING: - sleep(1 / tries_per_minute) - - else: - self.show_log_contents() - - raise OSError("Unknown running status for taskd '{0}'".format( - status)) - - # Force stop so we can collect output - proc = self.stop() - - # Collect output logs - out, err = proc.communicate() - - self.show_log_contents() - - raise OSError("Task server didn't start and listen on port {0} after " - "{1} minutes. Stdout: {2!r}. Stderr: {3!r}.".format( - self.port, minutes, out, err)) - - def stop(self): - """Stop the server by sending a SIGTERM and SIGKILL if fails to - terminate. - If it's already stopped OSError will be raised - - Returns: a reference to the old process object - """ - if self.proc is None: - raise OSError("Taskd server is not running") - - if self._check_pid(): - self.proc.send_signal(signal.SIGTERM) - - if self._check_pid(): - self.proc.kill() - - # Wait for process to end to avoid zombies - self.proc.wait() - - # Keep a reference to the old process - proc = self.proc - - # Unset the process to inform that no process is running - self.proc = None - - return proc - - def _check_pid(self): - "Check if self.proc is still running and a PID still exists" - # Wait ~1 sec for taskd to finish - signal = True - for i in range(10): - sleep(0.1) - if self.proc.poll() is not None: - signal = False - break - - return signal - - def destroy(self): - """Cleanup the data folder and release server port for other instances - """ - # Ensure server is stopped first - if self.proc is not None: - self.stop() - - try: - shutil.rmtree(self.datadir) - except OSError as e: - if e.errno == errno.ENOENT: - # Directory no longer exists - pass - else: - raise - - release_port(self.port) - - # Prevent future reuse of this instance - self.start = self.__destroyed - self.config = self.__destroyed - self.stop = self.__destroyed - - # self.destroy will get called when the python session closes. - # If self.destroy was already called, turn the action into a noop - self.destroy = lambda: None - - def __destroyed(self, *args, **kwargs): - raise AttributeError("Taskd instance has been destroyed. " - "Create a new instance if you need a new server.") - - @classmethod - def not_available(cls): - """Check if the taskd binary is available in the path""" - if which(cls.DEFAULT_TASKD): - return False - else: - return True - - def client_data(self, client): - """Return a python list with the content of tx.data matching the given - task client. tx.data will be parsed to string and JSON. - """ - file = os.path.join(self.datadir, - "orgs", - client.credentials["org"], - "users", - client.credentials["userkey"], - "tx.data") - - return parse_datafile(file) - - def show_log_contents(self): - """Print to to STDOUT the contents of taskd.log - """ - if os.path.isfile(self.tasklog): - with open(self.tasklog) as fh: - print("#### Start taskd.log ####") - for line in fh: - print(line, end='') - print("#### End taskd.log ####") - -# vim: ai sts=4 et sw=4 diff --git a/test/basetest/testing.py b/test/basetest/testing.py index 66ad529bc..5d557df8a 100644 --- a/test/basetest/testing.py +++ b/test/basetest/testing.py @@ -2,8 +2,7 @@ import unittest import sys -from .utils import TASKW_SKIP, TASKD_SKIP -from .taskd import Taskd +from .utils import TASKW_SKIP class BaseTestCase(unittest.TestCase): @@ -21,13 +20,4 @@ class TestCase(BaseTestCase): pass -@unittest.skipIf(TASKD_SKIP, "TASKD_SKIP set, skipping taskd tests.") -@unittest.skipIf(Taskd.not_available(), "Taskd binary not available at '{0}'" - .format(Taskd.DEFAULT_TASKD)) -class ServerTestCase(BaseTestCase): - """Automatically skips tests if TASKD_SKIP is present in the environment - """ - pass - - # vim: ai sts=4 et sw=4 diff --git a/test/basetest/utils.py b/test/basetest/utils.py index 9d25479f0..0c8a941d0 100644 --- a/test/basetest/utils.py +++ b/test/basetest/utils.py @@ -43,12 +43,10 @@ DEFAULT_HOOK_PATH = os.path.abspath( ) -# Environment flags to control skipping of task and taskd tests +# Environment flags to control skipping of task tests TASKW_SKIP = os.environ.get("TASKW_SKIP", False) -TASKD_SKIP = os.environ.get("TASKD_SKIP", False) # Environment flags to control use of PATH or in-tree binaries TASK_USE_PATH = os.environ.get("TASK_USE_PATH", False) -TASKD_USE_PATH = os.environ.get("TASKD_USE_PATH", False) UUID_REGEXP = ("[0-9A-Fa-f]{8}-" + ("[0-9A-Fa-f]{4}-" * 3) + "[0-9A-Fa-f]{12}") @@ -60,15 +58,8 @@ def task_binary_location(cmd="task"): return binary_location(cmd, TASK_USE_PATH) -def taskd_binary_location(cmd="taskd"): - """If TASKD_USE_PATH is set rely on PATH to look for taskd binaries. - Otherwise ../src/ is used by default. - """ - return binary_location(cmd, TASKD_USE_PATH) - - def binary_location(cmd, USE_PATH=False): - """If USE_PATH is True rely on PATH to look for taskd binaries. + """If USE_PATH is True rely on PATH to look for binaries. Otherwise ../src/ is used by default. """ if USE_PATH: @@ -135,8 +126,8 @@ def _queue_output(arguments, pidq, outputq): outputq.put(( "", ("Unexpected exception caught during execution of taskw: '{0}' . " - "If you are running out-of-tree tests set TASK_USE_PATH=1 or " - "TASKD_USE_PATH=1 in shell env before execution and add the " + "If you are running out-of-tree tests set TASK_USE_PATH=1 " + "in shell env before execution and add the " "location of the task(d) binary to the PATH".format(e)), 255)) # false exitcode @@ -276,80 +267,6 @@ def run_cmd_wait_nofail(*args, **kwargs): return e.code, e.out, e.err -def get_IPs(hostname): - output = {} - addrs = socket.getaddrinfo(hostname, 0, 0, 0, socket.IPPROTO_TCP) - - for family, socktype, proto, canonname, sockaddr in addrs: - addr = sockaddr[0] - output[family] = addr - - return output - - -def port_used(addr="localhost", port=None): - "Return True if port is in use, False otherwise" - if port is None: - raise TypeError("Argument 'port' may not be None") - - # If we got an address name, resolve it both to IPv6 and IPv4. - IPs = get_IPs(addr) - - # Taskd seems to prefer IPv6 so we do it first - for family in (socket.AF_INET6, socket.AF_INET): - try: - addr = IPs[family] - except KeyError: - continue - - s = socket.socket(family, socket.SOCK_STREAM) - result = s.connect_ex((addr, port)) - s.close() - if result == 0: - # connection was successful - return True - else: - return False - - -def find_unused_port(addr="localhost", start=53589, track=True): - """Find an unused port starting at `start` port - - If track=False the returned port will not be marked as in-use and the code - will rely entirely on the ability to connect to addr:port as detection - mechanism. Note this may cause problems if ports are assigned but not used - immediately - """ - maxport = 65535 - unused = None - - for port in xrange(start, maxport): - if not port_used(addr, port): - if track and port in USED_PORTS: - continue - - unused = port - break - - if unused is None: - raise ValueError("No available port in the range {0}-{1}".format( - start, maxport)) - - if track: - USED_PORTS.add(unused) - - return unused - - -def release_port(port): - """Forget that given port was marked as'in-use - """ - try: - USED_PORTS.remove(port) - except KeyError: - pass - - def memoize(obj): """Keep an in-memory cache of function results given its inputs """ diff --git a/test/diag.t b/test/diag.t index ed744d020..f19b3e8ce 100755 --- a/test/diag.t +++ b/test/diag.t @@ -40,9 +40,6 @@ class TestDiagnostics(TestCase): def setUp(self): self.t = Task() self.t.config("editor", "edlin") - self.t.config("taskd.ca", "/tmp/ca") - self.t.config("taskd.trust", "strict") - self.t.config("taskd.credentials", "us/me/xxx") @unittest.skipIf( getattr(platform, 'dist', None) == None or 'xenial' == platform.dist()[-1], @@ -54,9 +51,8 @@ class TestDiagnostics(TestCase): code, out, err = self.t.diag() self.tap(out) self.assertRegex(out, "Compliance:\s+C\+\+17") - self.assertRegex(out, "libgnutls:\s+\d+\.\d+\.\d+") self.assertIn("edlin", out) - self.assertIn("strict", out) + self.assertIn("Locking", out) def test_64bit_time_t(self): """Test that time_t has size of 64 bits""" diff --git a/test/docker/arch b/test/docker/arch index 8112cc005..d45ece8c7 100644 --- a/test/docker/arch +++ b/test/docker/arch @@ -2,7 +2,7 @@ FROM archlinux/archlinux:base-devel RUN pacman -Sy --noconfirm archlinux-keyring RUN pacman -Syyu --noconfirm -RUN pacman -S --noconfirm gnutls util-linux bash-completion cmake python3 git libfaketime curl +RUN pacman -S --noconfirm util-linux bash-completion cmake python3 git libfaketime curl # Setup language environment ENV LANG en_US.UTF-8 diff --git a/test/docker/centos7 b/test/docker/centos7 index 7748d6f09..0457a624c 100644 --- a/test/docker/centos7 +++ b/test/docker/centos7 @@ -1,7 +1,7 @@ FROM centos:7 RUN yum update -y -RUN yum install python3 git gcc gcc-c++ make gnutls-devel libuuid-devel -y +RUN yum install python3 git gcc gcc-c++ make libuuid-devel -y RUN yum install epel-release centos-release-scl -y RUN yum install which cmake3 devtoolset-7-gcc* libfaketime curl -y RUN source scl_source enable devtoolset-7; gcc --version; cmake3 --version diff --git a/test/docker/centos8 b/test/docker/centos8 index be1ba3e3f..11343fb1f 100644 --- a/test/docker/centos8 +++ b/test/docker/centos8 @@ -5,7 +5,7 @@ RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Linux-* RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Linux-* RUN dnf update -y -RUN dnf install python3 git gcc gcc-c++ make gnutls-devel libuuid-devel glibc-langpack-en -y +RUN dnf install python3 git gcc gcc-c++ make libuuid-devel glibc-langpack-en -y RUN dnf install epel-release -y RUN dnf install which cmake libfaketime curl -y RUN gcc --version; cmake --version diff --git a/test/docker/debianstable b/test/docker/debianstable index 8a672c11b..b1e0279b9 100644 --- a/test/docker/debianstable +++ b/test/docker/debianstable @@ -1,7 +1,7 @@ FROM debian:stable RUN apt-get update -RUN apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime +RUN apt-get install -y build-essential cmake git uuid-dev faketime RUN apt-get install -y python3 curl # Setup language environment diff --git a/test/docker/debiantesting b/test/docker/debiantesting index 43aac0bab..598ab5cae 100644 --- a/test/docker/debiantesting +++ b/test/docker/debiantesting @@ -1,7 +1,7 @@ FROM debian:testing RUN apt-get update -RUN apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime +RUN apt-get install -y build-essential cmake git uuid-dev faketime RUN apt-get install -y python3 curl # Setup language environment diff --git a/test/docker/fedora32 b/test/docker/fedora32 index 839ce4be8..b9da1735f 100644 --- a/test/docker/fedora32 +++ b/test/docker/fedora32 @@ -1,7 +1,7 @@ FROM fedora:32 RUN dnf update -y -RUN dnf install python3 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime glibc-langpack-en curl -y +RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y # Setup language environment ENV LC_ALL en_US.UTF-8 diff --git a/test/docker/fedora33 b/test/docker/fedora33 index 724e147e5..825fa8359 100644 --- a/test/docker/fedora33 +++ b/test/docker/fedora33 @@ -1,7 +1,7 @@ FROM fedora:33 RUN dnf update -y -RUN dnf install python3 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime glibc-langpack-en curl -y +RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y # Setup language environment ENV LC_ALL en_US.UTF-8 diff --git a/test/docker/fedora34 b/test/docker/fedora34 index 46136444a..ff32586dd 100644 --- a/test/docker/fedora34 +++ b/test/docker/fedora34 @@ -1,7 +1,7 @@ FROM fedora:34 RUN dnf update -y -RUN dnf install python3 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime glibc-langpack-en curl -y +RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y # Setup language environment ENV LC_ALL en_US.UTF-8 diff --git a/test/docker/fedora35 b/test/docker/fedora35 index 18e973f0f..6696dacf0 100644 --- a/test/docker/fedora35 +++ b/test/docker/fedora35 @@ -1,7 +1,7 @@ FROM fedora:35 RUN dnf update -y -RUN dnf install python3 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime glibc-langpack-en curl -y +RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y # Setup language environment ENV LC_ALL en_US.UTF-8 diff --git a/test/docker/gentoo b/test/docker/gentoo index 71e9d50aa..3ea9cc2f4 100644 --- a/test/docker/gentoo +++ b/test/docker/gentoo @@ -7,7 +7,7 @@ FROM gentoo/stage3-x86:latest # copy the entire portage volume in COPY --from=portage /usr/portage /usr/portage -RUN emerge -qv sys-libs/readline:0 net-libs/gnutls:0= sys-apps/util-linux dev-util/cmake sys-devel/make dev-vcs/git sys-libs/libfaketime net-misc/curl +RUN emerge -qv sys-libs/readline:0 sys-apps/util-linux dev-util/cmake sys-devel/make dev-vcs/git sys-libs/libfaketime net-misc/curl # Setup language environment ENV LC_ALL en_US.UTF-8 diff --git a/test/docker/opensuse15 b/test/docker/opensuse15 index 1c79542ec..53fcc98e0 100644 --- a/test/docker/opensuse15 +++ b/test/docker/opensuse15 @@ -1,6 +1,6 @@ FROM opensuse/leap:15 -RUN zypper install -y python3 awk coreutils git gcc gcc-c++ cmake make libgnutls-devel libuuid-devel libfaketime curl +RUN zypper install -y python3 awk coreutils git gcc gcc-c++ cmake make libuuid-devel libfaketime curl # Setup language environment ENV LC_ALL en_US.UTF-8 diff --git a/test/docker/ubuntu1604 b/test/docker/ubuntu1604 index 5f39b45e1..b7e5db8af 100644 --- a/test/docker/ubuntu1604 +++ b/test/docker/ubuntu1604 @@ -1,7 +1,7 @@ FROM ubuntu:16.04 RUN apt-get update -RUN apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime locales python3 curl +RUN apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl # Setup language environment RUN locale-gen en_US.UTF-8 diff --git a/test/docker/ubuntu1804 b/test/docker/ubuntu1804 index 038a9a1b8..1da777d6e 100644 --- a/test/docker/ubuntu1804 +++ b/test/docker/ubuntu1804 @@ -1,7 +1,7 @@ FROM ubuntu:18.04 RUN apt-get update -RUN apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime locales python3 curl +RUN apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl # Setup language environment RUN locale-gen en_US.UTF-8 diff --git a/test/docker/ubuntu2004 b/test/docker/ubuntu2004 index e783e8d20..41cea19f7 100644 --- a/test/docker/ubuntu2004 +++ b/test/docker/ubuntu2004 @@ -1,7 +1,7 @@ FROM ubuntu:20.04 RUN apt-get update -RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime locales python3 curl +RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl # Setup language environment RUN locale-gen en_US.UTF-8 diff --git a/test/docker/ubuntu2204 b/test/docker/ubuntu2204 index 5dc30996b..a505a1cdc 100644 --- a/test/docker/ubuntu2204 +++ b/test/docker/ubuntu2204 @@ -1,7 +1,7 @@ FROM ubuntu:22.04 RUN apt-get update -RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime locales python3 curl +RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl # Setup language environment RUN locale-gen en_US.UTF-8 diff --git a/test/feature.default.project.t b/test/feature.default.project.t index 39c0d1073..394ddda8a 100755 --- a/test/feature.default.project.t +++ b/test/feature.default.project.t @@ -32,7 +32,7 @@ import unittest # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from basetest import Task, TestCase, Taskd, ServerTestCase +from basetest import Task, TestCase class TestDefaultProject(TestCase): @@ -226,51 +226,6 @@ class TestDefaultProject(TestCase): self.assertNotIn(self.default_project, out) -class ServerTestDefaultProject(ServerTestCase): - @classmethod - def setUpClass(cls): - cls.taskd = Taskd() - # This takes a while... - cls.taskd.start() - - def setUp(self): - self.t1 = Task(taskd=self.taskd) - self.t2 = Task(taskd=self.taskd) - self.t3 = Task(taskd=self.taskd) - - def test_default_project_sync(self): - """default.project is not applied to projectless tasks during sync""" - # NOTE - reported on TW-1287 - desc = "Testing task" - self.t1(("add", desc)) - self.t1("sync") - - code, out, err = self.t1() - - self.assertIn(desc, out) - - # Testing scenario - default.project is applied on task arrival - proj2 = "Client2" - self.t2.config("default.project", proj2) - self.t2("sync") - - code, out, err = self.t2() - self.assertIn(desc, out) - self.assertNotIn(proj2, out) - - self.t2("sync") - - # Testing scenario - default.project is applied on task delivery - proj3 = "Client3" - self.t3.config("default.project", proj3) - self.t3("sync") - - code, out, err = self.t3() - self.assertIn(desc, out) - self.assertNotIn(proj2, out) - self.assertNotIn(proj3, out) - - if __name__ == "__main__": from simpletap import TAPTestRunner unittest.main(testRunner=TAPTestRunner()) diff --git a/test/run_all b/test/run_all index 812d3914e..8668d685f 100755 --- a/test/run_all +++ b/test/run_all @@ -22,9 +22,6 @@ except ImportError: # python 3 from queue import Queue, Empty -# Look for taskd in $PATH instead of task/src/ -os.environ["TASKD_USE_PATH"] = "1" - TIMEOUT = .2 diff --git a/test/scripts/test_osx.sh b/test/scripts/test_osx.sh index e3ac40457..2eb3e3e1d 100644 --- a/test/scripts/test_osx.sh +++ b/test/scripts/test_osx.sh @@ -1,7 +1,6 @@ set -ex export LDFLAGS="-framework Foundation -framework Security" -brew install gnutls brew install cmake brew install libfaketime git clean -dfx diff --git a/test/template.t b/test/template.t index d60335e04..a41273f8d 100644 --- a/test/template.t +++ b/test/template.t @@ -34,7 +34,6 @@ from datetime import datetime sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase -from basetest import Taskd, ServerTestCase # Test methods available: @@ -226,25 +225,6 @@ sys.exit(0) "This is an example modify hook") -class ServerTestBugNumber(ServerTestCase): - @classmethod - def setUpClass(cls): - cls.taskd = Taskd() - # This takes a while... - cls.taskd.start() - - def setUp(self): - """Executed before each test in the class""" - self.t = Task(taskd=self.taskd) - # Or if Task() is already available - # self.t.bind_taskd_server(self.taskd) - - def test_server_sync(self): - """Testing if client and server can speak to each other""" - self.t("add Something to sync") - self.t("sync") - - if __name__ == "__main__": from simpletap import TAPTestRunner unittest.main(testRunner=TAPTestRunner())