Sync against taskchampion-sync-server (#3118)

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

View file

@ -17,7 +17,6 @@ add_library (task STATIC CLI2.cpp CLI2.h
Lexer.cpp Lexer.h
TDB2.cpp TDB2.h
Task.cpp Task.h
TLSClient.cpp TLSClient.h
Variant.cpp Variant.h
ViewTask.cpp ViewTask.h
dependency.cpp
@ -52,9 +51,10 @@ add_executable (calc_executable calc.cpp)
add_executable (lex_executable lex.cpp)
# Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes.
target_link_libraries (task_executable task tc tc-rust commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (calc_executable task tc tc-rust commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task tc tc-rust commands columns libshared task libshared ${TASK_LIBRARIES})
# Similarly for `tc`.
target_link_libraries (task_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (calc_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES})
set_property (TARGET task_executable PROPERTY OUTPUT_NAME "task")

View file

@ -272,21 +272,17 @@ std::string configurationDefaults =
"list.all.tags=0 # Include old tag names in 'tags' command\n"
"print.empty.columns=0 # Print columns which have no data for any task\n"
"debug=0 # Display diagnostics\n"
"debug.tls=0 # Sync diagnostics\n"
"sugar=1 # Syntactic sugar\n"
"obfuscate=0 # Obfuscate data for error reporting\n"
"fontunderline=1 # Uses underlines rather than -------\n"
"\n"
"# WARNING: Please read the documentation (man task-sync) before setting up\n"
"# Taskwarrior for Taskserver synchronization.\n"
"#taskd.ca=<certificate file>\n"
"#taskd.certificate=<certificate file>\n"
"#taskd.credentials=<organization>/<name>/<password>\n"
"#taskd.server=<server>:<port>\n"
"taskd.trust=strict\n"
"#taskd.trust=ignore hostname\n"
"#taskd.trust=allow all\n"
"taskd.ciphers=NORMAL\n"
"\n"
"#sync.server.client_key # Client key for sync to a server\n"
"#sync.server.encryption_secret # Encryption secret for sync to a server\n"
"#sync.server.origin # Origin of the sync server\n"
"#sync.local.server_dir # Directory for local sync\n"
"\n"
"# Aliases - alternate names for commands\n"
"alias.rm=delete # Alias for the delete command\n"
@ -1354,16 +1350,13 @@ void Context::loadAliases ()
}
////////////////////////////////////////////////////////////////////////////////
// Using the general rc.debug setting automaticalls sets debug.tls, debug.hooks
// Using the general rc.debug setting automaticalls sets debug.hooks
// and debug.parser, unless they already have values, which by default they do
// not.
void Context::propagateDebug ()
{
if (config.getBoolean ("debug"))
{
if (! config.has ("debug.tls"))
config.set ("debug.tls", 2);
if (! config.has ("debug.hooks"))
config.set ("debug.hooks", 1);

View file

@ -41,6 +41,7 @@
#include <format.h>
#include <main.h>
#include <util.h>
#include "tc/Server.h"
bool TDB2::debug_mode = false;
static void dependency_scan (std::vector<Task> &);
@ -469,6 +470,12 @@ int TDB2::num_reverts_possible ()
return (int)replica.num_undo_points ();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::sync (tc::Server server, bool avoid_snapshots)
{
replica.sync(std::move(server), avoid_snapshots);
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::dump ()
{

View file

@ -38,6 +38,10 @@
#include <tc/WorkingSet.h>
#include <tc/Replica.h>
namespace tc {
class Server;
}
// TDB2 Class represents all the files in the task database.
class TDB2
{
@ -73,6 +77,8 @@ public:
void dump ();
void sync (tc::Server server, bool avoid_snapshots);
private:
tc::Replica replica;
std::optional<tc::WorkingSet> _working_set;

View file

@ -1,576 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#ifdef HAVE_LIBGNUTLS
#include <TLSClient.h>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <gnutls/x509.h>
#include <shared.h>
#include <format.h>
#define HEADER_SIZE 4
#define MAX_BUF 16384
#if GNUTLS_VERSION_NUMBER < 0x030406
#if GNUTLS_VERSION_NUMBER >= 0x020a00
static int verify_certificate_callback (gnutls_session_t);
#endif
#endif
////////////////////////////////////////////////////////////////////////////////
static void gnutls_log_function (int level, const char* message)
{
std::cout << "c: " << level << ' ' << message;
}
////////////////////////////////////////////////////////////////////////////////
#if GNUTLS_VERSION_NUMBER < 0x030406
#if GNUTLS_VERSION_NUMBER >= 0x020a00
static int verify_certificate_callback (gnutls_session_t session)
{
const TLSClient* client = (TLSClient*) gnutls_session_get_ptr (session); // All
return client->verify_certificate ();
}
#endif
#endif
////////////////////////////////////////////////////////////////////////////////
TLSClient::~TLSClient ()
{
gnutls_deinit (_session); // All
gnutls_certificate_free_credentials (_credentials); // All
#if GNUTLS_VERSION_NUMBER < 0x030300
gnutls_global_deinit (); // All
#endif
if (_socket)
{
shutdown (_socket, SHUT_RDWR);
close (_socket);
}
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::limit (int max)
{
_limit = max;
}
////////////////////////////////////////////////////////////////////////////////
// Calling this method results in all subsequent socket traffic being sent to
// std::cout, labelled with 'c: ...'.
void TLSClient::debug (int level)
{
if (level)
_debug = true;
gnutls_global_set_log_function (gnutls_log_function); // All
gnutls_global_set_log_level (level); // All
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::trust (const enum trust_level value)
{
_trust = value;
if (_debug)
{
if (_trust == allow_all)
std::cout << "c: INFO Server certificate will be trusted automatically.\n";
else if (_trust == ignore_hostname)
std::cout << "c: INFO Server certificate will be verified but hostname ignored.\n";
else
std::cout << "c: INFO Server certificate will be verified.\n";
}
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::ciphers (const std::string& cipher_list)
{
_ciphers = cipher_list;
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::init (
const std::string& ca,
const std::string& cert,
const std::string& key)
{
_ca = ca;
_cert = cert;
_key = key;
int ret;
#if GNUTLS_VERSION_NUMBER < 0x030300
ret = gnutls_global_init (); // All
if (ret < 0)
throw format ("TLS init error. {1}", gnutls_strerror (ret)); // All
#endif
ret = gnutls_certificate_allocate_credentials (&_credentials); // All
if (ret < 0)
throw format ("TLS allocation error. {1}", gnutls_strerror (ret)); // All
#if GNUTLS_VERSION_NUMBER >= 0x030014
// Automatic loading of system installed CA certificates.
ret = gnutls_certificate_set_x509_system_trust (_credentials); // 3.0.20
if (ret < 0)
throw format ("Bad System Trust. {1}", gnutls_strerror (ret)); // All
#endif
if (_ca != "")
{
// The gnutls_certificate_set_x509_key_file call returns number of
// certificates parsed on success (including 0, when no certificate was
// found) and negative values on error
ret = gnutls_certificate_set_x509_trust_file (_credentials, _ca.c_str (), GNUTLS_X509_FMT_PEM); // All
if (ret == 0)
throw format ("CA file {1} contains no certificate.", _ca);
else if (ret < 0)
throw format ("Bad CA file: {1}", gnutls_strerror (ret)); // All
}
// TODO This may need 0x030111 protection.
if (_cert != "" &&
_key != "" &&
(ret = gnutls_certificate_set_x509_key_file (_credentials, _cert.c_str (), _key.c_str (), GNUTLS_X509_FMT_PEM)) < 0) // 3.1.11
throw format ("Bad client CERT/KEY file. {1}", gnutls_strerror (ret)); // All
#if GNUTLS_VERSION_NUMBER < 0x030406
#if GNUTLS_VERSION_NUMBER >= 0x020a00
// The automatic verification for the server certificate with
// gnutls_certificate_set_verify_function only works with gnutls
// >=2.9.10. So with older versions we should call the verify function
// manually after the gnutls handshake.
gnutls_certificate_set_verify_function (_credentials, verify_certificate_callback); // 2.10.0
#endif
#endif
ret = gnutls_init (&_session, GNUTLS_CLIENT); // All
if (ret < 0)
throw format ("TLS client init error. {1}", gnutls_strerror (ret)); // All
// Use default priorities unless overridden.
if (_ciphers == "")
_ciphers = "NORMAL";
const char *err;
ret = gnutls_priority_set_direct (_session, _ciphers.c_str (), &err); // All
if (ret < 0)
{
if (_debug && ret == GNUTLS_E_INVALID_REQUEST)
std::cout << "c: ERROR Priority error at: " << err << '\n';
throw format ("Error initializing TLS. {1}", gnutls_strerror (ret)); // All
}
// Apply the x509 credentials to the current session.
ret = gnutls_credentials_set (_session, GNUTLS_CRD_CERTIFICATE, _credentials); // All
if (ret < 0)
throw format ("TLS credentials error. {1}", gnutls_strerror (ret)); // All
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::connect (const std::string& host, const std::string& port)
{
_host = host;
_port = port;
int ret;
#if GNUTLS_VERSION_NUMBER >= 0x030406
// For _trust == TLSClient::allow_all we perform no action
if (_trust == TLSClient::ignore_hostname)
gnutls_session_set_verify_cert (_session, nullptr, 0); // 3.4.6
else if (_trust == TLSClient::strict)
gnutls_session_set_verify_cert (_session, _host.c_str (), 0); // 3.4.6
#endif
// SNI. Only permitted when _host is a DNS name, not an IPv4/6 address.
std::string dummyAddress;
int dummyPort;
if (! isIPv4Address (_host, dummyAddress, dummyPort) &&
! isIPv6Address (_host, dummyAddress, dummyPort))
{
ret = gnutls_server_name_set (_session, GNUTLS_NAME_DNS, _host.c_str (), _host.length ()); // All
if (ret < 0)
throw format ("TLS SNI error. {1}", gnutls_strerror (ret)); // All
}
// Store the TLSClient instance, so that the verification callback can access
// it during the handshake below and call the verification method.
gnutls_session_set_ptr (_session, (void*) this); // All
// use IPv4 or IPv6, does not matter.
struct addrinfo hints {};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
struct addrinfo* res;
ret = ::getaddrinfo (host.c_str (), port.c_str (), &hints, &res);
if (ret != 0)
throw std::string (::gai_strerror (ret));
// Try them all, stop on success.
struct addrinfo* p;
for (p = res; p != nullptr; p = p->ai_next)
{
if ((_socket = ::socket (p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
continue;
// When a socket is closed, it remains unavailable for a while (netstat -an).
// Setting SO_REUSEADDR allows this program to assume control of a closed,
// but unavailable socket.
int on = 1;
if (::setsockopt (_socket,
SOL_SOCKET,
SO_REUSEADDR,
(const void*) &on,
sizeof (on)) == -1)
throw std::string (::strerror (errno));
if (::connect (_socket, p->ai_addr, p->ai_addrlen) == -1)
continue;
break;
}
free (res);
if (p == nullptr)
throw format ("Could not connect to {1} {2}", host, port);
#if GNUTLS_VERSION_NUMBER >= 0x030100
gnutls_handshake_set_timeout (_session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); // 3.1.0
#endif
#if GNUTLS_VERSION_NUMBER >= 0x030109
gnutls_transport_set_int (_session, _socket); // 3.1.9
#else
gnutls_transport_set_ptr (_session, (gnutls_transport_ptr_t) (intptr_t) _socket); // All
#endif
// Perform the TLS handshake
do
{
ret = gnutls_handshake (_session); // All
}
while (ret < 0 && gnutls_error_is_fatal (ret) == 0); // All
if (ret < 0)
{
#if GNUTLS_VERSION_NUMBER >= 0x030406
if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR)
{
auto type = gnutls_certificate_type_get (_session); // All
auto status = gnutls_session_get_verify_cert_status (_session); // 3.4.6
gnutls_datum_t out;
gnutls_certificate_verification_status_print (status, type, &out, 0); // 3.1.4
std::string error {(const char*) out.data};
gnutls_free (out.data); // All
throw format ("Handshake failed. {1}", error); // All
}
#else
throw format ("Handshake failed. {1}", gnutls_strerror (ret)); // All
#endif
}
#if GNUTLS_VERSION_NUMBER < 0x020a00
// The automatic verification for the server certificate with
// gnutls_certificate_set_verify_function does only work with gnutls
// >=2.10.0. So with older versions we should call the verify function
// manually after the gnutls handshake.
ret = verify_certificate ();
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR Certificate verification failed.\n";
throw format ("Error initializing TLS. {1}", gnutls_strerror (ret)); // All
}
#endif
if (_debug)
{
#if GNUTLS_VERSION_NUMBER >= 0x03010a
char* desc = gnutls_session_get_desc (_session); // 3.1.10
std::cout << "c: INFO Handshake was completed: " << desc << '\n';
gnutls_free (desc);
#else
std::cout << "c: INFO Handshake was completed.\n";
#endif
}
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::bye ()
{
gnutls_bye (_session, GNUTLS_SHUT_RDWR); // All
}
////////////////////////////////////////////////////////////////////////////////
int TLSClient::verify_certificate () const
{
if (_trust == TLSClient::allow_all)
return 0;
if (_debug)
std::cout << "c: INFO Verifying certificate.\n";
// This verification function uses the trusted CAs in the credentials
// structure. So you must have installed one or more CA certificates.
unsigned int status = 0;
const char* hostname = _host.c_str();
#if GNUTLS_VERSION_NUMBER >= 0x030104
if (_trust == TLSClient::ignore_hostname)
hostname = nullptr;
int ret = gnutls_certificate_verify_peers3 (_session, hostname, &status); // 3.1.4
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR Certificate verification peers3 failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
// status 16450 == 0100000001000010
// GNUTLS_CERT_INVALID 1<<1
// GNUTLS_CERT_SIGNER_NOT_FOUND 1<<6
// GNUTLS_CERT_UNEXPECTED_OWNER 1<<14 Hostname does not match
if (_debug && status)
std::cout << "c: ERROR Certificate status=" << status << '\n';
#else
int ret = gnutls_certificate_verify_peers2 (_session, &status); // All
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR Certificate verification peers2 failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (_debug && status)
std::cout << "c: ERROR Certificate status=" << status << '\n';
if ((status == 0) && (_trust != TLSClient::ignore_hostname))
{
if (gnutls_certificate_type_get (_session) == GNUTLS_CRT_X509) // All
{
const gnutls_datum* cert_list;
unsigned int cert_list_size;
gnutls_x509_crt cert;
cert_list = gnutls_certificate_get_peers (_session, &cert_list_size); // All
if (cert_list_size == 0)
{
if (_debug)
std::cout << "c: ERROR Certificate get peers failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
ret = gnutls_x509_crt_init (&cert); // All
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR x509 init failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
ret = gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER); // All
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR x509 cert import. " << gnutls_strerror (ret) << '\n'; // All
gnutls_x509_crt_deinit(cert); // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (gnutls_x509_crt_check_hostname (cert, hostname) == 0) // All
{
if (_debug)
std::cout << "c: ERROR x509 cert check hostname. " << gnutls_strerror (ret) << '\n'; // All
gnutls_x509_crt_deinit(cert);
return GNUTLS_E_CERTIFICATE_ERROR;
}
}
else
return GNUTLS_E_CERTIFICATE_ERROR;
}
#endif
#if GNUTLS_VERSION_NUMBER >= 0x030104
gnutls_certificate_type_t type = gnutls_certificate_type_get (_session); // All
gnutls_datum_t out;
ret = gnutls_certificate_verification_status_print (status, type, &out, 0); // 3.1.4
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR certificate verification status. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (_debug)
std::cout << "c: INFO " << out.data << '\n';
gnutls_free (out.data);
#endif
if (status != 0)
return GNUTLS_E_CERTIFICATE_ERROR;
// Continue handshake.
return 0;
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::send (const std::string& data)
{
std::string packet = "XXXX" + data;
// Encode the length.
unsigned long l = packet.length ();
packet[0] = l >>24;
packet[1] = l >>16;
packet[2] = l >>8;
packet[3] = l;
unsigned int total = 0;
int status;
do
{
status = gnutls_record_send (_session, packet.c_str () + total, packet.length () - total); // All
}
while ((status > 0 && (total += status) < packet.length ()) ||
status == GNUTLS_E_INTERRUPTED ||
status == GNUTLS_E_AGAIN);
if (_debug)
std::cout << "c: INFO Sending 'XXXX"
<< data.c_str ()
<< "' (" << total << " bytes)"
<< std::endl;
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::recv (std::string& data)
{
data = ""; // No appending of data.
int received = 0;
int total = 0;
// Get the encoded length.
unsigned char header[HEADER_SIZE] {};
do
{
received = gnutls_record_recv (_session, header + total, HEADER_SIZE - total); // All
}
while ((received > 0 && (total += received) < HEADER_SIZE) ||
received == GNUTLS_E_INTERRUPTED ||
received == GNUTLS_E_AGAIN);
if (total < HEADER_SIZE) {
throw std::string ("Failed to receive header: ") +
(received < 0 ? gnutls_strerror(received) : "connection lost?");
}
// Decode the length.
unsigned long expected = (header[0]<<24) |
(header[1]<<16) |
(header[2]<<8) |
header[3];
if (_debug)
std::cout << "c: INFO expecting " << expected << " bytes.\n";
if (_limit && expected >= (unsigned long) _limit) {
std::ostringstream err_str;
err_str << "Expected message size " << expected << " is larger than allowed limit " << _limit;
throw err_str.str ();
}
// Arbitrary buffer size.
char buffer[MAX_BUF];
// Keep reading until no more data. Concatenate chunks of data if a) the
// read was interrupted by a signal, and b) if there is more data than
// fits in the buffer.
do
{
int chunk_size = 0;
do
{
received = gnutls_record_recv (_session, buffer + chunk_size, MAX_BUF - chunk_size); // All
if (received > 0) {
total += received;
chunk_size += received;
}
}
while ((received > 0 && (unsigned long) total < expected && chunk_size < MAX_BUF) ||
received == GNUTLS_E_INTERRUPTED ||
received == GNUTLS_E_AGAIN);
// Other end closed the connection.
if (received == 0)
{
if (_debug)
std::cout << "c: INFO Peer has closed the TLS connection\n";
break;
}
// Something happened.
if (received < 0)
throw std::string (gnutls_strerror (received)); // All
data.append (buffer, chunk_size);
// Stop at defined limit.
if (_limit && total > _limit)
break;
}
while (received > 0 && total < (int) expected);
if (_debug)
std::cout << "c: INFO Receiving 'XXXX"
<< data.c_str ()
<< "' (" << total << " bytes)"
<< std::endl;
}
////////////////////////////////////////////////////////////////////////////////
#endif

View file

@ -1,72 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TLSCLIENT
#define INCLUDED_TLSCLIENT
#ifdef HAVE_LIBGNUTLS
#include <string>
#include <gnutls/gnutls.h>
class TLSClient
{
public:
enum trust_level { strict, ignore_hostname, allow_all };
TLSClient () = default;
~TLSClient ();
void limit (int);
void debug (int);
void trust (const enum trust_level);
void ciphers (const std::string&);
void init (const std::string&, const std::string&, const std::string&);
void connect (const std::string&, const std::string&);
void bye ();
int verify_certificate() const;
void send (const std::string&);
void recv (std::string&);
private:
std::string _ca {""};
std::string _cert {""};
std::string _key {""};
std::string _ciphers {""};
std::string _host {""};
std::string _port {""};
gnutls_certificate_credentials_t _credentials {};
gnutls_session_t _session {nullptr};
int _socket {0};
int _limit {0};
bool _debug {false};
enum trust_level _trust {strict};
};
#endif
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -39,10 +39,6 @@
#include <commit.h>
#endif
#ifdef HAVE_LIBGNUTLS
#include <gnutls/gnutls.h>
#endif
////////////////////////////////////////////////////////////////////////////////
CmdDiagnostics::CmdDiagnostics ()
{
@ -135,18 +131,6 @@ int CmdDiagnostics::execute (std::string& output)
#endif
<< '\n';
out << " libgnutls: "
#ifdef HAVE_LIBGNUTLS
#ifdef GNUTLS_VERSION
<< GNUTLS_VERSION
#elif defined LIBGNUTLS_VERSION
<< LIBGNUTLS_VERSION
#endif
#else
<< "n/a"
#endif
<< '\n';
out << " Build type: "
#ifdef CMAKE_BUILD_TYPE
<< CMAKE_BUILD_TYPE
@ -214,82 +198,6 @@ int CmdDiagnostics::execute (std::string& output)
else if ((peditor = getenv ("EDITOR")) != nullptr)
out << " $EDITOR: " << peditor << '\n';
out << " Server: "
<< Context::getContext ().config.get ("taskd.server")
<< '\n';
auto ca_pem = Context::getContext ().config.get ("taskd.ca");
out << " CA: ";
if (ca_pem != "")
{
File file_ca (ca_pem);
if (file_ca.exists ())
out << ca_pem
<< (file_ca.readable () ? ", readable, " : ", not readable, ")
<< file_ca.size ()
<< " bytes\n";
else
out << "not found\n";
}
else
out << "-\n";
auto cert_pem = Context::getContext ().config.get ("taskd.certificate");
out << "Certificate: ";
if (cert_pem != "")
{
File file_cert (cert_pem);
if (file_cert.exists ())
out << cert_pem
<< (file_cert.readable () ? ", readable, " : ", not readable, ")
<< file_cert.size ()
<< " bytes\n";
else
out << "not found\n";
}
else
out << "-\n";
auto key_pem = Context::getContext ().config.get ("taskd.key");
out << " Key: ";
if (key_pem != "")
{
File file_key (key_pem);
if (file_key.exists ())
out << key_pem
<< (file_key.readable () ? ", readable, " : ", not readable, ")
<< file_key.size ()
<< " bytes\n";
else
out << "not found\n";
}
else
out << "-\n";
auto trust_value = Context::getContext ().config.get ("taskd.trust");
if (trust_value == "strict" ||
trust_value == "ignore hostname" ||
trust_value == "allow all")
out << " Trust: " << trust_value << '\n';
else
out << " Trust: Bad value - see 'man taskrc'\n";
out << " Ciphers: "
<< Context::getContext ().config.get ("taskd.ciphers")
<< '\n';
// Get credentials, but mask out the key.
auto credentials = Context::getContext ().config.get ("taskd.credentials");
auto last_slash = credentials.rfind ('/');
if (last_slash != std::string::npos)
credentials = credentials.substr (0, last_slash)
+ '/'
+ std::string (credentials.length () - last_slash - 1, '*');
out << " Creds: "
<< credentials
<< "\n\n";
// Display hook status.
Path hookLocation;
if (Context::getContext ().config.has ("hooks.location"))

View file

@ -145,7 +145,6 @@ int CmdShow::execute (std::string& output)
" debug"
" debug.hooks"
" debug.parser"
" debug.tls"
" default.command"
" default.due"
" default.project"
@ -193,14 +192,11 @@ int CmdShow::execute (std::string& output)
" search.case.sensitive"
" sugar"
" summary.all.projects"
" sync.local.server_dir"
" sync.server.client_key"
" sync.server.encryption_secret"
" sync.server.origin"
" tag.indicator"
" taskd.server"
" taskd.ca"
" taskd.certificate"
" taskd.ciphers"
" taskd.credentials"
" taskd.key"
" taskd.trust"
" undo.style"
" urgency.active.coefficient"
" urgency.scheduled.coefficient"

View file

@ -35,6 +35,7 @@
#include <shared.h>
#include <format.h>
#include <util.h>
#include "tc/Server.h"
////////////////////////////////////////////////////////////////////////////////
CmdSync::CmdSync ()
@ -53,10 +54,37 @@ CmdSync::CmdSync ()
}
////////////////////////////////////////////////////////////////////////////////
int CmdSync::execute (std::string&)
int CmdSync::execute (std::string& output)
{
throw std::string ("Sync support is disabled during transition to TaskChampion.");
return 0;
int status = 0;
tc::Server server;
std::string server_ident;
// If no server is set up, quit.
std::string origin = Context::getContext ().config.get ("sync.server.origin");
std::string client_key = Context::getContext ().config.get ("sync.server.client_key");
std::string encryption_secret = Context::getContext ().config.get ("sync.server.encryption_secret");
std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir");
if (server_dir != "") {
server = tc::Server (server_dir);
server_ident = server_dir;
} else if (origin != "" && client_key != "" && encryption_secret != "") {
server = tc::Server (origin, client_key, encryption_secret);
server_ident = origin;
} else {
throw std::string ("Neither sync.server nor sync.local are configured.");
}
std::stringstream out;
if (Context::getContext ().verbose ("sync"))
out << format ("Syncing with {1}", server_ident)
<< '\n';
Context::getContext ().tdb2.sync(std::move(server), false);
output = out.str ();
return status;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -30,18 +30,12 @@
#include <string>
#include <Command.h>
#include <Msg.h>
#include <TLSClient.h>
class CmdSync : public Command
{
public:
CmdSync ();
int execute (std::string&);
#ifdef HAVE_LIBGNUTLS
private:
bool send (const std::string&, const std::string&, const std::string&, const std::string&, const enum TLSClient::trust_level, const Msg&, Msg&);
#endif
};
#endif

View file

@ -207,8 +207,7 @@ void feedback_unblocked (const Task& task)
///////////////////////////////////////////////////////////////////////////////
void feedback_backlog ()
{
if (Context::getContext ().config.get ("taskd.server") != "" &&
Context::getContext ().verbose ("sync"))
if (Context::getContext ().verbose ("sync"))
{
int count = Context::getContext ().tdb2.num_local_changes ();
if (count)

View file

@ -10,6 +10,7 @@ set (tc_SRCS
ffi.h
util.cpp util.h
Replica.cpp Replica.h
Server.cpp Server.h
WorkingSet.cpp WorkingSet.h
Task.cpp Task.h)

View file

@ -28,6 +28,7 @@
#include <format.h>
#include "tc/Replica.h"
#include "tc/Task.h"
#include "tc/Server.h"
#include "tc/WorkingSet.h"
#include "tc/util.h"
@ -142,6 +143,16 @@ tc::Task tc::Replica::import_task_with_uuid (const std::string &uuid)
return Task (tctask);
}
////////////////////////////////////////////////////////////////////////////////
void tc::Replica::sync (Server server, bool avoid_snapshots)
{
// The server remains owned by this function, per tc_replica_sync docs.
auto res = tc_replica_sync (&*inner, server.inner.get(), avoid_snapshots);
if (res != TC_RESULT_OK) {
throw replica_error ();
}
}
////////////////////////////////////////////////////////////////////////////////
void tc::Replica::undo (int32_t *undone_out)
{

View file

@ -38,6 +38,7 @@
namespace tc {
class Task;
class WorkingSet;
class Server;
// a unique_ptr to a TCReplica which will automatically free the value when
// it goes out of scope.
@ -91,7 +92,7 @@ namespace tc {
tc::Task new_task (Status status, const std::string &description);
tc::Task import_task_with_uuid (const std::string &uuid);
// TODO: struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid);
// TODO: TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots);
void sync(Server server, bool avoid_snapshots);
void undo (int32_t *undone_out);
int64_t num_local_operations ();
int64_t num_undo_points ();

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

@ -0,0 +1,101 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022, Dustin J. Mitchell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <format.h>
#include "tc/Server.h"
#include "tc/util.h"
using namespace tc::ffi;
////////////////////////////////////////////////////////////////////////////////
tc::Server::Server (const std::string &server_dir)
{
TCString tc_server_dir = tc_string_borrow (server_dir.c_str ());
TCString error;
auto tcserver = tc_server_new_local (tc_server_dir, &error);
if (!tcserver) {
auto errmsg = format ("Could not configure local server at {1}: {2}",
server_dir, tc_string_content (&error));
tc_string_free (&error);
throw errmsg;
}
inner = unique_tcserver_ptr (
tcserver,
[](TCServer* rep) { tc_server_free (rep); });
}
////////////////////////////////////////////////////////////////////////////////
tc::Server::Server (const std::string &origin, const std::string &client_key, const std::string &encryption_secret)
{
TCString tc_origin = tc_string_borrow (origin.c_str ());
TCString tc_client_key_str = tc_string_borrow (client_key.c_str ());
TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
TCUuid tc_client_key;
if (tc_uuid_from_str(tc_client_key_str, &tc_client_key) != TC_RESULT_OK) {
tc_string_free(&tc_origin);
tc_string_free(&tc_encryption_secret);
throw "client_key must be a valid UUID";
}
TCString error;
auto tcserver = tc_server_new_remote (tc_origin, tc_client_key, tc_encryption_secret, &error);
if (!tcserver) {
auto errmsg = format ("Could not configure connection to server at {1}: {2}",
origin, tc_string_content (&error));
tc_string_free (&error);
throw errmsg;
}
inner = unique_tcserver_ptr (
tcserver,
[](TCServer* rep) { tc_server_free (rep); });
}
////////////////////////////////////////////////////////////////////////////////
tc::Server::Server (tc::Server &&other) noexcept
{
// move inner from other
inner = unique_tcserver_ptr (
other.inner.release (),
[](TCServer* rep) { tc_server_free (rep); });
}
////////////////////////////////////////////////////////////////////////////////
tc::Server& tc::Server::operator= (tc::Server &&other) noexcept
{
if (this != &other) {
// move inner from other
inner = unique_tcserver_ptr (
other.inner.release (),
[](TCServer* rep) { tc_server_free (rep); });
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////

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

@ -0,0 +1,80 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022, Dustin J. Mitchell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TC_SERVER
#define INCLUDED_TC_SERVER
#include <string>
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#include "tc/ffi.h"
namespace tc {
// a unique_ptr to a TCServer which will automatically free the value when
// it goes out of scope.
using unique_tcserver_ptr = std::unique_ptr<
tc::ffi::TCServer,
std::function<void(tc::ffi::TCServer*)>>;
// Server wraps the TCServer type, managing its memory, errors, and so on.
//
// Except as noted, method names match the suffix to `tc_replica_..`.
class Server
{
public:
// Construct a null server
Server () = default;
// Construct a local server (tc_server_new_local).
Server (const std::string& server_dir);
// Construct a remote server (tc_server_new_remote).
Server (const std::string &origin, const std::string &client_key, const std::string &encryption_secret);
// This object "owns" inner, so copy is not allowed.
Server (const Server &) = delete;
Server &operator=(const Server &) = delete;
// Explicit move constructor and assignment
Server (Server &&) noexcept;
Server &operator=(Server &&) noexcept;
protected:
unique_tcserver_ptr inner;
// Replica accesses the inner pointer to call tc_replica_sync
friend class Replica;
// construct an error message from the given string.
std::string server_error (tc::ffi::TCString string);
};
}
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -767,9 +767,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.3.4"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
dependencies = [
"getrandom",
"serde",