diff options
Diffstat (limited to 'louloulibs')
31 files changed, 357 insertions, 273 deletions
diff --git a/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt index bf53504..908c35f 100644 --- a/louloulibs/CMakeLists.txt +++ b/louloulibs/CMakeLists.txt @@ -143,4 +143,25 @@ if(SYSTEMD_FOUND) target_link_libraries(xmpplib ${SYSTEMD_LIBRARIES}) endif() +# +## Check if we have std::get_time +# +include(CheckCXXSourceCompiles) + +check_cxx_source_compiles(" + #include <iomanip> + int main() + { std::get_time(nullptr, \"\"); }" + HAS_GET_TIME) + +mark_as_advanced(HAS_GET_TIME) + +check_cxx_source_compiles(" + #include <iomanip> + int main() + { std::put_time(nullptr, \"\"); }" + HAS_PUT_TIME) + +mark_as_advanced(HAS_PUT_TIME) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/louloulibs.h.cmake ${CMAKE_BINARY_DIR}/src/louloulibs.h) diff --git a/louloulibs/cmake/Modules/FindLIBUUID.cmake b/louloulibs/cmake/Modules/FindLIBUUID.cmake index 17d3c42..f344249 100644 --- a/louloulibs/cmake/Modules/FindLIBUUID.cmake +++ b/louloulibs/cmake/Modules/FindLIBUUID.cmake @@ -19,7 +19,7 @@ include(FindPkgConfig) pkg_check_modules(LIBUUID uuid) if(NOT LIBUUID_FOUND) - find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h + find_path(LIBUUID_INCLUDE_DIRS NAMES uuid/uuid.h PATH_SUFFIXES uuid DOC "The libuuid include directory") diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake index 2feaf4e..6131b70 100644 --- a/louloulibs/louloulibs.h.cmake +++ b/louloulibs/louloulibs.h.cmake @@ -6,4 +6,6 @@ #cmakedefine BOTAN_FOUND #cmakedefine CARES_FOUND #cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" -#cmakedefine PROJECT_NAME "${PROJECT_NAME}"
\ No newline at end of file +#cmakedefine PROJECT_NAME "${PROJECT_NAME}" +#cmakedefine HAS_GET_TIME +#cmakedefine HAS_PUT_TIME diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index ee83c3b..ed04d24 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -29,7 +29,7 @@ BasicCredentialsManager::BasicCredentialsManager(const TCPSocketHandler* const s socket_handler(socket_handler), trusted_fingerprint{} { - this->load_certs(); + BasicCredentialsManager::load_certs(); } void BasicCredentialsManager::set_trusted_fingerprint(const std::string& fingerprint) @@ -62,17 +62,8 @@ void BasicCredentialsManager::verify_certificate_chain(const std::string& type, } } -void BasicCredentialsManager::load_certs() +bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector<std::string>& paths) { - // Only load the certificates the first time - if (BasicCredentialsManager::certs_loaded) - return; - const std::string conf_path = Config::get("ca_file", ""); - std::vector<std::string> paths; - if (conf_path.empty()) - paths = default_cert_files; - else - paths.push_back(conf_path); for (const auto& path: paths) { try @@ -87,25 +78,39 @@ void BasicCredentialsManager::load_certs() // will be ignored. As a result, some TLS connection may be refused // because the certificate is signed by an issuer that was ignored. try { - const Botan::X509_Certificate cert(bundle); - BasicCredentialsManager::certificate_store.add_certificate(cert); - } catch (const Botan::Decoding_Error& error) - { + Botan::X509_Certificate cert(bundle); + BasicCredentialsManager::certificate_store.add_certificate(std::move(cert)); + } catch (const Botan::Decoding_Error& error) { continue; } } // Only use the first file that can successfully be read. - goto success; + return true; } - catch (Botan::Stream_IO_Error& e) + catch (const Botan::Stream_IO_Error& e) { log_debug(e.what()); } } - // If we could not open one of the files, print a warning - log_warning("The CA could not be loaded, TLS negociation will probably fail."); - success: - BasicCredentialsManager::certs_loaded = true; + return false; +} + +void BasicCredentialsManager::load_certs() +{ + // Only load the certificates the first time + if (BasicCredentialsManager::certs_loaded) + return; + const std::string conf_path = Config::get("ca_file", ""); + std::vector<std::string> paths; + if (conf_path.empty()) + paths = default_cert_files; + else + paths.push_back(conf_path); + + if (BasicCredentialsManager::try_to_open_one_ca_bundle(paths)) + BasicCredentialsManager::certs_loaded = true; + else + log_warning("The CA could not be loaded, TLS negociation will probably fail."); } std::vector<Botan::Certificate_Store*> BasicCredentialsManager::trusted_certificate_authorities(const std::string&, const std::string&) diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index 0fc4b89..7557372 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -29,6 +29,7 @@ public: private: const TCPSocketHandler* const socket_handler; + static bool try_to_open_one_ca_bundle(const std::vector<std::string>& paths); static void load_certs(); static Botan::Certificate_Store_In_Memory certificate_store; static bool certs_loaded; diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index e267944..fef0cfc 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -46,11 +46,7 @@ void DNSHandler::destroy() void DNSHandler::gethostbyname(const std::string& name, ares_host_callback callback, void* data, int family) { - if (family == AF_INET) - ::ares_gethostbyname(this->channel, name.data(), family, - callback, data); - else - ::ares_gethostbyname(this->channel, name.data(), family, + ::ares_gethostbyname(this->channel, name.data(), family, callback, data); } diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp index 5fd08cb..403a5be 100644 --- a/louloulibs/network/dns_socket_handler.cpp +++ b/louloulibs/network/dns_socket_handler.cpp @@ -42,7 +42,8 @@ bool DNSSocketHandler::is_connected() const void DNSSocketHandler::remove_from_poller() { - this->poller->remove_socket_handler(this->socket); + if (this->poller->is_managing_socket(this->socket)) + this->poller->remove_socket_handler(this->socket); } #endif /* CARES_FOUND */ diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 8a6fd97..9f5bcfb 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -95,7 +95,7 @@ void Poller::remove_socket_handler(const socket_t socket) void Poller::watch_send_events(SocketHandler* socket_handler) { #if POLLER == POLL - for (size_t i = 0; i <= this->nfds; ++i) + for (size_t i = 0; i < this->nfds; ++i) { if (this->fds[i].fd == socket_handler->get_socket()) { @@ -171,7 +171,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) // We cannot possibly have more ready events than the number of fds we are // watching assert(static_cast<unsigned int>(nb_events) <= this->nfds); - for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i) + for (size_t i = 0; i < this->nfds && nb_events != 0; ++i) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); if (this->fds[i].revents == 0) @@ -186,7 +186,8 @@ int Poller::poll(const std::chrono::milliseconds& timeout) socket_handler->on_send(); nb_events--; } - else if (this->fds[i].revents & POLLOUT) + else if (this->fds[i].revents & POLLOUT || + this->fds[i].revents & POLLIN) { socket_handler->connect(); nb_events--; @@ -226,3 +227,8 @@ size_t Poller::size() const { return this->socket_handlers.size(); } + +bool Poller::is_managing_socket(const socket_t socket) const +{ + return (this->socket_handlers.find(socket) != this->socket_handlers.end()); +} diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp index fc1a1a1..e39e438 100644 --- a/louloulibs/network/poller.hpp +++ b/louloulibs/network/poller.hpp @@ -74,6 +74,10 @@ public: * Returns the number of SocketHandlers managed by the poller. */ size_t size() const; + /** + * Whether the given socket is managed by the poller + */ + bool is_managing_socket(const socket_t socket) const; private: /** diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index 9d6de23..2987aaa 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -2,6 +2,7 @@ #include <network/resolver.hpp> #include <string.h> #include <arpa/inet.h> +#include <cstdlib> using namespace std::string_literals; @@ -116,12 +117,13 @@ void Resolver::fill_ares_addrinfo4(const struct hostent* hostent) current->ai_protocol = 0; current->ai_addrlen = sizeof(struct sockaddr_in); - struct sockaddr_in* addr = new struct sockaddr_in; - addr->sin_family = hostent->h_addrtype; - addr->sin_port = htons(strtoul(this->port.data(), nullptr, 10)); - addr->sin_addr.s_addr = (*address)->s_addr; + struct sockaddr_in* ai_addr = new struct sockaddr_in; + + ai_addr->sin_family = hostent->h_addrtype; + ai_addr->sin_port = htons(std::strtoul(this->port.data(), nullptr, 10)); + ai_addr->sin_addr.s_addr = (*address)->s_addr; - current->ai_addr = reinterpret_cast<struct sockaddr*>(addr); + current->ai_addr = reinterpret_cast<struct sockaddr*>(ai_addr); current->ai_next = nullptr; current->ai_canonname = nullptr; @@ -147,14 +149,14 @@ void Resolver::fill_ares_addrinfo6(const struct hostent* hostent) current->ai_protocol = 0; current->ai_addrlen = sizeof(struct sockaddr_in6); - struct sockaddr_in6* addr = new struct sockaddr_in6; - addr->sin6_family = hostent->h_addrtype; - addr->sin6_port = htons(strtoul(this->port.data(), nullptr, 10)); - ::memcpy(addr->sin6_addr.s6_addr, (*address)->s6_addr, 16); - addr->sin6_flowinfo = 0; - addr->sin6_scope_id = 0; + struct sockaddr_in6* ai_addr = new struct sockaddr_in6; + ai_addr->sin6_family = hostent->h_addrtype; + ai_addr->sin6_port = htons(std::strtoul(this->port.data(), nullptr, 10)); + ::memcpy(ai_addr->sin6_addr.s6_addr, (*address)->s6_addr, sizeof(ai_addr->sin6_addr.s6_addr)); + ai_addr->sin6_flowinfo = 0; + ai_addr->sin6_scope_id = 0; - current->ai_addr = reinterpret_cast<struct sockaddr*>(addr); + current->ai_addr = reinterpret_cast<struct sockaddr*>(ai_addr); current->ai_canonname = nullptr; current->ai_next = prev; diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp index afe6e2b..29e6f3a 100644 --- a/louloulibs/network/resolver.hpp +++ b/louloulibs/network/resolver.hpp @@ -11,8 +11,9 @@ #include <sys/socket.h> #include <netdb.h> -struct AddrinfoDeleter +class AddrinfoDeleter { + public: void operator()(struct addrinfo* addr) { #ifdef CARES_FOUND diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index eeb41fe..ea79a18 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -14,7 +14,7 @@ public: poller(poller), socket(socket) {} - virtual ~SocketHandler() {} + virtual ~SocketHandler() = default; SocketHandler(const SocketHandler&) = delete; SocketHandler(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 5420b1c..1dddde5 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -80,7 +80,7 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) } if (!rp) log_error("Failed to bind socket to ", this->bind_addr, ": ", - strerror(bind_error)); + strerror(errno)); else log_info("Socket successfully bound to ", this->bind_addr); } @@ -103,8 +103,6 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po this->port = port; this->use_tls = tls; - utils::ScopeGuard sg; - struct addrinfo* addr_res; if (!this->connecting) @@ -181,6 +179,8 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po if (this->use_tls) this->start_tls(); #endif + this->connection_date = std::chrono::system_clock::now(); + this->on_connected(); return ; } @@ -294,7 +294,8 @@ void TCPSocketHandler::on_send() // unconsting the content of s is ok, sendmsg will never modify it msg_iov[msg.msg_iovlen].iov_base = const_cast<char*>(s.data()); msg_iov[msg.msg_iovlen].iov_len = s.size(); - if (++msg.msg_iovlen == UIO_FASTIOV) + msg.msg_iovlen++; + if (msg.msg_iovlen == UIO_FASTIOV) break; } ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); @@ -307,23 +308,24 @@ void TCPSocketHandler::on_send() else { // remove all the strings that were successfully sent. - for (auto it = this->out_buf.begin(); - it != this->out_buf.end();) + auto it = this->out_buf.begin(); + while (it != this->out_buf.end()) { - if (static_cast<size_t>(res) >= (*it).size()) + if (static_cast<size_t>(res) >= it->size()) { - res -= (*it).size(); - it = this->out_buf.erase(it); + res -= it->size(); + ++it; } else { // If one string has partially been sent, we use substr to // crop it if (res > 0) - (*it) = (*it).substr(res, std::string::npos); + *it = it->substr(res, std::string::npos); break; } } + this->out_buf.erase(this->out_buf.begin(), it); if (this->out_buf.empty()) this->poller->stop_watching_send_events(this); } @@ -397,6 +399,16 @@ bool TCPSocketHandler::is_connecting() const return this->connecting || this->resolver.is_resolving(); } +bool TCPSocketHandler::is_using_tls() const +{ + return this->use_tls; +} + +std::string TCPSocketHandler::get_port() const +{ + return this->port; +} + void* TCPSocketHandler::get_receive_buffer(const size_t) const { return nullptr; @@ -418,15 +430,14 @@ void TCPSocketHandler::start_tls() void TCPSocketHandler::tls_recv() { static constexpr size_t buf_size = 4096; - char recv_buf[buf_size]; + Botan::byte recv_buf[buf_size]; const ssize_t size = this->do_recv(recv_buf, buf_size); if (size > 0) { const bool was_active = this->tls->is_active(); try { - this->tls->received_data(reinterpret_cast<const Botan::byte*>(recv_buf), - static_cast<size_t>(size)); + this->tls->received_data(recv_buf, static_cast<size_t>(size)); } catch (const Botan::TLS::TLS_Exception& e) { // May happen if the server sends malformed TLS data (buggy server, // or more probably we are just connected to a server that sends @@ -449,9 +460,8 @@ void TCPSocketHandler::tls_send(std::string&& data) const bool was_active = this->tls->is_active(); if (!this->pre_buf.empty()) { - this->tls->send(reinterpret_cast<const Botan::byte*>(this->pre_buf.data()), - this->pre_buf.size()); - this->pre_buf = ""; + this->tls->send(this->pre_buf.data(), this->pre_buf.size()); + this->pre_buf.clear(); } if (!data.empty()) this->tls->send(reinterpret_cast<const Botan::byte*>(data.data()), @@ -460,7 +470,9 @@ void TCPSocketHandler::tls_send(std::string&& data) this->on_tls_activated(); } else - this->pre_buf += data; + this->pre_buf.insert(this->pre_buf.end(), + std::make_move_iterator(data.begin()), + std::make_move_iterator(data.end())); } void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index b0ba493..20a3e5a 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -13,6 +13,7 @@ #include <netinet/in.h> #include <netdb.h> +#include <chrono> #include <vector> #include <memory> #include <string> @@ -106,6 +107,9 @@ public: #endif bool is_connected() const override final; bool is_connecting() const; + bool is_using_tls() const; + std::string get_port() const; + std::chrono::system_clock::time_point connection_date; private: /** @@ -266,9 +270,6 @@ private: * An additional buffer to keep data that the user wants to send, but * cannot because the handshake is not done. */ - std::string pre_buf; + std::vector<Botan::byte> pre_buf; #endif // BOTAN_FOUND }; - - - diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp index 507f38a..60f2212 100644 --- a/louloulibs/utils/encoding.cpp +++ b/louloulibs/utils/encoding.cpp @@ -75,13 +75,12 @@ namespace utils std::string remove_invalid_xml_chars(const std::string& original) { // The given string MUST be a valid utf-8 string - unsigned char* res = new unsigned char[original.size()]; - ScopeGuard sg([&res]() { delete[] res;}); + std::vector<char> res(original.size(), '\0'); // pointer where we write valid chars - unsigned char* r = res; + char* r = res.data(); - const unsigned char* str = reinterpret_cast<const unsigned char*>(original.c_str()); + const char* str = original.c_str(); std::bitset<20> codepoint; while (*str) @@ -140,7 +139,7 @@ namespace utils else throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); } - return std::string(reinterpret_cast<char*>(res), r-res); + return {res.data(), static_cast<size_t>(r - res.data())}; } std::string convert_to_utf8(const std::string& str, const char* charset) @@ -152,7 +151,7 @@ namespace utils throw std::runtime_error("Cannot convert into UTF-8"); // Make sure cd is always closed when we leave this function - ScopeGuard sg([&]{ iconv_close(cd); }); + const auto sg = utils::make_scope_guard([&cd](auto&&){ iconv_close(cd); }); size_t inbytesleft = str.size(); @@ -169,7 +168,7 @@ namespace utils char* outbuf_ptr = outbuf; // Make sure outbuf is always deleted when we leave this function - sg.add_callback([&]{ delete[] outbuf; }); + const auto sg2 = utils::make_scope_guard([outbuf](auto&&){ delete[] outbuf; }); bool done = false; while (done == false) @@ -197,12 +196,8 @@ namespace utils outbuf_ptr++; done = true; break; - case E2BIG: - // This should never happen - done = true; - break; - default: - // This should happen even neverer + case E2BIG: // This should never happen + default: // This should happen even neverer done = true; break; } diff --git a/louloulibs/utils/get_first_non_empty.cpp b/louloulibs/utils/get_first_non_empty.cpp new file mode 100644 index 0000000..5b3bedb --- /dev/null +++ b/louloulibs/utils/get_first_non_empty.cpp @@ -0,0 +1,11 @@ +#include <utils/get_first_non_empty.hpp> + +bool is_empty(const std::string& val) +{ + return val.empty(); +} + +bool is_empty(const int& val) +{ + return val == 0; +} diff --git a/louloulibs/utils/get_first_non_empty.hpp b/louloulibs/utils/get_first_non_empty.hpp new file mode 100644 index 0000000..a38f5fb --- /dev/null +++ b/louloulibs/utils/get_first_non_empty.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <string> + +bool is_empty(const std::string& val); +bool is_empty(const int& val); + +template <typename T> +T get_first_non_empty(T&& last) +{ + return last; +} + +template <typename T, typename... Args> +T get_first_non_empty(T&& first, Args&&... args) +{ + if (!is_empty(first)) + return first; + return get_first_non_empty(std::forward<Args>(args)...); +} diff --git a/louloulibs/utils/scopeguard.hpp b/louloulibs/utils/scopeguard.hpp index ee1e2ef..cd0e89e 100644 --- a/louloulibs/utils/scopeguard.hpp +++ b/louloulibs/utils/scopeguard.hpp @@ -1,6 +1,7 @@ #pragma once #include <functional> +#include <memory> #include <vector> /** @@ -85,5 +86,11 @@ private: }; +template<typename F> +auto make_scope_guard(F&& f) +{ + return std::unique_ptr<void, std::decay_t<F>>{(void*)1, std::forward<F>(f)}; +} + } diff --git a/louloulibs/utils/sha1.cpp b/louloulibs/utils/sha1.cpp index 76476df..f75bc2a 100644 --- a/louloulibs/utils/sha1.cpp +++ b/louloulibs/utils/sha1.cpp @@ -119,36 +119,3 @@ uint8_t* sha1_result(sha1nfo *s) { // Return pointer to hash (20 characters) return s->state.b; } - -#define HMAC_IPAD 0x36 -#define HMAC_OPAD 0x5c - -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { - uint8_t i; - memset(s->keyBuffer, 0, BLOCK_LENGTH); - if (keyLength > BLOCK_LENGTH) { - // Hash long keys - sha1_init(s); - for (;keyLength--;) sha1_writebyte(s, *key++); - memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); - } else { - // Block length keys are used as is - memcpy(s->keyBuffer, key, keyLength); - } - // Start inner hash - sha1_init(s); - for (i=0; i<BLOCK_LENGTH; i++) { - sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD); - } -} - -uint8_t* sha1_resultHmac(sha1nfo *s) { - uint8_t i; - // Complete inner hash - memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); - // Calculate outer hash - sha1_init(s); - for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); - for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]); - return sha1_result(s); -} diff --git a/louloulibs/utils/sha1.hpp b/louloulibs/utils/sha1.hpp index d02de75..d436782 100644 --- a/louloulibs/utils/sha1.hpp +++ b/louloulibs/utils/sha1.hpp @@ -31,5 +31,3 @@ void sha1_init(sha1nfo *s); void sha1_writebyte(sha1nfo *s, uint8_t data); void sha1_write(sha1nfo *s, const char *data, size_t len); uint8_t* sha1_result(sha1nfo *s); -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); -uint8_t* sha1_resultHmac(sha1nfo *s); diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp new file mode 100644 index 0000000..afd6117 --- /dev/null +++ b/louloulibs/utils/time.cpp @@ -0,0 +1,70 @@ +#include <utils/time.hpp> +#include <time.h> + +#include <sstream> +#include <iomanip> +#include <locale> + +#include "louloulibs.h" + +namespace utils +{ +std::string to_string(const std::time_t& timestamp) +{ + constexpr std::size_t stamp_size = 21; + char date_buf[stamp_size]; + if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)) != stamp_size - 1) + return ""; + return {std::begin(date_buf), std::end(date_buf) - 1}; +} + +std::time_t parse_datetime(const std::string& stamp) +{ + static const char* format = "%Y-%m-%dT%H:%M:%S"; + std::tm t = {}; +#ifdef HAS_GET_TIME + std::istringstream ss(stamp); + ss.imbue(std::locale("en_US.utf-8")); + + std::string timezone; + ss >> std::get_time(&t, format) >> timezone; + if (ss.fail()) + return -1; +#else + /* Y - m - d T H : M : S */ + constexpr std::size_t stamp_size_without_tz = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + if (!strptime(stamp.data(), format, &t)) { + return -1; + } + const std::string timezone(stamp.data() + stamp_size_without_tz); +#endif + + if (timezone.empty()) + return -1; + + if (timezone.compare(0, 1, "Z") != 0) + { + std::stringstream tz_ss; + tz_ss << timezone; + int multiplier = -1; + char prefix; + int hours; + char sep; + int minutes; + tz_ss >> prefix >> hours >> sep >> minutes; + if (tz_ss.fail()) + return -1; + if (prefix == '-') + multiplier = +1; + else if (prefix != '+') + return -1; + + t.tm_hour += multiplier * hours; + t.tm_min += multiplier * minutes; + } + return ::timegm(&t); +} + +} + + diff --git a/louloulibs/utils/time.hpp b/louloulibs/utils/time.hpp new file mode 100644 index 0000000..c71cd9c --- /dev/null +++ b/louloulibs/utils/time.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include <ctime> +#include <string> + +namespace utils +{ +std::string to_string(const std::time_t& timestamp); +std::time_t parse_datetime(const std::string& stamp); +}
\ No newline at end of file diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp index 17c4e67..540cac0 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -15,9 +15,12 @@ const std::map<const std::string, const AdhocCommand>& AdhocCommandsHandler::get return this->commands; } -std::map<const std::string, const AdhocCommand>& AdhocCommandsHandler::get_commands() +void AdhocCommandsHandler::add_command(std::string name, AdhocCommand command) { - return this->commands; + const auto found = this->commands.find(name); + if (found != this->commands.end()) + throw std::runtime_error("Trying to add an ad-hoc command that already exist: "s + name); + this->commands.emplace(std::make_pair(std::move(name), std::move(command))); } XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, const std::string& to, XmlNode command_node) diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp index 91eb5bd..e37d913 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -31,9 +31,9 @@ public: */ const std::map<const std::string, const AdhocCommand>& get_commands() const; /** - * This one can be used to add new commands. + * Add a command into the list, associated with the given name */ - std::map<const std::string, const AdhocCommand>& get_commands(); + void add_command(std::string name, AdhocCommand command); /** * Find the requested command, create a new session or use an existing * one, and process the request (provide a new form, an error, or a diff --git a/louloulibs/xmpp/auth.cpp b/louloulibs/xmpp/auth.cpp new file mode 100644 index 0000000..c20f95d --- /dev/null +++ b/louloulibs/xmpp/auth.cpp @@ -0,0 +1,21 @@ +#include <xmpp/auth.hpp> + +#include <utils/sha1.hpp> + +#include <iomanip> +#include <sstream> + +std::string get_handshake_digest(const std::string& stream_id, const std::string& secret) +{ + sha1nfo sha1; + sha1_init(&sha1); + sha1_write(&sha1, stream_id.data(), stream_id.size()); + sha1_write(&sha1, secret.data(), secret.size()); + const uint8_t* result = sha1_result(&sha1); + + std::ostringstream digest; + for (int i = 0; i < HASH_LENGTH; i++) + digest << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(result[i]); + + return digest.str(); +} diff --git a/louloulibs/xmpp/auth.hpp b/louloulibs/xmpp/auth.hpp new file mode 100644 index 0000000..34a2116 --- /dev/null +++ b/louloulibs/xmpp/auth.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include <string> + +std::string get_handshake_digest(const std::string& stream_id, const std::string& secret); + diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp index 08327ef..85e835c 100644 --- a/louloulibs/xmpp/jid.hpp +++ b/louloulibs/xmpp/jid.hpp @@ -26,7 +26,12 @@ public: } std::string full() const { - return this->local + "@" + this->domain + "/" + this->resource; + std::string res = this->domain; + if (!this->local.empty()) + res = this->local + "@" + this->domain; + if (!this->resource.empty()) + res += "/" + this->resource; + return res; } }; diff --git a/louloulibs/xmpp/roster.cpp b/louloulibs/xmpp/roster.cpp deleted file mode 100644 index a14a384..0000000 --- a/louloulibs/xmpp/roster.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include <xmpp/roster.hpp> - -RosterItem::RosterItem(const std::string& jid, const std::string& name, - std::vector<std::string>& groups): - jid(jid), - name(name), - groups(groups) -{ -} - -RosterItem::RosterItem(const std::string& jid, const std::string& name): - jid(jid), - name(name), - groups{} -{ -} - -void Roster::clear() -{ - this->items.clear(); -} diff --git a/louloulibs/xmpp/roster.hpp b/louloulibs/xmpp/roster.hpp deleted file mode 100644 index aa1b449..0000000 --- a/louloulibs/xmpp/roster.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - - -#include <algorithm> -#include <string> -#include <vector> - -class RosterItem -{ -public: - RosterItem(const std::string& jid, const std::string& name, - std::vector<std::string>& groups); - RosterItem(const std::string& jid, const std::string& name); - RosterItem() = default; - ~RosterItem() = default; - RosterItem(const RosterItem&) = default; - RosterItem(RosterItem&&) = default; - RosterItem& operator=(const RosterItem&) = default; - RosterItem& operator=(RosterItem&&) = default; - - std::string jid; - std::string name; - std::vector<std::string> groups; - -private: -}; - -/** - * Keep track of the last known stat of a JID's roster - */ -class Roster -{ -public: - Roster() = default; - ~Roster() = default; - - void clear(); - - template <typename... ArgsType> - RosterItem* add_item(ArgsType&&... args) - { - this->items.emplace_back(std::forward<ArgsType>(args)...); - auto it = this->items.end() - 1; - return &*it; - } - RosterItem* get_item(const std::string& jid) - { - auto it = std::find_if(this->items.begin(), this->items.end(), - [this, &jid](const auto& item) - { - return item.jid == jid; - }); - if (it != this->items.end()) - return &*it; - return nullptr; - } - const std::vector<RosterItem>& get_items() const - { - return this->items; - } - -private: - std::vector<RosterItem> items; - - Roster(const Roster&) = delete; - Roster(Roster&&) = delete; - Roster& operator=(const Roster&) = delete; - Roster& operator=(Roster&&) = delete; -}; - - diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index e87cdf7..fa8b0a5 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -5,16 +5,18 @@ #include <xmpp/xmpp_component.hpp> #include <config/config.hpp> +#include <utils/time.hpp> +#include <xmpp/auth.hpp> #include <xmpp/jid.hpp> -#include <utils/sha1.hpp> #include <stdexcept> #include <iostream> #include <set> -#include <stdio.h> +#include <uuid/uuid.h> -#include <uuid.h> +#include <cstdlib> +#include <set> #include <louloulibs.h> #ifdef SYSTEMD_FOUND @@ -136,17 +138,7 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) } // Try to authenticate - char digest[HASH_LENGTH * 2 + 1]; - sha1nfo sha1; - sha1_init(&sha1); - sha1_write(&sha1, this->stream_id.data(), this->stream_id.size()); - sha1_write(&sha1, this->secret.data(), this->secret.size()); - const uint8_t* result = sha1_result(&sha1); - for (int i=0; i < HASH_LENGTH; i++) - sprintf(digest + (i*2), "%02x", result[i]); - digest[HASH_LENGTH * 2] = '\0'; - - auto data = "<handshake xmlns='" COMPONENT_NS "'>"s + digest + "</handshake>"; + auto data = "<handshake xmlns='" COMPONENT_NS "'>"s + get_handshake_digest(this->stream_id, this->secret) + "</handshake>"; log_debug("XMPP SENDING: ", data); this->send_data(std::move(data)); } @@ -230,9 +222,8 @@ void XmppComponent::close_document() this->doc_open = false; } -void XmppComponent::handle_handshake(const Stanza& stanza) +void XmppComponent::handle_handshake(const Stanza&) { - (void)stanza; this->authenticated = true; this->ever_auth = true; log_info("Authenticated with the XMPP server"); @@ -270,7 +261,8 @@ void* XmppComponent::get_receive_buffer(const size_t size) const return this->parser.get_buffer(size); } -void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type, const bool fulljid) +void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, + const std::string& type, const bool fulljid, const bool nocopy) { XmlNode node("message"); node["to"] = to; @@ -291,6 +283,18 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con html.add_child(std::move(std::get<1>(body))); node.add_child(std::move(html)); } + + if (nocopy) + { + XmlNode private_node("private"); + private_node["xmlns"] = "urn:xmpp:carbons:2"; + node.add_child(std::move(private_node)); + + XmlNode nocopy("no-copy"); + nocopy["xmlns"] = "urn:xmpp:hints"; + node.add_child(std::move(nocopy)); + } + this->send_stanza(node); } @@ -363,31 +367,6 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, this->send_stanza(presence); } -void XmppComponent::send_invalid_user_error(const std::string& user_name, const std::string& to) -{ - Stanza message("message"); - message["from"] = user_name + "@" + this->served_hostname; - message["to"] = to; - message["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - message.add_child(std::move(x)); - XmlNode error("error"); - error["type"] = "cancel"; - XmlNode item_not_found("item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - error.add_child(std::move(item_not_found)); - XmlNode text("text"); - text["xmlns"] = STANZA_NS; - text["xml:lang"] = "en"; - text.set_inner(user_name + - " is not a valid IRC user name. A correct user jid is of the form: <nick>!<server>@" + - this->served_hostname); - error.add_child(std::move(text)); - message.add_child(std::move(error)); - this->send_stanza(message); -} - void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who) { XmlNode message("message"); @@ -426,6 +405,29 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str this->send_stanza(message); } +void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, std::time_t timestamp) +{ + Stanza message("message"); + message["to"] = jid_to; + if (!nick.empty()) + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else + message["from"] = muc_name + "@" + this->served_hostname; + message["type"] = "groupchat"; + + XmlNode body("body"); + body.set_inner(body_txt); + message.add_child(std::move(body)); + + XmlNode delay("delay"); + delay["xmlns"] = DELAY_NS; + delay["from"] = muc_name + "@" + this->served_hostname; + delay["stamp"] = utils::to_string(timestamp); + + message.add_child(std::move(delay)); + this->send_stanza(message); +} + void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) { Stanza presence("presence"); @@ -483,11 +485,8 @@ void XmppComponent::send_nick_change(const std::string& muc_name, this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self); } -void XmppComponent::kick_user(const std::string& muc_name, - const std::string& target, - const std::string& txt, - const std::string& author, - const std::string& jid_to) +void XmppComponent::kick_user(const std::string& muc_name, const std::string& target, const std::string& txt, + const std::string& author, const std::string& jid_to, const bool self) { Stanza presence("presence"); presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; @@ -509,6 +508,12 @@ void XmppComponent::kick_user(const std::string& muc_name, XmlNode status("status"); status["code"] = "307"; x.add_child(std::move(status)); + if (self) + { + XmlNode status("status"); + status["code"] = "110"; + x.add_child(std::move(status)); + } presence.add_child(std::move(x)); this->send_stanza(presence); } diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 5fc6d2e..5f5f937 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -9,6 +9,7 @@ #include <unordered_map> #include <memory> #include <string> +#include <ctime> #include <map> #define STREAM_NS "http://etherx.jabber.org/streams" @@ -25,6 +26,13 @@ #define VERSION_NS "jabber:iq:version" #define ADHOC_NS "http://jabber.org/protocol/commands" #define PING_NS "urn:xmpp:ping" +#define DELAY_NS "urn:xmpp:delay" +#define MAM_NS "urn:xmpp:mam:1" +#define FORWARD_NS "urn:xmpp:forward:0" +#define CLIENT_NS "jabber:client" +#define DATAFORM_NS "jabber:x:data" +#define RSM_NS "http://jabber.org/protocol/rsm" +#define MUC_TRAFFIC_NS "http://jabber.org/protocol/muc#traffic" /** * An XMPP component, communicating with an XMPP server using the protocole @@ -101,9 +109,8 @@ public: * If fulljid is false, the provided 'from' doesn't contain the * server-part of the JID and must be added. */ - void send_message(const std::string& from, Xmpp::body&& body, - const std::string& to, const std::string& type, - const bool fulljid=false); + void send_message(const std::string& from, Xmpp::body&& body, const std::string& to, + const std::string& type, const bool fulljid, const bool nocopy=false); /** * Send a join from a new participant */ @@ -121,12 +128,6 @@ public: const std::string& nick, const std::string& to); /** - * Send an error to indicate that the user tried to send a message to an - * invalid user. - */ - void send_invalid_user_error(const std::string& user_name, - const std::string& to); - /** * Send the MUC topic to the user */ void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to, const std::string& who); @@ -135,6 +136,11 @@ public: */ void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to); /** + * Send a message, with a <delay/> element, part of a MUC history + */ + void send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body, + const std::string& jid_to, const std::time_t timestamp); + /** * Send an unavailable presence for this nick */ void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); @@ -151,11 +157,8 @@ public: /** * An user is kicked from a room */ - void kick_user(const std::string& muc_name, - const std::string& target, - const std::string& reason, - const std::string& author, - const std::string& jid_to); + void kick_user(const std::string& muc_name, const std::string& target, const std::string& reason, + const std::string& author, const std::string& jid_to, const bool self); /** * Send a generic presence error */ @@ -208,6 +211,9 @@ public: virtual void after_handshake() {} + const std::string& get_served_hostname() const + { return this->served_hostname; } + /** * Whether or not we ever succeeded our authentication to the XMPP server */ |