Sync against taskchampion-sync-server (#3118)

This removes use of gnutls and the TLS implementation, which is no
longer needed (task synchronization is handled via Taskchampion, which
uses `reqwest`, which handles TLS via other Rust dependencies). This
incidentally removes the following config options:
 * `debug.tls`
 * `taskd.ca`
 * `taskd.certificate`
 * `taskd.ciphers`
 * `taskd.credentials`
 * `taskd.key`
 * `taskd.server`
 * `taskd.trust`
This commit is contained in:
Dustin J. Mitchell 2023-07-08 10:27:33 -04:00 committed by GitHub
parent 771977aa69
commit 31105c2ba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 403 additions and 1615 deletions

View file

@ -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)

View file

@ -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 -

17
INSTALL
View file

@ -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"

View file

@ -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:

View file

@ -38,9 +38,6 @@
#cmakedefine GNUHURD
#cmakedefine UNKNOWN
/* Found the GnuTLS library */
#cmakedefine HAVE_LIBGNUTLS
/* Found tm_gmtoff */
#cmakedefine HAVE_TM_GMTOFF

View file

@ -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 <origin>
$ task config sync.server.client_key <client_key>
$ task config sync.server.encryption_secret <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 <name>.cert.pem ~/.task
$ cp <name>.key.pem ~/.task
Then you configure Taskwarrior, using the provided details:
$ task config taskd.certificate ~/.task/<name>.cert.pem
$ task config taskd.key ~/.task/<name>.key.pem
$ task config taskd.credentials <organization>/<name>/<UUID>
$ task config taskd.server <server domain>:<port>
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.

View file

@ -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:

View file

@ -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=<host>:<port>
.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=<organization>/<user>/<key>
.RS
User identification for the Taskserver, which includes a private key.
.RE
.TP
.B taskd.certificate=<path>
.RS
Specifies the path to the client certificate used for identification with the
Taskserver.
.RE
.TP
.B taskd.key=<path>
.RS
Specifies the path to the client key used for encrypted communication with the
Taskserver.
.RE
.TP
.B taskd.ca=<path>
.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.

View file

@ -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.

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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=<certificate file>\n"
"#taskd.certificate=<certificate file>\n"
"#taskd.credentials=<organization>/<name>/<password>\n"
"#taskd.server=<server>:<port>\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);

View file

@ -41,6 +41,7 @@
#include <format.h>
#include <main.h>
#include <util.h>
#include "tc/Server.h"
bool TDB2::debug_mode = false;
static void dependency_scan (std::vector<Task> &);
@ -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 ()
{

View file

@ -38,6 +38,10 @@
#include <tc/WorkingSet.h>
#include <tc/Replica.h>
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<tc::WorkingSet> _working_set;

View file

@ -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 <cmake.h>
#ifdef HAVE_LIBGNUTLS
#include <TLSClient.h>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <gnutls/x509.h>
#include <shared.h>
#include <format.h>
#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

View file

@ -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 <string>
#include <gnutls/gnutls.h>
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
////////////////////////////////////////////////////////////////////////////////

View file

@ -39,10 +39,6 @@
#include <commit.h>
#endif
#ifdef HAVE_LIBGNUTLS
#include <gnutls/gnutls.h>
#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"))

View file

@ -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"

View file

@ -35,6 +35,7 @@
#include <shared.h>
#include <format.h>
#include <util.h>
#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;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -30,18 +30,12 @@
#include <string>
#include <Command.h>
#include <Msg.h>
#include <TLSClient.h>
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

View file

@ -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)

View file

@ -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)

View file

@ -28,6 +28,7 @@
#include <format.h>
#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)
{

View file

@ -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 ();

101
src/tc/Server.cpp Normal file
View file

@ -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 <cmake.h>
#include <format.h>
#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;
}
////////////////////////////////////////////////////////////////////////////////

80
src/tc/Server.h Normal file
View file

@ -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 <string>
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#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<void(tc::ffi::TCServer*)>>;
// 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
////////////////////////////////////////////////////////////////////////////////

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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
"""

View file

@ -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

View file

@ -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

View file

@ -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
"""

View file

@ -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"""

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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())

View file

@ -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

View file

@ -1,7 +1,6 @@
set -ex
export LDFLAGS="-framework Foundation -framework Security"
brew install gnutls
brew install cmake
brew install libfaketime
git clean -dfx

View file

@ -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())