mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
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:
parent
771977aa69
commit
31105c2ba3
57 changed files with 403 additions and 1615 deletions
|
@ -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)
|
||||
|
|
13
ChangeLog
13
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 -
|
||||
|
||||
|
|
17
INSTALL
17
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"
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -38,9 +38,6 @@
|
|||
#cmakedefine GNUHURD
|
||||
#cmakedefine UNKNOWN
|
||||
|
||||
/* Found the GnuTLS library */
|
||||
#cmakedefine HAVE_LIBGNUTLS
|
||||
|
||||
/* Found tm_gmtoff */
|
||||
#cmakedefine HAVE_TM_GMTOFF
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 ()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -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"))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
101
src/tc/Server.cpp
Normal 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
80
src/tc/Server.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
4
src/tc/rust/Cargo.lock
generated
4
src/tc/rust/Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
set -ex
|
||||
|
||||
export LDFLAGS="-framework Foundation -framework Security"
|
||||
brew install gnutls
|
||||
brew install cmake
|
||||
brew install libfaketime
|
||||
git clean -dfx
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue